├── .gitignore ├── .idea └── .gitignore ├── README.md ├── app ├── .gitignore ├── agconnect-services.json ├── build.gradle.kts ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── developersancho │ │ └── jetrorty │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── developersancho │ │ └── jetrorty │ │ ├── app │ │ ├── JetNetworkConfig.kt │ │ └── JetRortyApp.kt │ │ ├── di │ │ ├── AppModule.kt │ │ └── ProviderModule.kt │ │ ├── navigation │ │ ├── MainActivity.kt │ │ ├── MainRoot.kt │ │ ├── MainViewModel.kt │ │ └── RootNavGraph.kt │ │ └── provider │ │ ├── AppLanguageProvider.kt │ │ ├── AppNavigationProvider.kt │ │ ├── AppResourceProvider.kt │ │ └── AppThemeProvider.kt │ └── test │ └── java │ └── com │ └── developersancho │ └── jetrorty │ └── ExampleUnitTest.kt ├── art ├── architecture.png ├── architecturecircles.png ├── clean_arch.jpeg ├── project.png └── screenshots │ ├── about-dark.png │ ├── about.png │ ├── character-detail-dark.png │ ├── character-detail.png │ ├── characters-dark.png │ ├── characters-favs-dark.png │ ├── characters-favs.png │ ├── characters.png │ ├── episode-detail-dark.png │ ├── episode-detail.png │ ├── episodes-dark.png │ ├── episodes-favs-dark.png │ ├── episodes-favs.png │ ├── episodes.png │ ├── intro-dark.png │ ├── intro.png │ ├── language-dark.png │ ├── language.png │ ├── location-detail-dark.png │ ├── location-detail.png │ ├── locations-dark.png │ ├── locations-favs-dark.png │ ├── locations-favs.png │ ├── locations.png │ ├── settings-dark.png │ ├── settings.png │ └── splash.png ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ ├── Configs.kt │ ├── Libs.kt │ ├── codeanalyzetools │ ├── detekt-check.gradle.kts │ ├── jacoco-multi-report.gradle.kts │ ├── jacoco-report.gradle.kts │ ├── ktlint-check.gradle.kts │ ├── quality.gradle.kts │ ├── spotless-check.gradle.kts │ └── version-check.gradle.kts │ ├── codequality │ └── DependencyUpdatePlugin.kt │ ├── commons │ ├── AndroidCoreLibraryPlugin.kt │ ├── CommonExtensions.kt │ ├── android-compose.gradle.kts │ ├── android-feature.gradle.kts │ ├── android-library.gradle.kts │ └── dagger-hilt.gradle.kts │ └── extensions │ ├── BuildTypeExtensions.kt │ ├── DependencyHandlerExtensions.kt │ ├── ProjectExtensions.kt │ └── SigningExtensions.kt ├── common ├── component │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── component │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── component │ │ │ ├── LottieView.kt │ │ │ ├── ProgressIndicator.kt │ │ │ ├── SegmentedControl.kt │ │ │ ├── Spacer.kt │ │ │ └── widget │ │ │ ├── EmptyView.kt │ │ │ ├── ErrorView.kt │ │ │ ├── JRDivider.kt │ │ │ ├── JRToolbar.kt │ │ │ ├── LoadingView.kt │ │ │ ├── LottieEmptyView.kt │ │ │ ├── LottieErrorView.kt │ │ │ └── ThemeSwitch.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── component │ │ └── ExampleUnitTest.kt ├── provider │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── provider │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── provider │ │ │ ├── LanguageProvider.kt │ │ │ ├── NavigationProvider.kt │ │ │ ├── ResourceProvider.kt │ │ │ └── ThemeProvider.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── provider │ │ └── ExampleUnitTest.kt └── theme │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── developersancho │ │ └── theme │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── empty.json │ │ └── error.json │ ├── java │ │ └── com │ │ │ └── developersancho │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Dimen.kt │ │ │ ├── Font.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xxxhdpi │ │ └── ic_app_logo.jpeg │ │ ├── drawable │ │ ├── bg_splash.xml │ │ ├── bg_splash_12.xml │ │ ├── ic_close_circle.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_moon.xml │ │ ├── ic_profile.png │ │ ├── ic_settings.xml │ │ ├── ic_sun.xml │ │ ├── intro_1.jpeg │ │ ├── intro_2.jpeg │ │ ├── intro_3.png │ │ ├── selector_bg_dark_light.xml │ │ └── selector_dark_light.xml │ │ ├── font │ │ ├── raleway_bold.ttf │ │ ├── raleway_medium.ttf │ │ ├── raleway_regular.ttf │ │ └── raleway_semi_bold.ttf │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-tr-rTR │ │ └── strings.xml │ │ ├── values-v31 │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── developersancho │ └── theme │ └── ExampleUnitTest.kt ├── data ├── local │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── local │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── local │ │ │ ├── converter │ │ │ └── StringListConverter.kt │ │ │ ├── dao │ │ │ ├── CharacterFavoriteDao.kt │ │ │ ├── EpisodeFavoriteDao.kt │ │ │ └── LocationFavoriteDao.kt │ │ │ ├── db │ │ │ └── RortyDatabase.kt │ │ │ └── di │ │ │ └── LocalModule.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── local │ │ ├── dao │ │ ├── CharacterFavoriteDaoTest.kt │ │ ├── EpisodeFavoriteDaoTest.kt │ │ └── LocationFavoriteDaoTest.kt │ │ ├── db │ │ └── RortyDatabaseTest.kt │ │ ├── di │ │ └── LocalModuleTest.kt │ │ └── mockdata │ │ └── LocalMockData.kt ├── model │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── model │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── model │ │ │ ├── dto │ │ │ ├── KeyValueModel.kt │ │ │ ├── character │ │ │ │ ├── CharacterDto.kt │ │ │ │ ├── CharacterDtoExtension.kt │ │ │ │ └── CharacterLocationDto.kt │ │ │ ├── episode │ │ │ │ ├── EpisodeDto.kt │ │ │ │ └── EpisodeDtoExtension.kt │ │ │ ├── language │ │ │ │ └── LanguageDto.kt │ │ │ └── location │ │ │ │ ├── LocationDto.kt │ │ │ │ └── LocationDtoExtension.kt │ │ │ ├── local │ │ │ ├── character │ │ │ │ ├── CharacterEntity.kt │ │ │ │ └── CharacterLocationEntity.kt │ │ │ ├── episode │ │ │ │ └── EpisodeEntity.kt │ │ │ └── location │ │ │ │ └── LocationEntity.kt │ │ │ └── remote │ │ │ ├── base │ │ │ ├── PageInfo.kt │ │ │ └── Status.kt │ │ │ ├── character │ │ │ ├── CharacterInfo.kt │ │ │ ├── CharacterResponse.kt │ │ │ ├── Location.kt │ │ │ └── Origin.kt │ │ │ ├── episode │ │ │ ├── EpisodeInfo.kt │ │ │ └── EpisodeResponse.kt │ │ │ └── location │ │ │ ├── LocationInfo.kt │ │ │ └── LocationResponse.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── model │ │ ├── dto │ │ ├── CharacterDtoTest.kt │ │ ├── EpisodeDtoTest.kt │ │ └── LocationDtoTest.kt │ │ ├── local │ │ ├── character │ │ │ └── CharacterEntityTest.kt │ │ ├── episode │ │ │ └── EpisodeEntityTest.kt │ │ └── location │ │ │ └── LocationEntityTest.kt │ │ └── remote │ │ ├── base │ │ ├── PageInfoTest.kt │ │ └── StatusTest.kt │ │ ├── character │ │ ├── CharacterInfoTest.kt │ │ ├── CharacterResponseTest.kt │ │ ├── LocationTest.kt │ │ └── OriginTest.kt │ │ ├── episode │ │ ├── EpisodeInfoTest.kt │ │ └── EpisodeResponseTest.kt │ │ └── location │ │ ├── LocationInfoTest.kt │ │ └── LocationResponseTest.kt ├── remote │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── remote │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── developersancho │ │ │ │ └── remote │ │ │ │ ├── di │ │ │ │ └── RemoteModule.kt │ │ │ │ └── service │ │ │ │ ├── CharacterService.kt │ │ │ │ ├── EpisodeService.kt │ │ │ │ └── LocationService.kt │ │ └── resources │ │ │ └── mock-responses │ │ │ ├── get-character.json │ │ │ ├── get-characters-by-filter.json │ │ │ ├── get-characters.json │ │ │ ├── get-episode.json │ │ │ ├── get-episodes-by-filter.json │ │ │ ├── get-episodes.json │ │ │ ├── get-location.json │ │ │ ├── get-locations-by-filter.json │ │ │ └── get-locations.json │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── remote │ │ ├── di │ │ └── RemoteModuleTest.kt │ │ └── service │ │ ├── character │ │ └── CharacterServiceTest.kt │ │ ├── episode │ │ └── EpisodeServiceTest.kt │ │ ├── location │ │ └── LocationServiceTest.kt │ │ └── mock │ │ └── MockResponses.kt └── repository │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── developersancho │ │ └── repository │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── developersancho │ │ └── repository │ │ ├── character │ │ └── CharacterRepository.kt │ │ ├── di │ │ └── RepositoryModule.kt │ │ ├── episode │ │ └── EpisodeRepository.kt │ │ ├── langauge │ │ └── LanguageRepository.kt │ │ ├── location │ │ └── LocationRepository.kt │ │ └── welcome │ │ └── WelcomeRepository.kt │ └── test │ └── java │ └── com │ └── developersancho │ └── repository │ ├── character │ └── CharacterRepositoryTest.kt │ ├── di │ └── RepositoryModuleTest.kt │ ├── episode │ └── EpisodeRepositoryTest.kt │ ├── location │ └── LocationRepositoryTest.kt │ └── mockdata │ └── RepositoryMockData.kt ├── domain ├── .gitignore ├── build.gradle.kts └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── developersancho │ │ └── domain │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── developersancho │ │ └── domain │ │ ├── di │ │ ├── CharacterDomainModule.kt │ │ ├── DomainModule.kt │ │ ├── EpisodeDomainModule.kt │ │ ├── LanguageModule.kt │ │ ├── LocationDomainModule.kt │ │ └── WelcomeModule.kt │ │ └── usecase │ │ ├── character │ │ ├── CharacterPagingSource.kt │ │ ├── GetCharacterDetail.kt │ │ ├── GetCharacters.kt │ │ └── favorite │ │ │ ├── AddCharacterFavorite.kt │ │ │ ├── DeleteCharacterFavorite.kt │ │ │ ├── GetCharacterFavorites.kt │ │ │ └── UpdateCharacterFavorite.kt │ │ ├── episode │ │ ├── EpisodePagingSource.kt │ │ ├── GetEpisodeDetail.kt │ │ ├── GetEpisodes.kt │ │ └── favorite │ │ │ ├── AddEpisodeFavorite.kt │ │ │ ├── DeleteEpisodeFavorite.kt │ │ │ ├── GetEpisodeFavorites.kt │ │ │ └── UpdateEpisodeFavorite.kt │ │ ├── language │ │ ├── GetLanguage.kt │ │ └── SaveLanguage.kt │ │ ├── location │ │ ├── GetLocationDetail.kt │ │ ├── GetLocations.kt │ │ ├── LocationPagingSource.kt │ │ └── favorite │ │ │ ├── AddLocationFavorite.kt │ │ │ ├── DeleteLocationFavorite.kt │ │ │ ├── GetLocationFavorites.kt │ │ │ └── UpdateLocationFavorite.kt │ │ └── welcome │ │ ├── ReadOnBoarding.kt │ │ └── SaveOnBoarding.kt │ └── test │ └── java │ └── com │ └── developersancho │ └── domain │ ├── di │ ├── CharacterDomainModuleTest.kt │ ├── EpisodeDomainModuleTest.kt │ ├── LanguageModuleTest.kt │ ├── LocationDomainModuleTest.kt │ └── WelcomeModuleTest.kt │ ├── mockdata │ └── MockData.kt │ └── usecase │ ├── character │ ├── CharacterPagingSourceTest.kt │ ├── GetCharacterDetailTest.kt │ ├── GetCharactersTest.kt │ └── favorite │ │ ├── AddCharacterFavoriteTest.kt │ │ ├── DeleteCharacterFavoriteTest.kt │ │ ├── GetCharacterFavoritesTest.kt │ │ └── UpdateCharacterFavoriteTest.kt │ ├── episode │ ├── EpisodePagingSourceTest.kt │ ├── GetEpisodeDetailTest.kt │ ├── GetEpisodesTest.kt │ └── favorite │ │ ├── AddEpisodeFavoriteTest.kt │ │ ├── DeleteEpisodeFavoriteTest.kt │ │ ├── GetEpisodeFavoritesTest.kt │ │ └── UpdateEpisodeFavoriteTest.kt │ ├── language │ ├── GetLanguageTest.kt │ └── SaveLanguageTest.kt │ ├── location │ ├── GetLocationDetailTest.kt │ ├── GetLocationsTest.kt │ ├── LocationPagingSourceTest.kt │ └── favorite │ │ ├── AddLocationFavoriteTest.kt │ │ ├── DeleteLocationFavoriteTest.kt │ │ ├── GetLocationFavoritesTest.kt │ │ └── UpdateLocationFavoriteTest.kt │ └── welcome │ ├── ReadOnBoardingTest.kt │ └── SaveOnBoardingTest.kt ├── features ├── characters │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── characters │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── characters │ │ │ ├── detail │ │ │ ├── CharacterDetailContract.kt │ │ │ ├── CharacterDetailScreen.kt │ │ │ ├── CharacterDetailViewModel.kt │ │ │ └── view │ │ │ │ ├── CharacterDetailContent.kt │ │ │ │ ├── CharacterDetailHeaderView.kt │ │ │ │ ├── CharacterDetailInfoView.kt │ │ │ │ ├── CharacterDetailLocationView.kt │ │ │ │ └── CharacterEpisodesView.kt │ │ │ └── list │ │ │ ├── CharactersContract.kt │ │ │ ├── CharactersScreen.kt │ │ │ ├── CharactersViewModel.kt │ │ │ └── view │ │ │ ├── CharacterContent.kt │ │ │ ├── CharacterFavoriteContent.kt │ │ │ ├── CharacterFavoriteRow.kt │ │ │ ├── CharacterRow.kt │ │ │ ├── CharacterStatusDotView.kt │ │ │ ├── CharacterStatusView.kt │ │ │ ├── FavoriteBottomSheetContent.kt │ │ │ └── FavoriteButton.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── characters │ │ ├── detail │ │ ├── CharacterDetailContractTest.kt │ │ └── CharacterDetailViewModelTest.kt │ │ └── list │ │ ├── CharactersContractTest.kt │ │ └── CharactersViewModelTest.kt ├── episodes │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── episodes │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── episodes │ │ │ ├── detail │ │ │ ├── EpisodeDetailContract.kt │ │ │ ├── EpisodeDetailScreen.kt │ │ │ ├── EpisodeDetailViewModel.kt │ │ │ └── view │ │ │ │ └── EpisodeDetailContent.kt │ │ │ └── list │ │ │ ├── EpisodesContract.kt │ │ │ ├── EpisodesScreen.kt │ │ │ ├── EpisodesViewModel.kt │ │ │ └── view │ │ │ ├── EpisodeBottomSheetContent.kt │ │ │ ├── EpisodeContent.kt │ │ │ ├── EpisodeFavoriteContent.kt │ │ │ ├── EpisodeFavoriteRow.kt │ │ │ └── EpisodeRow.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── episodes │ │ ├── detail │ │ ├── EpisodeDetailContractTest.kt │ │ └── EpisodeDetailViewModelTest.kt │ │ └── list │ │ ├── EpisodesContractTest.kt │ │ └── EpisodesViewModelTest.kt ├── home │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── home │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── home │ │ │ ├── BottomBarHomeItem.kt │ │ │ └── HomeScreen.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── home │ │ └── ExampleUnitTest.kt ├── locations │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── locations │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── locations │ │ │ ├── detail │ │ │ ├── LocationDetailContract.kt │ │ │ ├── LocationDetailScreen.kt │ │ │ ├── LocationDetailViewModel.kt │ │ │ └── view │ │ │ │ └── LocationDetailContent.kt │ │ │ └── list │ │ │ ├── LocationsContract.kt │ │ │ ├── LocationsScreen.kt │ │ │ ├── LocationsViewModel.kt │ │ │ └── view │ │ │ ├── LocationBottomSheetContent.kt │ │ │ ├── LocationContent.kt │ │ │ ├── LocationFavoriteContent.kt │ │ │ ├── LocationFavoriteRow.kt │ │ │ └── LocationRow.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── locations │ │ ├── detail │ │ ├── LocationDetailContractTest.kt │ │ └── LocationDetailViewModelTest.kt │ │ └── list │ │ ├── LocationsContractTest.kt │ │ └── LocationsViewModelTest.kt ├── settings │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── settings │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── settings │ │ │ ├── SettingsScreen.kt │ │ │ ├── SettingsViewModel.kt │ │ │ ├── about │ │ │ └── AboutScreen.kt │ │ │ ├── language │ │ │ ├── LanguageScreen.kt │ │ │ └── LanguageViewModel.kt │ │ │ ├── termandprivacy │ │ │ ├── Docs.kt │ │ │ └── TermsAndPrivacyScreen.kt │ │ │ └── view │ │ │ └── SettingsContent.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── settings │ │ └── ExampleUnitTest.kt ├── splash │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── splash │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── splash │ │ │ ├── StartActivity.kt │ │ │ └── StartViewModel.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── splash │ │ └── ExampleUnitTest.kt └── welcome │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── developersancho │ │ └── welcome │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── developersancho │ │ └── welcome │ │ ├── WelcomeActivity.kt │ │ ├── environment │ │ └── EnvironmentScreen.kt │ │ ├── navgraph │ │ ├── WelcomeNavGraph.kt │ │ └── WelcomeScreen.kt │ │ └── onboarding │ │ ├── OnBoardingPage.kt │ │ ├── OnBoardingScreen.kt │ │ └── OnBoardingViewModel.kt │ └── test │ └── java │ └── com │ └── developersancho │ └── welcome │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── libraries ├── framework │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── framework │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── framework │ │ │ ├── base │ │ │ ├── app │ │ │ │ ├── AppInitializer.kt │ │ │ │ ├── AppInitializerImpl.kt │ │ │ │ ├── AppLifecycleCallback.kt │ │ │ │ ├── CoreApplication.kt │ │ │ │ ├── FirebaseCrashlyticsReportTree.kt │ │ │ │ ├── NetworkConfig.kt │ │ │ │ └── TimberInitializer.kt │ │ │ ├── mvi │ │ │ │ ├── BaseViewState.kt │ │ │ │ └── MviViewModel.kt │ │ │ └── mvvm │ │ │ │ └── MvvmViewModel.kt │ │ │ ├── extension │ │ │ ├── AnyExtension.kt │ │ │ ├── CoroutineExtension.kt │ │ │ ├── DeviceExtension.kt │ │ │ ├── FlowExtension.kt │ │ │ ├── IntentExtension.kt │ │ │ ├── InternetExtension.kt │ │ │ ├── MoshiExtension.kt │ │ │ ├── StringExtension.kt │ │ │ ├── ToastExtension.kt │ │ │ ├── VariableExtension.kt │ │ │ └── ViewExtension.kt │ │ │ ├── network │ │ │ ├── ApiCallExtension.kt │ │ │ ├── DataState.kt │ │ │ ├── HandleError.kt │ │ │ ├── HttpStatusCode.kt │ │ │ ├── NetworkHelper.kt │ │ │ ├── environment │ │ │ │ ├── Environment.kt │ │ │ │ └── EnvironmentInterceptor.kt │ │ │ ├── interceptor │ │ │ │ └── HttpRequestInterceptor.kt │ │ │ ├── moshi │ │ │ │ └── EnumValueJsonAdapter.kt │ │ │ └── sslpinning │ │ │ │ ├── SSLPinning.kt │ │ │ │ └── SSLPinningImpl.kt │ │ │ ├── platform │ │ │ └── MobileService.kt │ │ │ ├── pref │ │ │ ├── CacheManager.kt │ │ │ ├── CacheManagerExtension.kt │ │ │ └── CacheStore.kt │ │ │ ├── room │ │ │ ├── converter │ │ │ │ └── StringConverter.kt │ │ │ └── dao │ │ │ │ └── BaseDao.kt │ │ │ └── usecase │ │ │ ├── DataStateUseCase.kt │ │ │ ├── FlowPagingUseCase.kt │ │ │ ├── LocalUseCase.kt │ │ │ └── ReturnUseCase.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── framework │ │ └── ExampleUnitTest.kt ├── jetframework │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── jetframework │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── developersancho │ │ │ └── jetframework │ │ │ ├── ClickableSingle.kt │ │ │ ├── FirebaseAnalytic.kt │ │ │ ├── LanguageHelper.kt │ │ │ ├── LinkText.kt │ │ │ ├── RememberFlow.kt │ │ │ ├── SystemUiController.kt │ │ │ ├── TimedVisibility.kt │ │ │ ├── WindowInfo.kt │ │ │ └── htmltext │ │ │ ├── CharacterStyle.kt │ │ │ ├── HtmlText.kt │ │ │ └── MetricAffectingSpan.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── developersancho │ │ └── jetframework │ │ └── ExampleUnitTest.kt └── testutils │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── developersancho │ │ └── testutils │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── developersancho │ │ └── testutils │ │ ├── BaseModelTest.kt │ │ ├── BaseRobot.kt │ │ ├── BaseServiceTest.kt │ │ ├── MockkUnitTest.kt │ │ ├── TestCoroutineRule.kt │ │ └── TestRobolectric.kt │ └── test │ └── java │ └── com │ └── developersancho │ └── testutils │ └── ExampleUnitTest.kt ├── settings.gradle.kts └── signing ├── debug.keystore ├── jetrorty-release.jks └── release.signing.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /.idea/compiler.xml 17 | /.idea/.name 18 | /.idea/gradle.xml 19 | /.idea/misc.xml 20 | /.idea/vcs.xml 21 | /.idea/kotlinScripting.xml 22 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/src/androidTest/java/com/developersancho/jetrorty/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.jetrorty", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developersancho/jetrorty/app/JetNetworkConfig.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty.app 2 | 3 | import com.developersancho.framework.base.app.NetworkConfig 4 | import com.developersancho.jetrorty.BuildConfig 5 | 6 | class JetNetworkConfig : NetworkConfig() { 7 | override fun baseUrl(): String { 8 | return BuildConfig.BASE_URL 9 | } 10 | 11 | override fun timeOut(): Long { 12 | return 30L 13 | } 14 | 15 | override fun isDev(): Boolean { 16 | return BuildConfig.DEBUG 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developersancho/jetrorty/app/JetRortyApp.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty.app 2 | 3 | import com.developersancho.framework.base.app.AppInitializer 4 | import com.developersancho.framework.base.app.CoreApplication 5 | import dagger.hilt.android.HiltAndroidApp 6 | import javax.inject.Inject 7 | 8 | @HiltAndroidApp 9 | class JetRortyApp : CoreApplication() { 10 | 11 | @Inject 12 | lateinit var initializer: AppInitializer 13 | 14 | override fun onCreate() { 15 | super.onCreate() 16 | initializer.init(this) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developersancho/jetrorty/navigation/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty.navigation 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.compose.ui.platform.LocalContext 6 | import androidx.fragment.app.FragmentActivity 7 | import com.developersancho.framework.extension.toast 8 | import com.developersancho.provider.LanguageProvider 9 | import com.developersancho.theme.R 10 | import dagger.hilt.android.AndroidEntryPoint 11 | import javax.inject.Inject 12 | 13 | @AndroidEntryPoint 14 | class MainActivity : FragmentActivity() { 15 | @Inject 16 | lateinit var languageProvider: LanguageProvider 17 | 18 | private var backPressed = 0L 19 | 20 | private val finish: () -> Unit = { 21 | if (backPressed + 3000 > System.currentTimeMillis()) { 22 | finishAndRemoveTask() 23 | } else { 24 | toast(getString(R.string.app_exit_label)) 25 | } 26 | backPressed = System.currentTimeMillis() 27 | } 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContent { 32 | languageProvider.setLocale(languageProvider.getLanguageCode(), LocalContext.current) 33 | MainRoot(finish = finish) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developersancho/jetrorty/navigation/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty.navigation 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.developersancho.provider.ThemeProvider 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import javax.inject.Inject 7 | 8 | @HiltViewModel 9 | class MainViewModel @Inject constructor(private val themeProvider: ThemeProvider) : ViewModel() { 10 | 11 | fun themeProvider() = themeProvider 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developersancho/jetrorty/navigation/RootNavGraph.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty.navigation 2 | 3 | import com.developersancho.characters.detail.CharactersNavGraph 4 | import com.developersancho.episodes.detail.EpisodesNavGraph 5 | import com.developersancho.home.HomeNavGraph 6 | import com.developersancho.locations.detail.LocationsNavGraph 7 | import com.developersancho.settings.SettingsNavGraph 8 | import com.ramcosta.composedestinations.spec.DestinationSpec 9 | import com.ramcosta.composedestinations.spec.NavGraphSpec 10 | 11 | object RootNavGraph : NavGraphSpec { 12 | override val route = "root" 13 | 14 | override val destinationsByRoute = emptyMap>() 15 | 16 | override val startRoute = HomeNavGraph 17 | 18 | override val nestedNavGraphs = listOf( 19 | HomeNavGraph, 20 | CharactersNavGraph, 21 | EpisodesNavGraph, 22 | LocationsNavGraph, 23 | SettingsNavGraph 24 | ) 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developersancho/jetrorty/provider/AppLanguageProvider.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty.provider 2 | 3 | import android.content.Context 4 | import com.developersancho.framework.pref.CacheManager 5 | import com.developersancho.provider.LanguageProvider 6 | import java.util.* 7 | 8 | class AppLanguageProvider constructor(private val cacheManager: CacheManager) : LanguageProvider { 9 | 10 | companion object { 11 | private const val LANG_CODE = "lang_code" 12 | private const val DefaultLanguage = "en" 13 | } 14 | 15 | override fun saveLanguageCode(languageCode: String) { 16 | cacheManager.write(key = LANG_CODE, value = languageCode) 17 | } 18 | 19 | override fun getLanguageCode(): String { 20 | return cacheManager.read(key = LANG_CODE, DefaultLanguage) 21 | } 22 | 23 | override fun setLocale(language: String, context: Context) { 24 | updateResources(language, context) 25 | } 26 | 27 | private fun updateResources(language: String, context: Context) { 28 | val locale = Locale(language) 29 | val resources = context.resources 30 | val configuration = resources.configuration 31 | configuration.setLocale(locale) 32 | resources.updateConfiguration(configuration, resources.displayMetrics) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/developersancho/jetrorty/provider/AppResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty.provider 2 | 3 | import android.content.Context 4 | import com.developersancho.provider.ResourceProvider 5 | 6 | class AppResourceProvider(private val context: Context) : ResourceProvider { 7 | override fun getString(id: Int): String { 8 | return context.getString(id) 9 | } 10 | } -------------------------------------------------------------------------------- /app/src/test/java/com/developersancho/jetrorty/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetrorty 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /art/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/architecture.png -------------------------------------------------------------------------------- /art/architecturecircles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/architecturecircles.png -------------------------------------------------------------------------------- /art/clean_arch.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/clean_arch.jpeg -------------------------------------------------------------------------------- /art/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/project.png -------------------------------------------------------------------------------- /art/screenshots/about-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/about-dark.png -------------------------------------------------------------------------------- /art/screenshots/about.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/about.png -------------------------------------------------------------------------------- /art/screenshots/character-detail-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/character-detail-dark.png -------------------------------------------------------------------------------- /art/screenshots/character-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/character-detail.png -------------------------------------------------------------------------------- /art/screenshots/characters-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/characters-dark.png -------------------------------------------------------------------------------- /art/screenshots/characters-favs-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/characters-favs-dark.png -------------------------------------------------------------------------------- /art/screenshots/characters-favs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/characters-favs.png -------------------------------------------------------------------------------- /art/screenshots/characters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/characters.png -------------------------------------------------------------------------------- /art/screenshots/episode-detail-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/episode-detail-dark.png -------------------------------------------------------------------------------- /art/screenshots/episode-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/episode-detail.png -------------------------------------------------------------------------------- /art/screenshots/episodes-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/episodes-dark.png -------------------------------------------------------------------------------- /art/screenshots/episodes-favs-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/episodes-favs-dark.png -------------------------------------------------------------------------------- /art/screenshots/episodes-favs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/episodes-favs.png -------------------------------------------------------------------------------- /art/screenshots/episodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/episodes.png -------------------------------------------------------------------------------- /art/screenshots/intro-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/intro-dark.png -------------------------------------------------------------------------------- /art/screenshots/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/intro.png -------------------------------------------------------------------------------- /art/screenshots/language-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/language-dark.png -------------------------------------------------------------------------------- /art/screenshots/language.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/language.png -------------------------------------------------------------------------------- /art/screenshots/location-detail-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/location-detail-dark.png -------------------------------------------------------------------------------- /art/screenshots/location-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/location-detail.png -------------------------------------------------------------------------------- /art/screenshots/locations-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/locations-dark.png -------------------------------------------------------------------------------- /art/screenshots/locations-favs-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/locations-favs-dark.png -------------------------------------------------------------------------------- /art/screenshots/locations-favs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/locations-favs.png -------------------------------------------------------------------------------- /art/screenshots/locations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/locations.png -------------------------------------------------------------------------------- /art/screenshots/settings-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/settings-dark.png -------------------------------------------------------------------------------- /art/screenshots/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/settings.png -------------------------------------------------------------------------------- /art/screenshots/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/art/screenshots/splash.png -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | buildscript { 2 | dependencies { 3 | classpath("com.android.tools.build:gradle:7.1.3") 4 | classpath("com.huawei.agconnect:agcp:1.6.5.300") 5 | } 6 | } 7 | 8 | plugins { 9 | id("com.google.devtools.ksp") version "1.6.10-1.0.4" apply false 10 | } 11 | 12 | apply() 13 | 14 | apply(plugin = "codeanalyzetools.jacoco-multi-report") 15 | 16 | tasks.register("clean", Delete::class) { 17 | delete(rootProject.buildDir) 18 | } -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /buildSrc/src/main/java/codeanalyzetools/detekt-check.gradle.kts: -------------------------------------------------------------------------------- 1 | package codeanalyzetools 2 | 3 | import io.gitlab.arturbosch.detekt.Detekt 4 | import io.gitlab.arturbosch.detekt.DetektPlugin 5 | import io.gitlab.arturbosch.detekt.extensions.DetektExtension 6 | 7 | apply() 8 | 9 | plugins { 10 | id("io.gitlab.arturbosch.detekt") 11 | } 12 | 13 | configure { 14 | autoCorrect = true 15 | toolVersion = "1.20.0" 16 | parallel = false 17 | source = files( 18 | "src/main/kotlin", 19 | "src/main/java" 20 | ) 21 | config = files("${project.rootDir}/buildSrc/detekt.yml") 22 | } 23 | 24 | dependencies { 25 | detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.20.0") 26 | } 27 | 28 | tasks.withType().configureEach { 29 | reports { 30 | html.required.set(true) 31 | html.outputLocation.set(file("${project.buildDir}/build/reports/detekt/detekt-report.html")) 32 | xml.required.set(true) 33 | xml.outputLocation.set(file("${project.buildDir}/build/reports/detekt/detekt-report.xml")) 34 | } 35 | } 36 | 37 | tasks.withType().configureEach { 38 | include("**/*.kt", "**/*.kts") 39 | exclude("**/build/**", ".*/resources/.*", ".*test.*,.*/resources/.*,.*/tmp/.*") 40 | 41 | jvmTarget = JavaVersion.VERSION_11.toString() 42 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/codeanalyzetools/ktlint-check.gradle.kts: -------------------------------------------------------------------------------- 1 | package codeanalyzetools 2 | 3 | import org.jlleitschuh.gradle.ktlint.reporter.ReporterType 4 | 5 | plugins { 6 | id("org.jlleitschuh.gradle.ktlint") 7 | } 8 | 9 | ktlint { 10 | version.set("0.42.1") 11 | debug.set(true) 12 | verbose.set(true) 13 | android.set(false) 14 | outputToConsole.set(true) 15 | outputColorName.set("RED") 16 | enableExperimentalRules.set(true) 17 | ignoreFailures.set(false) 18 | disabledRules.set( 19 | setOf( 20 | "import-ordering", 21 | "no-wildcard-imports", 22 | "experimental:annotation", 23 | "experimental:argument-list-wrapping", 24 | "experimental:trailingcomma:trailing-comma", 25 | "final-newline" 26 | ) 27 | ) 28 | reporters { 29 | reporter(ReporterType.HTML) 30 | reporter(ReporterType.CHECKSTYLE) 31 | } 32 | filter { 33 | exclude("**/generated/**") 34 | exclude("**/build/**") 35 | include("**/kotlin/**") 36 | } 37 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/codeanalyzetools/quality.gradle.kts: -------------------------------------------------------------------------------- 1 | package codeanalyzetools 2 | 3 | plugins { 4 | id("codeanalyzetools.detekt-check") 5 | id("codeanalyzetools.ktlint-check") 6 | } 7 | 8 | tasks.getByName("check") { 9 | setDependsOn( 10 | listOf( 11 | tasks.getByName("ktlintFormat"), 12 | tasks.getByName("ktlintCheck"), 13 | tasks.getByName("detekt") 14 | ) 15 | ) 16 | } 17 | 18 | val codeAnalyze by tasks.registering { 19 | setDependsOn( 20 | listOf( 21 | tasks.getByName("ktlintFormat"), 22 | tasks.getByName("ktlintCheck"), 23 | tasks.getByName("detekt") 24 | ) 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /buildSrc/src/main/java/codeanalyzetools/version-check.gradle.kts: -------------------------------------------------------------------------------- 1 | package codeanalyzetools 2 | 3 | import com.github.benmanes.gradle.versions.VersionsPlugin 4 | import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask 5 | 6 | apply() 7 | 8 | fun isNonStable(version: String): Boolean { 9 | val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { version.toUpperCase().contains(it) } 10 | val regex = "^[0-9,.v-]+(-r)?$".toRegex() 11 | val isStable = stableKeyword || regex.matches(version) 12 | return isStable.not() 13 | } 14 | 15 | tasks.named("dependencyUpdates").configure { 16 | rejectVersionIf { 17 | isNonStable(candidate.version) 18 | } 19 | outputFormatter = "html" 20 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/codequality/DependencyUpdatePlugin.kt: -------------------------------------------------------------------------------- 1 | package codequality 2 | 3 | import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import java.util.* 7 | 8 | class DependencyUpdatePlugin : Plugin { 9 | override fun apply(project: Project) = with(project) { 10 | plugins.apply("com.github.ben-manes.versions") 11 | tasks.named("dependencyUpdates", DependencyUpdatesTask::class.java).configure { 12 | rejectVersionIf { 13 | isNonStable(candidate.version) 14 | } 15 | outputFormatter = "html" 16 | doLast { 17 | exec { 18 | commandLine("open", "build/dependencyUpdates/report.html") 19 | } 20 | } 21 | } 22 | } 23 | 24 | private fun isNonStable(version: String): Boolean { 25 | val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { 26 | version.toUpperCase(Locale.getDefault()) 27 | .contains(it) 28 | } 29 | val regex = "^[0-9,.v-]+(-r)?$".toRegex() 30 | val isStable = stableKeyword || regex.matches(version) 31 | return isStable.not() 32 | } 33 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/commons/CommonExtensions.kt: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import com.android.build.api.dsl.CommonExtension 4 | import org.gradle.api.artifacts.dsl.DependencyHandler 5 | 6 | /** 7 | * Adds the base Compose configurations on Gradle. 8 | */ 9 | fun CommonExtension<*, *, *, *>.addComposeConfig() { 10 | buildFeatures { 11 | compose = true 12 | } 13 | 14 | composeOptions { 15 | kotlinCompilerExtensionVersion = Versions.Compose 16 | } 17 | 18 | packagingOptions { 19 | resources.excludes.apply { 20 | add("META-INF/AL2.0") 21 | add("META-INF/LGPL2.1") 22 | } 23 | } 24 | } 25 | 26 | /** 27 | * Adds the base default app configurations on Gradle. 28 | */ 29 | fun CommonExtension<*, *, *, *>.addDefaultConfig() { 30 | defaultConfig { 31 | compileSdk = Configs.CompileSdk 32 | minSdk = Configs.MinSdk 33 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 34 | } 35 | 36 | testOptions { 37 | unitTests.isReturnDefaultValues = true 38 | } 39 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/commons/android-compose.gradle.kts: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import extensions.addComposeDependencies 4 | 5 | plugins { 6 | id("com.android.library") 7 | id("org.jetbrains.kotlin.android") 8 | } 9 | 10 | android { 11 | addComposeConfig() 12 | } 13 | 14 | dependencies { 15 | // Compose 16 | addComposeDependencies() 17 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/commons/dagger-hilt.gradle.kts: -------------------------------------------------------------------------------- 1 | package commons 2 | 3 | import extensions.implementation 4 | import extensions.kapt 5 | import org.gradle.kotlin.dsl.dependencies 6 | 7 | plugins { 8 | id("com.android.library") 9 | id("org.jetbrains.kotlin.android") 10 | id("org.jetbrains.kotlin.kapt") 11 | id("dagger.hilt.android.plugin") 12 | } 13 | 14 | dependencies { 15 | // Dagger Hilt 16 | implementation(DaggerHiltLib.Android) 17 | kapt(DaggerHiltLib.Compiler) 18 | } -------------------------------------------------------------------------------- /buildSrc/src/main/java/extensions/BuildTypeExtensions.kt: -------------------------------------------------------------------------------- 1 | package extensions 2 | 3 | //fun BuildType.buildConfigStringField(name: String, value: String) { 4 | // this.buildConfigField("String", name, "\"$value\"") 5 | //} -------------------------------------------------------------------------------- /buildSrc/src/main/java/extensions/ProjectExtensions.kt: -------------------------------------------------------------------------------- 1 | package extensions 2 | 3 | import org.gradle.api.Project 4 | import java.io.File 5 | 6 | fun Project.propOrDef(propertyName: String, defaultValue: Any): Any { 7 | return project.properties[propertyName] ?: (System.getenv(propertyName) 8 | ?: defaultValue) 9 | } 10 | 11 | fun Project.getFile(vararg fileNames: String): File? { 12 | for (fileName in fileNames) { 13 | val file = rootProject.file(fileName) 14 | if (file.exists()) { 15 | return file 16 | } 17 | } 18 | return null 19 | } 20 | 21 | fun Project.propertyOrEmpty(name: String): String { 22 | return findProperty(name) as String? ?: "" 23 | } -------------------------------------------------------------------------------- /common/component/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/component/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.JETFRAMEWORK 2 | import extensions.THEME 3 | 4 | plugins { 5 | id("commons.android-library") 6 | id("commons.android-compose") 7 | } 8 | 9 | dependencies { 10 | THEME 11 | JETFRAMEWORK 12 | } -------------------------------------------------------------------------------- /common/component/src/androidTest/java/com/developersancho/component/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.component 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.component.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /common/component/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /common/component/src/main/java/com/developersancho/component/LottieView.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.component 2 | 3 | import androidx.compose.foundation.layout.defaultMinSize 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.unit.dp 8 | import com.airbnb.lottie.compose.LottieAnimation 9 | import com.airbnb.lottie.compose.LottieCompositionSpec 10 | import com.airbnb.lottie.compose.rememberLottieComposition 11 | 12 | @Composable 13 | fun LottieView( 14 | file: String, 15 | modifier: Modifier = Modifier, 16 | iterations: Int = 10 17 | ) { 18 | val composition by rememberLottieComposition(LottieCompositionSpec.Asset(file)) 19 | LottieAnimation( 20 | composition, 21 | modifier = modifier.defaultMinSize(300.dp), 22 | iterations = iterations 23 | ) 24 | } -------------------------------------------------------------------------------- /common/component/src/main/java/com/developersancho/component/widget/JRDivider.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.component.widget 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material.Divider 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.compose.ui.unit.dp 14 | import com.developersancho.theme.JetRortyColors 15 | import com.developersancho.theme.JetRortyTheme 16 | import com.developersancho.theme.dividerColor 17 | 18 | @Composable 19 | fun JRDivider(modifier: Modifier = Modifier) { 20 | Divider( 21 | modifier = modifier 22 | .fillMaxWidth() 23 | .height(1.dp), 24 | color = JetRortyColors.dividerColor 25 | ) 26 | } 27 | 28 | @Preview("default", showBackground = true) 29 | @Preview("dark theme", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true) 30 | @Composable 31 | private fun DividerPreview() { 32 | JetRortyTheme { 33 | Box(Modifier.size(height = 10.dp, width = 100.dp)) { 34 | JRDivider(Modifier.align(Alignment.Center)) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /common/component/src/main/java/com/developersancho/component/widget/LoadingView.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.component.widget 2 | 3 | import android.content.res.Configuration 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Alignment 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.tooling.preview.Preview 10 | import com.developersancho.component.ProgressIndicator 11 | import com.developersancho.jetframework.Delayed 12 | import com.developersancho.theme.JetRortyTheme 13 | 14 | @Composable 15 | fun LoadingView(modifier: Modifier = Modifier, delayMillis: Long = 100L) { 16 | Delayed(delayMillis = delayMillis) { 17 | Box( 18 | contentAlignment = Alignment.Center, 19 | modifier = when (modifier == Modifier) { 20 | true -> Modifier.fillMaxSize() 21 | false -> modifier 22 | } 23 | ) { 24 | ProgressIndicator() 25 | } 26 | } 27 | } 28 | 29 | @Preview( 30 | showBackground = true, 31 | name = "Light Mode" 32 | ) 33 | @Preview( 34 | showBackground = true, 35 | uiMode = Configuration.UI_MODE_NIGHT_YES, 36 | name = "Dark Mode" 37 | ) 38 | @Composable 39 | fun LoadingViewPreview() { 40 | JetRortyTheme { 41 | LoadingView() 42 | } 43 | } -------------------------------------------------------------------------------- /common/component/src/main/java/com/developersancho/component/widget/ThemeSwitch.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.component.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.appcompat.content.res.AppCompatResources 6 | import androidx.appcompat.widget.SwitchCompat 7 | import com.developersancho.theme.R 8 | 9 | class ThemeSwitch @JvmOverloads constructor( 10 | context: Context, 11 | attrs: AttributeSet? = null 12 | ) : SwitchCompat(context, attrs) { 13 | 14 | init { 15 | thumbDrawable = AppCompatResources.getDrawable(context, R.drawable.selector_dark_light) 16 | trackDrawable = AppCompatResources.getDrawable(context, R.drawable.selector_bg_dark_light) 17 | } 18 | } -------------------------------------------------------------------------------- /common/component/src/test/java/com/developersancho/component/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.component 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /common/provider/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/provider/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("commons.android-library") 3 | id("commons.android-compose") 4 | } -------------------------------------------------------------------------------- /common/provider/src/androidTest/java/com/developersancho/provider/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.provider 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.provider.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /common/provider/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /common/provider/src/main/java/com/developersancho/provider/LanguageProvider.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.provider 2 | 3 | import android.content.Context 4 | 5 | interface LanguageProvider { 6 | fun saveLanguageCode(languageCode: String) 7 | fun getLanguageCode(): String 8 | fun setLocale(language: String, context: Context) 9 | } -------------------------------------------------------------------------------- /common/provider/src/main/java/com/developersancho/provider/NavigationProvider.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.provider 2 | 3 | interface NavigationProvider { 4 | fun openCharacterDetail(characterId: Int) 5 | fun openEpisodeDetail(episodeId: Int) 6 | fun openLocationDetail(locationId: Int) 7 | fun openTermAndPrivacy() 8 | fun openAppLanguage() 9 | fun openAbout() 10 | fun navigateUp() 11 | } -------------------------------------------------------------------------------- /common/provider/src/main/java/com/developersancho/provider/ResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.provider 2 | 3 | import androidx.annotation.StringRes 4 | 5 | interface ResourceProvider { 6 | fun getString(@StringRes id: Int): String 7 | } -------------------------------------------------------------------------------- /common/provider/src/main/java/com/developersancho/provider/ThemeProvider.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.provider 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.collectAsState 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface ThemeProvider { 9 | var theme: Theme 10 | fun observeTheme(): Flow 11 | 12 | enum class Theme { 13 | LIGHT, 14 | DARK, 15 | SYSTEM 16 | } 17 | 18 | fun isNightMode(): Boolean 19 | 20 | fun setNightMode(forceNight: Boolean) 21 | } 22 | 23 | @Composable 24 | fun ThemeProvider.shouldUseDarkMode(): Boolean { 25 | val themePreference = observeTheme().collectAsState(initial = ThemeProvider.Theme.SYSTEM) 26 | val mode = when (themePreference.value) { 27 | ThemeProvider.Theme.LIGHT -> false 28 | ThemeProvider.Theme.DARK -> true 29 | else -> isSystemInDarkTheme() 30 | } 31 | setNightMode(mode) 32 | return mode 33 | } -------------------------------------------------------------------------------- /common/provider/src/test/java/com/developersancho/provider/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.provider 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /common/theme/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /common/theme/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("commons.android-library") 3 | id("commons.android-compose") 4 | } 5 | 6 | dependencies { 7 | implementation(SupportLib.Splashscreen) 8 | } -------------------------------------------------------------------------------- /common/theme/src/androidTest/java/com/developersancho/theme/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.theme 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.theme.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /common/theme/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /common/theme/src/main/java/com/developersancho/theme/Dimen.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.theme 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.unit.dp 5 | 6 | @Composable 7 | internal fun singlePadding() = 8.dp 8 | 9 | @Composable 10 | internal fun iconSize() = 48.dp -------------------------------------------------------------------------------- /common/theme/src/main/java/com/developersancho/theme/Font.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.theme 2 | 3 | import androidx.compose.ui.text.font.Font 4 | import androidx.compose.ui.text.font.FontFamily 5 | import androidx.compose.ui.text.font.FontWeight 6 | 7 | val RalewayFonts = FontFamily( 8 | Font(R.font.raleway_regular, weight = FontWeight.Normal), 9 | Font(R.font.raleway_medium, weight = FontWeight.Medium), 10 | Font(R.font.raleway_semi_bold, weight = FontWeight.SemiBold), 11 | Font(R.font.raleway_bold, weight = FontWeight.Bold) 12 | ) -------------------------------------------------------------------------------- /common/theme/src/main/java/com/developersancho/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(8.dp), 10 | large = RoundedCornerShape(16.dp) 11 | ) -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable-xxxhdpi/ic_app_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/drawable-xxxhdpi/ic_app_logo.jpeg -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/bg_splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/bg_splash_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/ic_close_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/ic_moon.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/ic_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/drawable/ic_profile.png -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/intro_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/drawable/intro_1.jpeg -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/intro_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/drawable/intro_2.jpeg -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/intro_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/drawable/intro_3.png -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/selector_bg_dark_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /common/theme/src/main/res/drawable/selector_dark_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/theme/src/main/res/font/raleway_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/font/raleway_bold.ttf -------------------------------------------------------------------------------- /common/theme/src/main/res/font/raleway_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/font/raleway_medium.ttf -------------------------------------------------------------------------------- /common/theme/src/main/res/font/raleway_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/font/raleway_regular.ttf -------------------------------------------------------------------------------- /common/theme/src/main/res/font/raleway_semi_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/font/raleway_semi_bold.ttf -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/common/theme/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /common/theme/src/main/res/values-v31/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /common/theme/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /common/theme/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 11 | -------------------------------------------------------------------------------- /common/theme/src/test/java/com/developersancho/theme/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.theme 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /data/local/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/local/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-library") 5 | id("com.google.devtools.ksp") 6 | id("dagger.hilt.android.plugin") 7 | id("codeanalyzetools.jacoco-report") 8 | } 9 | 10 | dependencies { 11 | FRAMEWORK 12 | MODEL 13 | 14 | implementation(StorageLib.RoomKtx) 15 | ksp(StorageLib.RoomCompiler) 16 | 17 | // Dagger Hilt 18 | implementation(DaggerHiltLib.Android) 19 | kapt(DaggerHiltLib.Compiler) 20 | } -------------------------------------------------------------------------------- /data/local/src/androidTest/java/com/developersancho/local/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.local 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.local.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /data/local/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /data/local/src/main/java/com/developersancho/local/converter/StringListConverter.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.local.converter 2 | 3 | import androidx.room.TypeConverter 4 | import com.developersancho.framework.extension.fromJson 5 | import com.developersancho.framework.extension.toJson 6 | 7 | class StringListConverter { 8 | @TypeConverter 9 | fun toListOfStrings(stringValue: String): List? { 10 | return stringValue.fromJson() 11 | } 12 | 13 | @TypeConverter 14 | fun fromListOfStrings(listOfString: List?): String { 15 | return listOfString.toJson() 16 | } 17 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/developersancho/local/dao/CharacterFavoriteDao.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | import com.developersancho.framework.room.dao.BaseDao 6 | import com.developersancho.model.local.character.CharacterEntity 7 | 8 | @Dao 9 | interface CharacterFavoriteDao : BaseDao { 10 | @Query("SELECT * FROM ${CharacterEntity.TABLE_NAME}") 11 | suspend fun getFavoriteList(): List 12 | 13 | @Query("SELECT * FROM ${CharacterEntity.TABLE_NAME} WHERE id = :favoriteId") 14 | suspend fun getFavorite(favoriteId: Int): CharacterEntity? 15 | 16 | @Query("DELETE FROM ${CharacterEntity.TABLE_NAME}") 17 | suspend fun deleteFavoriteList() 18 | 19 | @Query("DELETE FROM ${CharacterEntity.TABLE_NAME} WHERE id = :favoriteId") 20 | suspend fun deleteFavoriteById(favoriteId: Int) 21 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/developersancho/local/dao/EpisodeFavoriteDao.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | import com.developersancho.framework.room.dao.BaseDao 6 | import com.developersancho.model.local.episode.EpisodeEntity 7 | 8 | @Dao 9 | interface EpisodeFavoriteDao : BaseDao { 10 | @Query("SELECT * FROM ${EpisodeEntity.TABLE_NAME}") 11 | suspend fun getFavoriteList(): List 12 | 13 | @Query("SELECT * FROM ${EpisodeEntity.TABLE_NAME} WHERE id = :favoriteId") 14 | suspend fun getFavorite(favoriteId: Int): EpisodeEntity? 15 | 16 | @Query("DELETE FROM ${EpisodeEntity.TABLE_NAME}") 17 | suspend fun deleteFavoriteList() 18 | 19 | @Query("DELETE FROM ${EpisodeEntity.TABLE_NAME} WHERE id = :favoriteId") 20 | suspend fun deleteFavoriteById(favoriteId: Int) 21 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/developersancho/local/dao/LocationFavoriteDao.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.local.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Query 5 | import com.developersancho.framework.room.dao.BaseDao 6 | import com.developersancho.model.local.location.LocationEntity 7 | 8 | @Dao 9 | interface LocationFavoriteDao : BaseDao { 10 | @Query("SELECT * FROM ${LocationEntity.TABLE_NAME}") 11 | suspend fun getFavoriteList(): List 12 | 13 | @Query("SELECT * FROM ${LocationEntity.TABLE_NAME} WHERE id = :favoriteId") 14 | suspend fun getFavorite(favoriteId: Int): LocationEntity? 15 | 16 | @Query("DELETE FROM ${LocationEntity.TABLE_NAME}") 17 | suspend fun deleteFavoriteList() 18 | 19 | @Query("DELETE FROM ${LocationEntity.TABLE_NAME} WHERE id = :favoriteId") 20 | suspend fun deleteFavoriteById(favoriteId: Int) 21 | } -------------------------------------------------------------------------------- /data/local/src/main/java/com/developersancho/local/db/RortyDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.local.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import com.developersancho.local.converter.StringListConverter 7 | import com.developersancho.local.dao.CharacterFavoriteDao 8 | import com.developersancho.local.dao.EpisodeFavoriteDao 9 | import com.developersancho.local.dao.LocationFavoriteDao 10 | import com.developersancho.model.local.character.CharacterEntity 11 | import com.developersancho.model.local.episode.EpisodeEntity 12 | import com.developersancho.model.local.location.LocationEntity 13 | 14 | @Database( 15 | entities = [CharacterEntity::class, EpisodeEntity::class, LocationEntity::class], 16 | version = 1, 17 | exportSchema = false 18 | ) 19 | @TypeConverters(StringListConverter::class) 20 | abstract class RortyDatabase : RoomDatabase() { 21 | abstract fun characterFavoriteDao(): CharacterFavoriteDao 22 | abstract fun episodeFavoriteDao(): EpisodeFavoriteDao 23 | abstract fun locationFavoriteDao(): LocationFavoriteDao 24 | } -------------------------------------------------------------------------------- /data/model/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/model/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.FRAMEWORK 2 | import extensions.implementation 3 | import extensions.ksp 4 | 5 | plugins { 6 | id("commons.android-library") 7 | id("com.google.devtools.ksp") 8 | id("codeanalyzetools.jacoco-report") 9 | } 10 | 11 | dependencies { 12 | FRAMEWORK 13 | 14 | implementation(NetworkLib.Moshi) 15 | ksp(NetworkLib.MoshiCodegen) 16 | implementation(NetworkLib.MoshiLazyAdapter) 17 | 18 | implementation(StorageLib.RoomKtx) 19 | ksp(StorageLib.RoomCompiler) 20 | } -------------------------------------------------------------------------------- /data/model/src/androidTest/java/com/developersancho/model/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.model.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /data/model/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/dto/KeyValueModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data class KeyValueModel( 8 | val key: String?, 9 | val value: String?, 10 | var click: () -> Unit = {} 11 | ) : Parcelable -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/dto/character/CharacterLocationDto.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto.character 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data class CharacterLocationDto( 8 | val locationId: Int, 9 | val name: String, 10 | val url: String 11 | ) : Parcelable -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/dto/episode/EpisodeDto.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto.episode 2 | 3 | import android.os.Parcelable 4 | import com.developersancho.model.dto.character.CharacterDto 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class EpisodeDto( 9 | val id: Int, 10 | val name: String, 11 | val url: String?, 12 | val airDate: String?, 13 | val created: String?, 14 | val episode: String?, 15 | val characters: List, 16 | val characterDtoList: MutableList = mutableListOf(), 17 | var isFavorite: Boolean = false 18 | ) : Parcelable { 19 | companion object { 20 | fun init() = EpisodeDto( 21 | id = 28, 22 | name = "The Ricklantis Mixup", 23 | url = "https://rickandmortyapi.com/api/episode/28", 24 | airDate = "September 10, 2017", 25 | created = "2017-11-10T12:56:36.618Z", 26 | episode = "S03E07", 27 | characters = listOf( 28 | "https://rickandmortyapi.com/api/character/1", 29 | "https://rickandmortyapi.com/api/character/2" 30 | ) 31 | ) 32 | } 33 | } -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/dto/episode/EpisodeDtoExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto.episode 2 | 3 | import com.developersancho.framework.extension.orZero 4 | import com.developersancho.model.local.episode.EpisodeEntity 5 | import com.developersancho.model.remote.episode.EpisodeInfo 6 | 7 | fun EpisodeInfo.toEpisodeDto() = EpisodeDto( 8 | id = id.orZero(), 9 | name = name.orEmpty(), 10 | url = url, 11 | airDate = airDate, 12 | created = created, 13 | episode = episode, 14 | characters = characters.orEmpty() 15 | ) 16 | 17 | fun List.toEpisodeDtoList() = map { it.toEpisodeDto() } 18 | 19 | fun EpisodeEntity.toEpisodeDto() = EpisodeDto( 20 | id = id.orZero(), 21 | name = name, 22 | url = url, 23 | airDate = airDate, 24 | created = created, 25 | episode = episode, 26 | characters = characters 27 | ) 28 | 29 | fun List.toFavoriteDtoList() = map { it.toEpisodeDto() } 30 | 31 | fun EpisodeDto.toEpisodeEntity() = EpisodeEntity( 32 | id = id.orZero(), 33 | name = name, 34 | url = url.orEmpty(), 35 | airDate = airDate.orEmpty(), 36 | created = created.orEmpty(), 37 | episode = episode.orEmpty(), 38 | characters = characters 39 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/dto/language/LanguageDto.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto.language 2 | 3 | data class LanguageDto( 4 | val id: Int, 5 | val code: String, 6 | val name: String, 7 | var isSelected: Boolean = false 8 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/dto/location/LocationDto.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto.location 2 | 3 | import android.os.Parcelable 4 | import com.developersancho.model.dto.character.CharacterDto 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class LocationDto( 9 | val id: Int, 10 | val name: String, 11 | val url: String? = null, 12 | val dimension: String? = null, 13 | val created: String? = null, 14 | val type: String? = null, 15 | val residents: List = emptyList(), 16 | val residentDtoList: MutableList = mutableListOf(), 17 | var isFavorite: Boolean = false 18 | ) : Parcelable { 19 | companion object { 20 | fun init() = LocationDto( 21 | id = 3, 22 | name = "Citadel of Ricks", 23 | url = "https://rickandmortyapi.com/api/location/3", 24 | dimension = "unknown", 25 | created = "2017-11-10T13:08:13.191Z", 26 | type = "Space station", 27 | residents = listOf( 28 | "https://rickandmortyapi.com/api/character/8", 29 | "https://rickandmortyapi.com/api/character/14" 30 | ) 31 | ) 32 | } 33 | } -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/dto/location/LocationDtoExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto.location 2 | 3 | import com.developersancho.framework.extension.orZero 4 | import com.developersancho.model.local.location.LocationEntity 5 | import com.developersancho.model.remote.location.LocationInfo 6 | 7 | fun LocationInfo.toLocationDto() = LocationDto( 8 | id = id.orZero(), 9 | name = name.orEmpty(), 10 | url = url, 11 | dimension = dimension, 12 | created = created, 13 | type = type, 14 | residents = residents.orEmpty() 15 | ) 16 | 17 | fun List.toLocationDtoList() = map { it.toLocationDto() } 18 | 19 | fun LocationEntity.toLocationDto() = LocationDto( 20 | id = id.orZero(), 21 | name = name, 22 | url = url, 23 | dimension = dimension, 24 | created = created, 25 | type = type, 26 | residents = residents 27 | ) 28 | 29 | fun List.toFavoriteDtoList() = map { it.toLocationDto() } 30 | 31 | fun LocationDto.toLocationEntity() = LocationEntity( 32 | id = id.orZero(), 33 | name = name, 34 | url = url.orEmpty(), 35 | dimension = dimension.orEmpty(), 36 | created = created.orEmpty(), 37 | type = type.orEmpty(), 38 | residents = residents 39 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/local/character/CharacterLocationEntity.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.local.character 2 | 3 | data class CharacterLocationEntity( 4 | val locationId: Int, 5 | val name: String, 6 | val url: String 7 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/local/episode/EpisodeEntity.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.local.episode 2 | 3 | import androidx.annotation.NonNull 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity(tableName = EpisodeEntity.TABLE_NAME) 9 | data class EpisodeEntity( 10 | @PrimaryKey 11 | @NonNull 12 | @ColumnInfo(name = COLUMN_ID) val id: Int, 13 | @ColumnInfo(name = COLUMN_NAME) val name: String, 14 | @ColumnInfo(name = COLUMN_URL) val url: String, 15 | @ColumnInfo(name = COLUMN_AIR_DATE) val airDate: String, 16 | @ColumnInfo(name = COLUMN_CREATED) val created: String, 17 | @ColumnInfo(name = COLUMN_EPISODE) val episode: String, 18 | @ColumnInfo(name = COLUMN_CHARACTERS) val characters: List 19 | ) { 20 | companion object { 21 | const val TABLE_NAME = "episode_favorite" 22 | const val COLUMN_ID = "id" 23 | const val COLUMN_NAME = "name" 24 | const val COLUMN_URL = "url" 25 | const val COLUMN_AIR_DATE = "airDate" 26 | const val COLUMN_CREATED = "created" 27 | const val COLUMN_EPISODE = "episode" 28 | const val COLUMN_CHARACTERS = "characters" 29 | } 30 | } -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/local/location/LocationEntity.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.local.location 2 | 3 | import androidx.annotation.NonNull 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | 8 | @Entity(tableName = LocationEntity.TABLE_NAME) 9 | data class LocationEntity( 10 | @PrimaryKey 11 | @NonNull 12 | @ColumnInfo(name = COLUMN_ID) val id: Int, 13 | @ColumnInfo(name = COLUMN_NAME) val name: String, 14 | @ColumnInfo(name = COLUMN_URL) val url: String, 15 | @ColumnInfo(name = COLUMN_DIMENSION) val dimension: String, 16 | @ColumnInfo(name = COLUMN_CREATED) val created: String, 17 | @ColumnInfo(name = COLUMN_TYPE) val type: String, 18 | @ColumnInfo(name = COLUMN_RESIDENTS) val residents: List 19 | ) { 20 | companion object { 21 | const val TABLE_NAME = "location_favorite" 22 | const val COLUMN_ID = "id" 23 | const val COLUMN_NAME = "name" 24 | const val COLUMN_URL = "url" 25 | const val COLUMN_DIMENSION = "dimension" 26 | const val COLUMN_CREATED = "created" 27 | const val COLUMN_TYPE = "type" 28 | const val COLUMN_RESIDENTS = "residents" 29 | } 30 | } -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/base/PageInfo.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.base 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class PageInfo( 8 | @Json(name = "count") val count: Int?, 9 | @Json(name = "next") val next: String?, 10 | @Json(name = "pages") val pages: Int?, 11 | @Json(name = "prev") val prev: String? 12 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/base/Status.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.base 2 | 3 | import com.developersancho.framework.network.moshi.IValueEnum 4 | import com.serjltt.moshi.adapters.FallbackEnum 5 | import com.squareup.moshi.Json 6 | 7 | @FallbackEnum(name = "unknown") 8 | enum class Status(override val value: String) : IValueEnum { 9 | @Json(name = "Alive") 10 | Alive("Alive"), 11 | 12 | @Json(name = "Dead") 13 | Dead("Dead"), 14 | 15 | @Json(name = "unknown") 16 | Unknown("unknown"); 17 | } -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/character/CharacterInfo.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.developersancho.model.remote.base.Status 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class CharacterInfo( 9 | @Json(name = "created") val created: String?, 10 | @Json(name = "episode") val episodes: List?, 11 | @Json(name = "gender") val gender: String?, 12 | @Json(name = "id") val id: Int?, 13 | @Json(name = "image") val image: String?, 14 | @Json(name = "location") val location: Location?, 15 | @Json(name = "name") val name: String?, 16 | @Json(name = "origin") val origin: Origin?, 17 | @Json(name = "species") val species: String?, 18 | @Json(name = "status") val status: Status?, 19 | @Json(name = "type") val type: String?, 20 | @Json(name = "url") val url: String? 21 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/character/CharacterResponse.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.developersancho.model.remote.base.PageInfo 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class CharacterResponse( 9 | @Json(name = "info") val pageInfo: PageInfo?, 10 | @Json(name = "results") val results: List? 11 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/character/Location.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class Location( 8 | @Json(name = "name") val name: String?, 9 | @Json(name = "url") val url: String? 10 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/character/Origin.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class Origin( 8 | @Json(name = "name") val name: String?, 9 | @Json(name = "url") val url: String? 10 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/episode/EpisodeInfo.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.episode 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class EpisodeInfo( 8 | @Json(name = "air_date") val airDate: String?, 9 | @Json(name = "characters") val characters: List?, 10 | @Json(name = "created") val created: String?, 11 | @Json(name = "episode") val episode: String?, 12 | @Json(name = "id") val id: Int?, 13 | @Json(name = "name") val name: String?, 14 | @Json(name = "url") val url: String? 15 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/episode/EpisodeResponse.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.episode 2 | 3 | import com.developersancho.model.remote.base.PageInfo 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class EpisodeResponse( 9 | @Json(name = "info") val pageInfo: PageInfo?, 10 | @Json(name = "results") val results: List? 11 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/location/LocationInfo.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.location 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class LocationInfo( 8 | @Json(name = "dimension") val dimension: String?, 9 | @Json(name = "residents") val residents: List?, 10 | @Json(name = "created") val created: String?, 11 | @Json(name = "type") val type: String?, 12 | @Json(name = "id") val id: Int?, 13 | @Json(name = "name") val name: String?, 14 | @Json(name = "url") val url: String? 15 | ) -------------------------------------------------------------------------------- /data/model/src/main/java/com/developersancho/model/remote/location/LocationResponse.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.location 2 | 3 | import com.developersancho.model.remote.base.PageInfo 4 | import com.squareup.moshi.Json 5 | import com.squareup.moshi.JsonClass 6 | 7 | @JsonClass(generateAdapter = true) 8 | data class LocationResponse( 9 | @Json(name = "info") val pageInfo: PageInfo?, 10 | @Json(name = "results") val results: List? 11 | ) -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/dto/CharacterDtoTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto 2 | 3 | import com.developersancho.model.dto.character.CharacterDto 4 | import com.developersancho.model.remote.base.Status 5 | import org.junit.Assert 6 | import org.junit.Test 7 | 8 | class CharacterDtoTest { 9 | 10 | @Test 11 | fun checkCorrectAttributes() { 12 | val characterId = 1 13 | val characterName = "A.I.M" 14 | val characterImageUrl = "http://i.annihil.us/535fecbbb9784.jpg" 15 | 16 | val dto = CharacterDto( 17 | id = characterId, 18 | name = characterName, 19 | imageUrl = characterImageUrl, 20 | created = "", 21 | origin = null, 22 | location = null, 23 | status = Status.Unknown, 24 | species = "", 25 | gender = "", 26 | type = "", 27 | url = "", 28 | episodes = emptyList() 29 | ) 30 | 31 | Assert.assertEquals(characterId, dto.id) 32 | Assert.assertEquals(characterName, dto.name) 33 | Assert.assertEquals(characterImageUrl, dto.imageUrl) 34 | } 35 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/dto/EpisodeDtoTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto 2 | 3 | import com.developersancho.model.dto.episode.EpisodeDto 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class EpisodeDtoTest { 8 | @Test 9 | fun checkCorrectAttributes() { 10 | val id = 1 11 | val name = "Citadel of Ricks" 12 | val url = "https://rickandmortyapi.com/api/location/3" 13 | 14 | val dto = EpisodeDto( 15 | id = id, 16 | name = name, 17 | url = url, 18 | "", 19 | "", 20 | "", 21 | emptyList() 22 | ) 23 | 24 | Assert.assertEquals(id, dto.id) 25 | Assert.assertEquals(name, dto.name) 26 | Assert.assertEquals(url, dto.url) 27 | } 28 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/dto/LocationDtoTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.dto 2 | 3 | import com.developersancho.model.dto.location.LocationDto 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class LocationDtoTest { 8 | 9 | @Test 10 | fun checkCorrectAttributes() { 11 | val locationId = 1 12 | val name = "Citadel of Ricks" 13 | val url = "https://rickandmortyapi.com/api/location/3" 14 | 15 | val dto = LocationDto( 16 | id = locationId, 17 | name = name, 18 | url = url 19 | ) 20 | 21 | Assert.assertEquals(locationId, dto.id) 22 | Assert.assertEquals(name, dto.name) 23 | Assert.assertEquals(url, dto.url) 24 | } 25 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/local/character/CharacterEntityTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.local.character 2 | 3 | import com.developersancho.model.remote.base.Status 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class CharacterEntityTest { 8 | 9 | @Test 10 | fun checkCorrectAttributes() { 11 | val characterId = 1 12 | val characterName = "A.I.M" 13 | val characterImageUrl = "http://i.annihil.us/535fecbbb9784.jpg" 14 | 15 | val entity = CharacterEntity( 16 | id = characterId, 17 | name = characterName, 18 | imageUrl = characterImageUrl, 19 | created = "", 20 | origin = null, 21 | location = null, 22 | status = Status.Unknown, 23 | species = "", 24 | gender = "", 25 | type = "", 26 | url = "", 27 | episodes = emptyList() 28 | ) 29 | 30 | Assert.assertEquals(characterId, entity.id) 31 | Assert.assertEquals(characterName, entity.name) 32 | Assert.assertEquals(characterImageUrl, entity.imageUrl) 33 | } 34 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/local/episode/EpisodeEntityTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.local.episode 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | class EpisodeEntityTest { 7 | @Test 8 | fun checkCorrectAttributes() { 9 | val id = 1 10 | val name = "The Ricklantis Mixup" 11 | 12 | val entity = EpisodeEntity( 13 | id = id, 14 | name = name, 15 | "", 16 | "", 17 | "", 18 | "", 19 | emptyList() 20 | ) 21 | 22 | Assert.assertEquals(id, entity.id) 23 | Assert.assertEquals(name, entity.name) 24 | } 25 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/local/location/LocationEntityTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.local.location 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | class LocationEntityTest { 7 | @Test 8 | fun checkCorrectAttributes() { 9 | val id = 1 10 | val name = "The Ricklantis Mixup" 11 | 12 | val entity = LocationEntity( 13 | id = id, 14 | name = name, 15 | "", 16 | "", 17 | "", 18 | "", 19 | emptyList() 20 | ) 21 | 22 | Assert.assertEquals(id, entity.id) 23 | Assert.assertEquals(name, entity.name) 24 | } 25 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/base/PageInfoTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.base 2 | 3 | import com.developersancho.testutils.BaseModelTest 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class PageInfoTest : BaseModelTest() { 8 | override fun checkModelClass(): Class<*> { 9 | return PageInfo::class.java 10 | } 11 | 12 | override fun checkModelFields(): List { 13 | return listOf( 14 | "count", 15 | "next", 16 | "pages", 17 | "prev" 18 | ) 19 | } 20 | 21 | @Test 22 | fun createResponse() { 23 | val pageInfo = PageInfo( 24 | count = 10, 25 | next = "", 26 | pages = 1, 27 | prev = "" 28 | ) 29 | 30 | Assert.assertEquals(10, pageInfo.count) 31 | Assert.assertEquals(1, pageInfo.pages) 32 | Assert.assertEquals("", pageInfo.next) 33 | Assert.assertEquals("", pageInfo.prev) 34 | } 35 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/base/StatusTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.base 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | 6 | class StatusTest { 7 | 8 | @Test 9 | fun checkFieldValues() { 10 | Assert.assertEquals(Status.Alive.value, "Alive") 11 | Assert.assertEquals(Status.Dead.value, "Dead") 12 | Assert.assertEquals(Status.Unknown.value, "unknown") 13 | } 14 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/character/CharacterInfoTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.developersancho.testutils.BaseModelTest 4 | import io.mockk.mockk 5 | import org.junit.Assert 6 | import org.junit.Test 7 | 8 | class CharacterInfoTest : BaseModelTest() { 9 | 10 | override fun checkModelClass(): Class<*> { 11 | return CharacterInfo::class.java 12 | } 13 | 14 | override fun checkModelFields(): List { 15 | return listOf( 16 | "created", 17 | "episode", 18 | "gender", 19 | "id", 20 | "image", 21 | "location", 22 | "name", 23 | "origin", 24 | "species", 25 | "status", 26 | "type", 27 | "url" 28 | ) 29 | } 30 | 31 | @Test 32 | fun createResponse() { 33 | val characterInfo: CharacterInfo = mockk() 34 | Assert.assertNotNull(characterInfo) 35 | } 36 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/character/CharacterResponseTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.developersancho.model.remote.base.PageInfo 4 | import com.developersancho.testutils.BaseModelTest 5 | import io.mockk.mockk 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | class CharacterResponseTest : BaseModelTest() { 10 | 11 | override fun checkModelClass(): Class<*> { 12 | return CharacterResponse::class.java 13 | } 14 | 15 | override fun checkModelFields(): List { 16 | return listOf( 17 | "info", 18 | "results" 19 | ) 20 | } 21 | 22 | @Test 23 | fun createResponse() { 24 | val pageInfo: PageInfo = mockk() 25 | val characterInfo: CharacterInfo = mockk() 26 | 27 | val response = CharacterResponse( 28 | pageInfo = pageInfo, 29 | results = listOf(characterInfo) 30 | ) 31 | 32 | Assert.assertEquals(characterInfo, response.results?.first()) 33 | Assert.assertEquals(pageInfo, response.pageInfo) 34 | } 35 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/character/LocationTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.developersancho.testutils.BaseModelTest 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class LocationTest : BaseModelTest() { 8 | 9 | override fun checkModelClass(): Class<*> { 10 | return Location::class.java 11 | } 12 | 13 | override fun checkModelFields(): List { 14 | return listOf( 15 | "name", 16 | "url" 17 | ) 18 | } 19 | 20 | @Test 21 | fun createResponse() { 22 | val location = Location( 23 | name = "Earth (Replacement Dimension)", 24 | url = "https://rickandmortyapi.com/api/location/20" 25 | ) 26 | 27 | Assert.assertEquals("Earth (Replacement Dimension)", location.name) 28 | Assert.assertEquals("https://rickandmortyapi.com/api/location/20", location.url) 29 | } 30 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/character/OriginTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.character 2 | 3 | import com.developersancho.testutils.BaseModelTest 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class OriginTest : BaseModelTest() { 8 | 9 | override fun checkModelClass(): Class<*> { 10 | return Origin::class.java 11 | } 12 | 13 | override fun checkModelFields(): List { 14 | return listOf( 15 | "name", 16 | "url" 17 | ) 18 | } 19 | 20 | @Test 21 | fun createResponse() { 22 | val origin = Origin( 23 | name = "Earth (C-137)", 24 | url = "https://rickandmortyapi.com/api/location/1" 25 | ) 26 | 27 | Assert.assertEquals("Earth (C-137)", origin.name) 28 | Assert.assertEquals("https://rickandmortyapi.com/api/location/1", origin.url) 29 | } 30 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/episode/EpisodeInfoTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.episode 2 | 3 | import com.developersancho.testutils.BaseModelTest 4 | import io.mockk.mockk 5 | import org.junit.Assert 6 | import org.junit.Test 7 | 8 | class EpisodeInfoTest : BaseModelTest() { 9 | 10 | override fun checkModelClass(): Class<*> { 11 | return EpisodeInfo::class.java 12 | } 13 | 14 | override fun checkModelFields(): List { 15 | return listOf( 16 | "air_date", 17 | "characters", 18 | "created", 19 | "episode", 20 | "id", 21 | "name", 22 | "url" 23 | ) 24 | } 25 | 26 | @Test 27 | fun createResponse() { 28 | val episodeInfo: EpisodeInfo = mockk() 29 | Assert.assertNotNull(episodeInfo) 30 | } 31 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/episode/EpisodeResponseTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.episode 2 | 3 | import com.developersancho.model.remote.base.PageInfo 4 | import com.developersancho.testutils.BaseModelTest 5 | import io.mockk.mockk 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | class EpisodeResponseTest : BaseModelTest() { 10 | 11 | override fun checkModelClass(): Class<*> { 12 | return EpisodeResponse::class.java 13 | } 14 | 15 | override fun checkModelFields(): List { 16 | return listOf( 17 | "info", 18 | "results" 19 | ) 20 | } 21 | 22 | @Test 23 | fun createResponse() { 24 | val pageInfo: PageInfo = mockk() 25 | val episodeInfo: EpisodeInfo = mockk() 26 | 27 | val response = EpisodeResponse( 28 | pageInfo = pageInfo, 29 | results = listOf(episodeInfo) 30 | ) 31 | 32 | Assert.assertEquals(episodeInfo, response.results?.first()) 33 | Assert.assertEquals(pageInfo, response.pageInfo) 34 | } 35 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/location/LocationInfoTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.location 2 | 3 | import com.developersancho.testutils.BaseModelTest 4 | import io.mockk.mockk 5 | import org.junit.Assert 6 | import org.junit.Test 7 | 8 | class LocationInfoTest : BaseModelTest() { 9 | 10 | override fun checkModelClass(): Class<*> { 11 | return LocationInfo::class.java 12 | } 13 | 14 | override fun checkModelFields(): List { 15 | return listOf( 16 | "dimension", 17 | "residents", 18 | "created", 19 | "type", 20 | "id", 21 | "name", 22 | "url" 23 | ) 24 | } 25 | 26 | @Test 27 | fun createResponse() { 28 | val locationInfo: LocationInfo = mockk() 29 | Assert.assertNotNull(locationInfo) 30 | } 31 | } -------------------------------------------------------------------------------- /data/model/src/test/java/com/developersancho/model/remote/location/LocationResponseTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.model.remote.location 2 | 3 | import com.developersancho.model.remote.base.PageInfo 4 | import com.developersancho.testutils.BaseModelTest 5 | import io.mockk.mockk 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | class LocationResponseTest : BaseModelTest() { 10 | 11 | override fun checkModelClass(): Class<*> { 12 | return LocationResponse::class.java 13 | } 14 | 15 | override fun checkModelFields(): List { 16 | return listOf( 17 | "info", 18 | "results" 19 | ) 20 | } 21 | 22 | @Test 23 | fun createResponse() { 24 | val pageInfo: PageInfo = mockk() 25 | val locationInfo: LocationInfo = mockk() 26 | 27 | val response = LocationResponse( 28 | pageInfo = pageInfo, 29 | results = listOf(locationInfo) 30 | ) 31 | 32 | Assert.assertEquals(locationInfo, response.results?.first()) 33 | Assert.assertEquals(pageInfo, response.pageInfo) 34 | } 35 | } -------------------------------------------------------------------------------- /data/remote/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/remote/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-library") 5 | id("com.google.devtools.ksp") 6 | id("dagger.hilt.android.plugin") 7 | id("codeanalyzetools.jacoco-report") 8 | } 9 | 10 | dependencies { 11 | FRAMEWORK 12 | MODEL 13 | 14 | // Network 15 | addNetworkDependencies() 16 | 17 | // Dagger Hilt 18 | implementation(DaggerHiltLib.Android) 19 | kapt(DaggerHiltLib.Compiler) 20 | } -------------------------------------------------------------------------------- /data/remote/src/androidTest/java/com/developersancho/remote/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.remote 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.remote.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /data/remote/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /data/remote/src/main/java/com/developersancho/remote/service/CharacterService.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.remote.service 2 | 3 | import com.developersancho.model.remote.character.CharacterInfo 4 | import com.developersancho.model.remote.character.CharacterResponse 5 | import retrofit2.http.* 6 | 7 | interface CharacterService { 8 | @GET(CHARACTER) 9 | suspend fun getCharacterList( 10 | @Query("page") page: Int, 11 | @QueryMap options: Map? = null 12 | ): CharacterResponse 13 | 14 | @GET("$CHARACTER/{id}") 15 | suspend fun getCharacter( 16 | @Path("id") characterId: Int 17 | ): CharacterInfo 18 | 19 | @GET 20 | suspend fun getCharacter( 21 | @Url url: String 22 | ): CharacterInfo 23 | 24 | companion object { 25 | const val CHARACTER = "character" 26 | } 27 | } -------------------------------------------------------------------------------- /data/remote/src/main/java/com/developersancho/remote/service/EpisodeService.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.remote.service 2 | 3 | import com.developersancho.model.remote.episode.EpisodeInfo 4 | import com.developersancho.model.remote.episode.EpisodeResponse 5 | import retrofit2.http.* 6 | 7 | interface EpisodeService { 8 | @GET(EPISODE) 9 | suspend fun getEpisodeList( 10 | @Query("page") page: Int, 11 | @QueryMap options: Map? = null 12 | ): EpisodeResponse 13 | 14 | @GET("$EPISODE/{id}") 15 | suspend fun getEpisode( 16 | @Path("id") episodeId: Int 17 | ): EpisodeInfo 18 | 19 | @GET 20 | suspend fun getEpisode( 21 | @Url url: String 22 | ): EpisodeInfo 23 | 24 | companion object { 25 | const val EPISODE = "episode" 26 | } 27 | } -------------------------------------------------------------------------------- /data/remote/src/main/java/com/developersancho/remote/service/LocationService.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.remote.service 2 | 3 | import com.developersancho.model.remote.location.LocationInfo 4 | import com.developersancho.model.remote.location.LocationResponse 5 | import retrofit2.http.* 6 | 7 | interface LocationService { 8 | @GET(LOCATION) 9 | suspend fun getLocationList( 10 | @Query("page") page: Int, 11 | @QueryMap options: Map? = null 12 | ): LocationResponse 13 | 14 | @GET("$LOCATION/{id}") 15 | suspend fun getLocation( 16 | @Path("id") locationId: Int 17 | ): LocationInfo 18 | 19 | @GET 20 | suspend fun getLocation( 21 | @Url url: String 22 | ): LocationInfo 23 | 24 | companion object { 25 | const val LOCATION = "location" 26 | } 27 | } -------------------------------------------------------------------------------- /data/remote/src/main/resources/mock-responses/get-episode.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Pilot", 4 | "air_date": "December 2, 2013", 5 | "episode": "S01E01", 6 | "characters": [ 7 | "https://rickandmortyapi.com/api/character/1", 8 | "https://rickandmortyapi.com/api/character/2", 9 | "https://rickandmortyapi.com/api/character/35", 10 | "https://rickandmortyapi.com/api/character/38", 11 | "https://rickandmortyapi.com/api/character/62", 12 | "https://rickandmortyapi.com/api/character/92", 13 | "https://rickandmortyapi.com/api/character/127", 14 | "https://rickandmortyapi.com/api/character/144", 15 | "https://rickandmortyapi.com/api/character/158", 16 | "https://rickandmortyapi.com/api/character/175", 17 | "https://rickandmortyapi.com/api/character/179", 18 | "https://rickandmortyapi.com/api/character/181", 19 | "https://rickandmortyapi.com/api/character/239", 20 | "https://rickandmortyapi.com/api/character/249", 21 | "https://rickandmortyapi.com/api/character/271", 22 | "https://rickandmortyapi.com/api/character/338", 23 | "https://rickandmortyapi.com/api/character/394", 24 | "https://rickandmortyapi.com/api/character/395", 25 | "https://rickandmortyapi.com/api/character/435" 26 | ], 27 | "url": "https://rickandmortyapi.com/api/episode/1", 28 | "created": "2017-11-10T12:56:33.798Z" 29 | } 30 | -------------------------------------------------------------------------------- /data/remote/src/test/java/com/developersancho/remote/service/mock/MockResponses.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.remote.service.mock 2 | 3 | object MockResponses { 4 | object GetCharacters { 5 | const val STATUS_200 = "mock-responses/get-characters.json" 6 | } 7 | 8 | object GetCharactersByFilter { 9 | const val STATUS_200 = "mock-responses/get-characters-by-filter.json" 10 | } 11 | 12 | object GetCharacter { 13 | const val STATUS_200 = "mock-responses/get-character.json" 14 | } 15 | 16 | object GetEpisodes { 17 | const val STATUS_200 = "mock-responses/get-episodes.json" 18 | } 19 | 20 | object GetEpisodesByFilter { 21 | const val STATUS_200 = "mock-responses/get-episodes-by-filter.json" 22 | } 23 | 24 | object GetEpisode { 25 | const val STATUS_200 = "mock-responses/get-episode.json" 26 | } 27 | 28 | object GetLocations { 29 | const val STATUS_200 = "mock-responses/get-locations.json" 30 | } 31 | 32 | object GetLocationsByFilter { 33 | const val STATUS_200 = "mock-responses/get-locations-by-filter.json" 34 | } 35 | 36 | object GetLocation { 37 | const val STATUS_200 = "mock-responses/get-location.json" 38 | } 39 | } -------------------------------------------------------------------------------- /data/repository/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/repository/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-library") 5 | id("dagger.hilt.android.plugin") 6 | id("codeanalyzetools.jacoco-report") 7 | } 8 | 9 | dependencies { 10 | FRAMEWORK 11 | MODEL 12 | LOCAL 13 | REMOTE 14 | 15 | // Dagger Hilt 16 | implementation(DaggerHiltLib.Android) 17 | kapt(DaggerHiltLib.Compiler) 18 | 19 | implementation(StorageLib.DatastorePref) 20 | } -------------------------------------------------------------------------------- /data/repository/src/androidTest/java/com/developersancho/repository/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.repository 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.repository.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /data/repository/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-library") 5 | id("dagger.hilt.android.plugin") 6 | id("codeanalyzetools.jacoco-report") 7 | } 8 | 9 | dependencies { 10 | FRAMEWORK 11 | MODEL 12 | LOCAL 13 | REMOTE 14 | REPOSITORY 15 | 16 | // Dagger Hilt 17 | implementation(DaggerHiltLib.Android) 18 | kapt(DaggerHiltLib.Compiler) 19 | // Paging 20 | implementation(SupportLib.Paging) 21 | } -------------------------------------------------------------------------------- /domain/src/androidTest/java/com/developersancho/domain/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.domain.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/di/DomainModule.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.di 2 | 3 | import android.annotation.SuppressLint 4 | import dagger.Module 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | 8 | @SuppressLint("VisibleForTests") 9 | @Module( 10 | includes = [ 11 | CharacterDomainModule::class, 12 | EpisodeDomainModule::class, 13 | LocationDomainModule::class, 14 | WelcomeModule::class, 15 | LanguageModule::class 16 | ] 17 | ) 18 | @InstallIn(SingletonComponent::class) 19 | class DomainModule -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/di/LanguageModule.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.di 2 | 3 | import android.annotation.SuppressLint 4 | import com.developersancho.domain.usecase.language.GetLanguage 5 | import com.developersancho.domain.usecase.language.SaveLanguage 6 | import com.developersancho.repository.langauge.LanguageRepository 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @SuppressLint("VisibleForTests") 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | class LanguageModule { 17 | 18 | @Singleton 19 | @Provides 20 | fun provideSaveLanguage(repository: LanguageRepository): SaveLanguage { 21 | return SaveLanguage(repository) 22 | } 23 | 24 | @Singleton 25 | @Provides 26 | fun provideGetLanguage(repository: LanguageRepository): GetLanguage { 27 | return GetLanguage(repository) 28 | } 29 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/di/WelcomeModule.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.di 2 | 3 | import android.annotation.SuppressLint 4 | import com.developersancho.domain.usecase.welcome.ReadOnBoarding 5 | import com.developersancho.domain.usecase.welcome.SaveOnBoarding 6 | import com.developersancho.repository.welcome.WelcomeRepository 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @SuppressLint("VisibleForTests") 14 | @Module 15 | @InstallIn(SingletonComponent::class) 16 | class WelcomeModule { 17 | 18 | @Singleton 19 | @Provides 20 | fun provideSaveOnBoarding(repository: WelcomeRepository): SaveOnBoarding { 21 | return SaveOnBoarding(repository) 22 | } 23 | 24 | @Singleton 25 | @Provides 26 | fun provideReadOnBoarding(repository: WelcomeRepository): ReadOnBoarding { 27 | return ReadOnBoarding(repository) 28 | } 29 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/character/GetCharacters.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.character 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.paging.Pager 5 | import androidx.paging.PagingConfig 6 | import androidx.paging.PagingData 7 | import com.developersancho.framework.usecase.FlowPagingUseCase 8 | import com.developersancho.model.dto.character.CharacterDto 9 | import com.developersancho.repository.character.CharacterRepository 10 | import kotlinx.coroutines.flow.Flow 11 | import javax.inject.Inject 12 | 13 | class GetCharacters @Inject constructor( 14 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 15 | internal val repository: CharacterRepository 16 | ) : FlowPagingUseCase() { 17 | 18 | data class Params( 19 | val pagingConfig: PagingConfig, 20 | val options: Map 21 | ) 22 | 23 | override fun execute(params: Params): Flow> { 24 | return Pager( 25 | config = params.pagingConfig, 26 | pagingSourceFactory = { CharacterPagingSource(repository, params.options) } 27 | ).flow 28 | } 29 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/character/favorite/AddCharacterFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.character.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.model.dto.character.CharacterDto 6 | import com.developersancho.model.dto.character.toCharacterEntity 7 | import com.developersancho.repository.character.CharacterRepository 8 | import kotlinx.coroutines.flow.FlowCollector 9 | import javax.inject.Inject 10 | 11 | class AddCharacterFavorite @Inject constructor( 12 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 13 | internal val repository: CharacterRepository 14 | ) : LocalUseCase() { 15 | 16 | data class Params( 17 | val character: CharacterDto 18 | ) 19 | 20 | override suspend fun FlowCollector.execute(params: Params) { 21 | val dto = params.character 22 | repository.saveFavorite(dto.toCharacterEntity()) 23 | emit(Unit) 24 | } 25 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/character/favorite/DeleteCharacterFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.character.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.repository.character.CharacterRepository 6 | import kotlinx.coroutines.flow.FlowCollector 7 | import javax.inject.Inject 8 | 9 | class DeleteCharacterFavorite @Inject constructor( 10 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 11 | internal val repository: CharacterRepository 12 | ) : LocalUseCase() { 13 | 14 | data class Params( 15 | val characterId: Int 16 | ) 17 | 18 | override suspend fun FlowCollector.execute(params: Params) { 19 | repository.deleteFavoriteById(params.characterId) 20 | emit(Unit) 21 | } 22 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/character/favorite/GetCharacterFavorites.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.character.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.model.dto.character.CharacterDto 6 | import com.developersancho.model.dto.character.toFavoriteDtoList 7 | import com.developersancho.repository.character.CharacterRepository 8 | import kotlinx.coroutines.flow.FlowCollector 9 | import javax.inject.Inject 10 | 11 | class GetCharacterFavorites @Inject constructor( 12 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 13 | internal val repository: CharacterRepository 14 | ) : LocalUseCase>() { 15 | 16 | override suspend fun FlowCollector>.execute(params: Unit) { 17 | val favors = repository.getFavoriteList() 18 | emit(favors.toFavoriteDtoList()) 19 | } 20 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/character/favorite/UpdateCharacterFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.character.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.extension.orZero 5 | import com.developersancho.framework.usecase.LocalUseCase 6 | import com.developersancho.model.dto.character.CharacterDto 7 | import com.developersancho.model.dto.character.toCharacterEntity 8 | import com.developersancho.repository.character.CharacterRepository 9 | import kotlinx.coroutines.flow.FlowCollector 10 | import javax.inject.Inject 11 | 12 | class UpdateCharacterFavorite @Inject constructor( 13 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 14 | internal val repository: CharacterRepository 15 | ) : LocalUseCase() { 16 | 17 | data class Params( 18 | val character: CharacterDto 19 | ) 20 | 21 | override suspend fun FlowCollector.execute(params: Params) { 22 | val dto = params.character 23 | val character = repository.getFavorite(dto.id.orZero()) 24 | if (character == null) { 25 | repository.saveFavorite(dto.toCharacterEntity()) 26 | } else { 27 | repository.deleteFavoriteById(dto.id.orZero()) 28 | } 29 | emit(Unit) 30 | } 31 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/episode/GetEpisodes.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.paging.Pager 5 | import androidx.paging.PagingConfig 6 | import androidx.paging.PagingData 7 | import com.developersancho.framework.usecase.FlowPagingUseCase 8 | import com.developersancho.model.dto.episode.EpisodeDto 9 | import com.developersancho.repository.episode.EpisodeRepository 10 | import kotlinx.coroutines.flow.Flow 11 | import javax.inject.Inject 12 | 13 | class GetEpisodes @Inject constructor( 14 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 15 | internal val repository: EpisodeRepository 16 | ) : FlowPagingUseCase() { 17 | 18 | data class Params( 19 | val pagingConfig: PagingConfig, 20 | val options: Map 21 | ) 22 | 23 | override fun execute(params: Params): Flow> { 24 | return Pager( 25 | config = params.pagingConfig, 26 | pagingSourceFactory = { EpisodePagingSource(repository, params.options) } 27 | ).flow 28 | } 29 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/episode/favorite/AddEpisodeFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.model.dto.episode.EpisodeDto 6 | import com.developersancho.model.dto.episode.toEpisodeEntity 7 | import com.developersancho.repository.episode.EpisodeRepository 8 | import kotlinx.coroutines.flow.FlowCollector 9 | import javax.inject.Inject 10 | 11 | class AddEpisodeFavorite @Inject constructor( 12 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 13 | internal val repository: EpisodeRepository 14 | ) : LocalUseCase() { 15 | 16 | data class Params( 17 | val episode: EpisodeDto 18 | ) 19 | 20 | override suspend fun FlowCollector.execute(params: Params) { 21 | val dto = params.episode 22 | repository.saveFavorite(dto.toEpisodeEntity()) 23 | emit(Unit) 24 | } 25 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/episode/favorite/DeleteEpisodeFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.repository.episode.EpisodeRepository 6 | import kotlinx.coroutines.flow.FlowCollector 7 | import javax.inject.Inject 8 | 9 | class DeleteEpisodeFavorite @Inject constructor( 10 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 11 | internal val repository: EpisodeRepository 12 | ) : LocalUseCase() { 13 | 14 | data class Params( 15 | val episodeId: Int 16 | ) 17 | 18 | override suspend fun FlowCollector.execute(params: Params) { 19 | repository.deleteFavoriteById(params.episodeId) 20 | emit(Unit) 21 | } 22 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/episode/favorite/GetEpisodeFavorites.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.model.dto.episode.EpisodeDto 6 | import com.developersancho.model.dto.episode.toFavoriteDtoList 7 | import com.developersancho.repository.episode.EpisodeRepository 8 | import kotlinx.coroutines.flow.FlowCollector 9 | import javax.inject.Inject 10 | 11 | class GetEpisodeFavorites @Inject constructor( 12 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 13 | internal val repository: EpisodeRepository 14 | ) : LocalUseCase>() { 15 | 16 | override suspend fun FlowCollector>.execute(params: Unit) { 17 | val favors = repository.getFavoriteList() 18 | emit(favors.toFavoriteDtoList()) 19 | } 20 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/episode/favorite/UpdateEpisodeFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.extension.orZero 5 | import com.developersancho.framework.usecase.LocalUseCase 6 | import com.developersancho.model.dto.episode.EpisodeDto 7 | import com.developersancho.model.dto.episode.toEpisodeEntity 8 | import com.developersancho.repository.episode.EpisodeRepository 9 | import kotlinx.coroutines.flow.FlowCollector 10 | import javax.inject.Inject 11 | 12 | class UpdateEpisodeFavorite @Inject constructor( 13 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 14 | internal val repository: EpisodeRepository 15 | ) : LocalUseCase() { 16 | 17 | data class Params( 18 | val episode: EpisodeDto 19 | ) 20 | 21 | override suspend fun FlowCollector.execute(params: Params) { 22 | val dto = params.episode 23 | val character = repository.getFavorite(dto.id.orZero()) 24 | if (character == null) { 25 | repository.saveFavorite(dto.toEpisodeEntity()) 26 | } else { 27 | repository.deleteFavoriteById(dto.id.orZero()) 28 | } 29 | emit(Unit) 30 | } 31 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/language/GetLanguage.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.language 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.ReturnUseCase 5 | import com.developersancho.repository.langauge.LanguageRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetLanguage @Inject constructor( 10 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 11 | internal val repository: LanguageRepository 12 | ) : ReturnUseCase() { 13 | 14 | override suspend fun execute(params: Unit): Flow { 15 | return repository.getLanguage 16 | } 17 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/language/SaveLanguage.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.language 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.repository.langauge.LanguageRepository 6 | import kotlinx.coroutines.flow.FlowCollector 7 | import javax.inject.Inject 8 | 9 | class SaveLanguage @Inject constructor( 10 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 11 | internal val repository: LanguageRepository 12 | ) : LocalUseCase() { 13 | data class Params( 14 | val language: String 15 | ) 16 | 17 | override suspend fun FlowCollector.execute(params: Params) { 18 | repository.setLanguage(params.language) 19 | emit(Unit) 20 | } 21 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/location/GetLocations.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import androidx.paging.Pager 5 | import androidx.paging.PagingConfig 6 | import androidx.paging.PagingData 7 | import com.developersancho.framework.usecase.FlowPagingUseCase 8 | import com.developersancho.model.dto.location.LocationDto 9 | import com.developersancho.repository.location.LocationRepository 10 | import kotlinx.coroutines.flow.Flow 11 | import javax.inject.Inject 12 | 13 | class GetLocations @Inject constructor( 14 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 15 | internal val repository: LocationRepository 16 | ) : FlowPagingUseCase() { 17 | 18 | data class Params( 19 | val pagingConfig: PagingConfig, 20 | val options: Map 21 | ) 22 | 23 | override fun execute(params: Params): Flow> { 24 | return Pager( 25 | config = params.pagingConfig, 26 | pagingSourceFactory = { LocationPagingSource(repository, params.options) } 27 | ).flow 28 | } 29 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/location/favorite/AddLocationFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.model.dto.location.LocationDto 6 | import com.developersancho.model.dto.location.toLocationEntity 7 | import com.developersancho.repository.location.LocationRepository 8 | import kotlinx.coroutines.flow.FlowCollector 9 | import javax.inject.Inject 10 | 11 | class AddLocationFavorite @Inject constructor( 12 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 13 | internal val repository: LocationRepository 14 | ) : LocalUseCase() { 15 | 16 | data class Params( 17 | val location: LocationDto 18 | ) 19 | 20 | override suspend fun FlowCollector.execute(params: Params) { 21 | val dto = params.location 22 | repository.saveFavorite(dto.toLocationEntity()) 23 | emit(Unit) 24 | } 25 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/location/favorite/DeleteLocationFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.repository.location.LocationRepository 6 | import kotlinx.coroutines.flow.FlowCollector 7 | import javax.inject.Inject 8 | 9 | class DeleteLocationFavorite @Inject constructor( 10 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 11 | internal val repository: LocationRepository 12 | ) : LocalUseCase() { 13 | 14 | data class Params( 15 | val locationId: Int 16 | ) 17 | 18 | override suspend fun FlowCollector.execute(params: Params) { 19 | repository.deleteFavoriteById(params.locationId) 20 | emit(Unit) 21 | } 22 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/location/favorite/GetLocationFavorites.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.model.dto.location.LocationDto 6 | import com.developersancho.model.dto.location.toFavoriteDtoList 7 | import com.developersancho.repository.location.LocationRepository 8 | import kotlinx.coroutines.flow.FlowCollector 9 | import javax.inject.Inject 10 | 11 | class GetLocationFavorites @Inject constructor( 12 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 13 | internal val repository: LocationRepository 14 | ) : LocalUseCase>() { 15 | 16 | override suspend fun FlowCollector>.execute(params: Unit) { 17 | val favors = repository.getFavoriteList() 18 | emit(favors.toFavoriteDtoList()) 19 | } 20 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/location/favorite/UpdateLocationFavorite.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location.favorite 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.extension.orZero 5 | import com.developersancho.framework.usecase.LocalUseCase 6 | import com.developersancho.model.dto.location.LocationDto 7 | import com.developersancho.model.dto.location.toLocationEntity 8 | import com.developersancho.repository.location.LocationRepository 9 | import kotlinx.coroutines.flow.FlowCollector 10 | import javax.inject.Inject 11 | 12 | class UpdateLocationFavorite @Inject constructor( 13 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 14 | internal val repository: LocationRepository 15 | ) : LocalUseCase() { 16 | 17 | data class Params( 18 | val location: LocationDto 19 | ) 20 | 21 | override suspend fun FlowCollector.execute(params: Params) { 22 | val dto = params.location 23 | val character = repository.getFavorite(dto.id.orZero()) 24 | if (character == null) { 25 | repository.saveFavorite(dto.toLocationEntity()) 26 | } else { 27 | repository.deleteFavoriteById(dto.id.orZero()) 28 | } 29 | emit(Unit) 30 | } 31 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/welcome/ReadOnBoarding.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.welcome 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.ReturnUseCase 5 | import com.developersancho.repository.welcome.WelcomeRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class ReadOnBoarding @Inject constructor( 10 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 11 | internal val repository: WelcomeRepository 12 | ) : ReturnUseCase() { 13 | 14 | override suspend fun execute(params: Unit): Flow { 15 | return repository.readOnBoardingState() 16 | } 17 | } -------------------------------------------------------------------------------- /domain/src/main/java/com/developersancho/domain/usecase/welcome/SaveOnBoarding.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.welcome 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.developersancho.framework.usecase.LocalUseCase 5 | import com.developersancho.repository.welcome.WelcomeRepository 6 | import kotlinx.coroutines.flow.FlowCollector 7 | import javax.inject.Inject 8 | 9 | class SaveOnBoarding @Inject constructor( 10 | @get:VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) 11 | internal val repository: WelcomeRepository 12 | ) : LocalUseCase() { 13 | 14 | data class Params( 15 | val completed: Boolean 16 | ) 17 | 18 | override suspend fun FlowCollector.execute(params: Params) { 19 | repository.saveOnBoardingState(params.completed) 20 | emit(Unit) 21 | } 22 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/di/LanguageModuleTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.di 2 | 3 | import com.developersancho.repository.langauge.LanguageRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.mockk 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | class LanguageModuleTest : MockkUnitTest() { 10 | private lateinit var module: LanguageModule 11 | 12 | override fun onCreate() { 13 | super.onCreate() 14 | module = LanguageModule() 15 | } 16 | 17 | @Test 18 | fun verifyProvideSaveLanguage() { 19 | val repository = mockk() 20 | val saveLanguage = module.provideSaveLanguage(repository) 21 | 22 | Assert.assertEquals(repository, saveLanguage.repository) 23 | } 24 | 25 | @Test 26 | fun verifyProvideGetLanguage() { 27 | val repository = mockk() 28 | val getLanguage = module.provideGetLanguage(repository) 29 | 30 | Assert.assertEquals(repository, getLanguage.repository) 31 | } 32 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/di/WelcomeModuleTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.di 2 | 3 | import com.developersancho.repository.welcome.WelcomeRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.impl.annotations.MockK 6 | import org.junit.Assert 7 | import org.junit.Test 8 | 9 | class WelcomeModuleTest : MockkUnitTest() { 10 | private lateinit var module: WelcomeModule 11 | 12 | @MockK 13 | private lateinit var repository: WelcomeRepository 14 | 15 | override fun onCreate() { 16 | super.onCreate() 17 | module = WelcomeModule() 18 | } 19 | 20 | @Test 21 | fun verifyProvideSaveOnBoarding() { 22 | val saveOnBoarding = module.provideSaveOnBoarding(repository) 23 | 24 | Assert.assertEquals(repository, saveOnBoarding.repository) 25 | } 26 | 27 | @Test 28 | fun verifyProvideReadOnBoarding() { 29 | val readOnBoarding = module.provideReadOnBoarding(repository) 30 | 31 | Assert.assertEquals(repository, readOnBoarding.repository) 32 | } 33 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/character/GetCharactersTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.character 2 | 3 | import androidx.paging.PagingConfig 4 | import com.developersancho.repository.character.CharacterRepository 5 | import com.developersancho.testutils.MockkUnitTest 6 | import io.mockk.coVerify 7 | import io.mockk.impl.annotations.InjectMockKs 8 | import io.mockk.impl.annotations.MockK 9 | import io.mockk.impl.annotations.SpyK 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetCharactersTest : MockkUnitTest() { 14 | 15 | @MockK(relaxed = true) 16 | lateinit var repository: CharacterRepository 17 | 18 | @SpyK 19 | @InjectMockKs 20 | private lateinit var getCharacters: GetCharacters 21 | 22 | @Test 23 | fun verifyExecute() = runTest { 24 | // Arrange (Given) 25 | val pagingConfig = PagingConfig(pageSize = 20) 26 | val params = GetCharacters.Params(pagingConfig, hashMapOf()) 27 | 28 | // Act (When) 29 | getCharacters.invoke(params) 30 | 31 | // Assert (Then) 32 | coVerify { getCharacters.invoke(any()) } 33 | } 34 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/character/favorite/GetCharacterFavoritesTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.character.favorite 2 | 3 | import com.developersancho.repository.character.CharacterRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.coVerify 6 | import io.mockk.impl.annotations.InjectMockKs 7 | import io.mockk.impl.annotations.MockK 8 | import io.mockk.impl.annotations.SpyK 9 | import kotlinx.coroutines.flow.single 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetCharacterFavoritesTest : MockkUnitTest() { 14 | 15 | @MockK(relaxed = true) 16 | lateinit var repository: CharacterRepository 17 | 18 | @SpyK 19 | @InjectMockKs 20 | private lateinit var getFavorites: GetCharacterFavorites 21 | 22 | @Test 23 | fun verifyExecute() = runTest { 24 | // Arrange (Given) 25 | 26 | // Act (When) 27 | getFavorites.invoke(Unit) 28 | 29 | // Assert (Then) 30 | coVerify { getFavorites.invoke(Unit) } 31 | } 32 | 33 | @Test 34 | fun collectExecute() = runTest { 35 | // Arrange (Given) 36 | 37 | // Act (When) 38 | getFavorites.invoke(Unit).single() 39 | 40 | // Assert (Then) 41 | coVerify { repository.getFavoriteList() } 42 | } 43 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/episode/GetEpisodeDetailTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode 2 | 3 | import com.developersancho.repository.character.CharacterRepository 4 | import com.developersancho.repository.episode.EpisodeRepository 5 | import com.developersancho.testutils.MockkUnitTest 6 | import io.mockk.coVerify 7 | import io.mockk.impl.annotations.InjectMockKs 8 | import io.mockk.impl.annotations.RelaxedMockK 9 | import io.mockk.impl.annotations.SpyK 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetEpisodeDetailTest : MockkUnitTest() { 14 | 15 | @RelaxedMockK 16 | lateinit var charRepo: CharacterRepository 17 | 18 | @RelaxedMockK 19 | lateinit var episodeRepo: EpisodeRepository 20 | 21 | @SpyK 22 | @InjectMockKs 23 | private lateinit var getEpisodeDetail: GetEpisodeDetail 24 | 25 | @Test 26 | fun verifyExecute() = runTest { 27 | // Arrange (Given) 28 | val detailId = -1 29 | 30 | // Act (When) 31 | val params = GetEpisodeDetail.Params(detailId = detailId) 32 | getEpisodeDetail.invoke(params) 33 | 34 | // Assert (Then) 35 | coVerify { getEpisodeDetail.invoke(any()) } 36 | } 37 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/episode/GetEpisodesTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode 2 | 3 | import androidx.paging.PagingConfig 4 | import com.developersancho.repository.episode.EpisodeRepository 5 | import com.developersancho.testutils.MockkUnitTest 6 | import io.mockk.coVerify 7 | import io.mockk.impl.annotations.InjectMockKs 8 | import io.mockk.impl.annotations.MockK 9 | import io.mockk.impl.annotations.SpyK 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetEpisodesTest : MockkUnitTest() { 14 | @MockK(relaxed = true) 15 | lateinit var repository: EpisodeRepository 16 | 17 | @SpyK 18 | @InjectMockKs 19 | private lateinit var getEpisodes: GetEpisodes 20 | 21 | @Test 22 | fun verifyExecute() = runTest { 23 | // Arrange (Given) 24 | val pagingConfig = PagingConfig(pageSize = 20) 25 | val params = GetEpisodes.Params(pagingConfig, hashMapOf()) 26 | 27 | // Act (When) 28 | getEpisodes.invoke(params) 29 | 30 | // Assert (Then) 31 | coVerify { getEpisodes.invoke(any()) } 32 | } 33 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/episode/favorite/GetEpisodeFavoritesTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.episode.favorite 2 | 3 | import com.developersancho.repository.episode.EpisodeRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.coVerify 6 | import io.mockk.impl.annotations.InjectMockKs 7 | import io.mockk.impl.annotations.RelaxedMockK 8 | import io.mockk.impl.annotations.SpyK 9 | import kotlinx.coroutines.flow.single 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetEpisodeFavoritesTest : MockkUnitTest() { 14 | @RelaxedMockK 15 | lateinit var repository: EpisodeRepository 16 | 17 | @SpyK 18 | @InjectMockKs 19 | private lateinit var getFavorites: GetEpisodeFavorites 20 | 21 | @Test 22 | fun verifyExecute() = runTest { 23 | // Arrange (Given) 24 | 25 | // Act (When) 26 | getFavorites.invoke(Unit) 27 | 28 | // Assert (Then) 29 | coVerify { getFavorites.invoke(Unit) } 30 | } 31 | 32 | @Test 33 | fun collectExecute() = runTest { 34 | // Arrange (Given) 35 | 36 | // Act (When) 37 | getFavorites.invoke(Unit).single() 38 | 39 | // Assert (Then) 40 | coVerify { repository.getFavoriteList() } 41 | } 42 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/language/GetLanguageTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.language 2 | 3 | import com.developersancho.repository.langauge.LanguageRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.coVerify 6 | import io.mockk.impl.annotations.InjectMockKs 7 | import io.mockk.impl.annotations.RelaxedMockK 8 | import io.mockk.impl.annotations.SpyK 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Test 11 | 12 | class GetLanguageTest : MockkUnitTest() { 13 | @RelaxedMockK 14 | lateinit var repository: LanguageRepository 15 | 16 | @SpyK 17 | @InjectMockKs 18 | private lateinit var getLanguage: GetLanguage 19 | 20 | @Test 21 | fun verifyExecute() = runTest { 22 | // Arrange (Given) 23 | 24 | // Act (When) 25 | getLanguage.invoke(Unit) 26 | 27 | // Assert (Then) 28 | coVerify { repository.getLanguage } 29 | } 30 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/language/SaveLanguageTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.language 2 | 3 | import com.developersancho.repository.langauge.LanguageRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.coVerify 6 | import io.mockk.impl.annotations.InjectMockKs 7 | import io.mockk.impl.annotations.RelaxedMockK 8 | import io.mockk.impl.annotations.SpyK 9 | import kotlinx.coroutines.flow.single 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class SaveLanguageTest : MockkUnitTest() { 14 | @RelaxedMockK 15 | lateinit var repository: LanguageRepository 16 | 17 | @SpyK 18 | @InjectMockKs 19 | private lateinit var saveLanguage: SaveLanguage 20 | 21 | @Test 22 | fun verifyExecute() = runTest { 23 | // Arrange (Given) 24 | val params = SaveLanguage.Params("tr") 25 | 26 | // Act (When) 27 | saveLanguage.invoke(params) 28 | 29 | // Assert (Then) 30 | coVerify { saveLanguage.invoke(any()) } 31 | } 32 | 33 | @Test 34 | fun collectExecute() = runTest { 35 | // Arrange (Given) 36 | val params = SaveLanguage.Params("tr") 37 | 38 | // Act (When) 39 | saveLanguage.invoke(params).single() 40 | 41 | // Assert (Then) 42 | coVerify { repository.setLanguage(params.language) } 43 | } 44 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/location/GetLocationDetailTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location 2 | 3 | import com.developersancho.repository.character.CharacterRepository 4 | import com.developersancho.repository.location.LocationRepository 5 | import com.developersancho.testutils.MockkUnitTest 6 | import io.mockk.coVerify 7 | import io.mockk.impl.annotations.InjectMockKs 8 | import io.mockk.impl.annotations.RelaxedMockK 9 | import io.mockk.impl.annotations.SpyK 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetLocationDetailTest : MockkUnitTest() { 14 | @RelaxedMockK 15 | lateinit var charRepo: CharacterRepository 16 | 17 | @RelaxedMockK 18 | lateinit var locRepo: LocationRepository 19 | 20 | @SpyK 21 | @InjectMockKs 22 | private lateinit var getLocationDetail: GetLocationDetail 23 | 24 | @Test 25 | fun verifyExecute() = runTest { 26 | // Arrange (Given) 27 | val detailId = -1 28 | 29 | // Act (When) 30 | val params = GetLocationDetail.Params(detailId = detailId) 31 | getLocationDetail.invoke(params) 32 | 33 | // Assert (Then) 34 | coVerify { getLocationDetail.invoke(any()) } 35 | } 36 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/location/GetLocationsTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location 2 | 3 | import androidx.paging.PagingConfig 4 | import com.developersancho.repository.location.LocationRepository 5 | import com.developersancho.testutils.MockkUnitTest 6 | import io.mockk.coVerify 7 | import io.mockk.impl.annotations.InjectMockKs 8 | import io.mockk.impl.annotations.RelaxedMockK 9 | import io.mockk.impl.annotations.SpyK 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetLocationsTest : MockkUnitTest() { 14 | @RelaxedMockK 15 | lateinit var repository: LocationRepository 16 | 17 | @SpyK 18 | @InjectMockKs 19 | private lateinit var getLocations: GetLocations 20 | 21 | @Test 22 | fun verifyExecute() = runTest { 23 | // Arrange (Given) 24 | val pagingConfig = PagingConfig(pageSize = 20) 25 | val params = GetLocations.Params(pagingConfig, hashMapOf()) 26 | 27 | // Act (When) 28 | getLocations.invoke(params) 29 | 30 | // Assert (Then) 31 | coVerify { getLocations.invoke(any()) } 32 | } 33 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/location/favorite/GetLocationFavoritesTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.location.favorite 2 | 3 | import com.developersancho.repository.location.LocationRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.coVerify 6 | import io.mockk.impl.annotations.InjectMockKs 7 | import io.mockk.impl.annotations.RelaxedMockK 8 | import io.mockk.impl.annotations.SpyK 9 | import kotlinx.coroutines.flow.single 10 | import kotlinx.coroutines.test.runTest 11 | import org.junit.Test 12 | 13 | class GetLocationFavoritesTest : MockkUnitTest() { 14 | @RelaxedMockK 15 | lateinit var repository: LocationRepository 16 | 17 | @SpyK 18 | @InjectMockKs 19 | private lateinit var getFavorites: GetLocationFavorites 20 | 21 | @Test 22 | fun verifyExecute() = runTest { 23 | // Arrange (Given) 24 | 25 | // Act (When) 26 | getFavorites.invoke(Unit) 27 | 28 | // Assert (Then) 29 | coVerify { getFavorites.invoke(Unit) } 30 | } 31 | 32 | @Test 33 | fun collectExecute() = runTest { 34 | // Arrange (Given) 35 | 36 | // Act (When) 37 | getFavorites.invoke(Unit).single() 38 | 39 | // Assert (Then) 40 | coVerify { repository.getFavoriteList() } 41 | } 42 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/developersancho/domain/usecase/welcome/ReadOnBoardingTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.domain.usecase.welcome 2 | 3 | import com.developersancho.repository.welcome.WelcomeRepository 4 | import com.developersancho.testutils.MockkUnitTest 5 | import io.mockk.coVerify 6 | import io.mockk.impl.annotations.InjectMockKs 7 | import io.mockk.impl.annotations.RelaxedMockK 8 | import io.mockk.impl.annotations.SpyK 9 | import kotlinx.coroutines.test.runTest 10 | import org.junit.Test 11 | 12 | class ReadOnBoardingTest : MockkUnitTest() { 13 | @RelaxedMockK 14 | lateinit var repository: WelcomeRepository 15 | 16 | @SpyK 17 | @InjectMockKs 18 | private lateinit var readOnBoarding: ReadOnBoarding 19 | 20 | @Test 21 | fun verifyExecute() = runTest { 22 | // Arrange (Given) 23 | 24 | // Act (When) 25 | readOnBoarding.invoke(Unit) 26 | 27 | // Assert (Then) 28 | coVerify { repository.readOnBoardingState() } 29 | } 30 | } -------------------------------------------------------------------------------- /features/characters/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/characters/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-feature") 5 | id("commons.android-compose") 6 | id("commons.dagger-hilt") 7 | id("com.google.devtools.ksp") 8 | } 9 | 10 | ksp { 11 | arg("compose-destinations.mode", "navgraphs") 12 | arg("compose-destinations.moduleName", "characters") 13 | } 14 | 15 | dependencies { 16 | JETFRAMEWORK 17 | FRAMEWORK 18 | MODEL 19 | DOMAIN 20 | PROVIDER 21 | THEME 22 | COMPONENT 23 | 24 | addNavigationDependencies() 25 | 26 | // Dagger Hilt 27 | implementation(DaggerHiltLib.Android) 28 | kapt(DaggerHiltLib.Compiler) 29 | implementation(DaggerHiltLib.Compose) 30 | 31 | // Paging 32 | implementation(SupportLib.Paging) 33 | implementation(ComposeLib.Paging) 34 | } -------------------------------------------------------------------------------- /features/characters/src/androidTest/java/com/developersancho/characters/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.characters 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.characters.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /features/characters/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /features/characters/src/main/java/com/developersancho/characters/detail/CharacterDetailContract.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.characters.detail 2 | 3 | import com.developersancho.model.dto.character.CharacterDto 4 | 5 | data class CharacterDetailViewState( 6 | val character: CharacterDto? = null 7 | ) 8 | 9 | sealed class CharacterDetailEvent { 10 | data class LoadDetail(val id: Int) : CharacterDetailEvent() 11 | } -------------------------------------------------------------------------------- /features/characters/src/main/java/com/developersancho/characters/detail/CharacterDetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.characters.detail 2 | 3 | import com.developersancho.domain.usecase.character.GetCharacterDetail 4 | import com.developersancho.framework.base.mvi.BaseViewState 5 | import com.developersancho.framework.base.mvi.MviViewModel 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import javax.inject.Inject 8 | 9 | @HiltViewModel 10 | class CharacterDetailViewModel @Inject constructor( 11 | private val getCharacterDetail: GetCharacterDetail 12 | ) : MviViewModel, CharacterDetailEvent>() { 13 | 14 | override fun onTriggerEvent(eventType: CharacterDetailEvent) { 15 | when (eventType) { 16 | is CharacterDetailEvent.LoadDetail -> onLoadDetail(eventType.id) 17 | } 18 | } 19 | 20 | private fun onLoadDetail(id: Int) = safeLaunch { 21 | val params = GetCharacterDetail.Params(detailId = id) 22 | execute(getCharacterDetail(params)) { dto -> 23 | setState(BaseViewState.Data(CharacterDetailViewState(character = dto))) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /features/characters/src/main/java/com/developersancho/characters/list/CharactersContract.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.characters.list 2 | 3 | import androidx.paging.PagingData 4 | import com.developersancho.model.dto.character.CharacterDto 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.emptyFlow 7 | 8 | data class CharactersViewState( 9 | val pagedData: Flow> = emptyFlow(), 10 | val favorList: List = emptyList() 11 | ) 12 | 13 | sealed class CharactersEvent { 14 | object LoadCharacters : CharactersEvent() 15 | data class AddOrRemoveFavorite(val characterDto: CharacterDto) : CharactersEvent() 16 | object LoadFavorites : CharactersEvent() 17 | data class DeleteFavorite(val id: Int) : CharactersEvent() 18 | } -------------------------------------------------------------------------------- /features/characters/src/test/java/com/developersancho/characters/detail/CharacterDetailContractTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.characters.detail 2 | 3 | import com.developersancho.domain.mockdata.MockData 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class CharacterDetailContractTest { 8 | 9 | private lateinit var event: CharacterDetailEvent 10 | 11 | private lateinit var state: CharacterDetailViewState 12 | 13 | @Test 14 | fun verifyEventLoadDetail() { 15 | val characterId = 1 16 | event = CharacterDetailEvent.LoadDetail(characterId) 17 | 18 | val eventLoadDetail = event as CharacterDetailEvent.LoadDetail 19 | Assert.assertEquals(characterId, eventLoadDetail.id) 20 | } 21 | 22 | @Test 23 | fun verifyStateCharacterDetail() { 24 | val dto = MockData.getCharacterDto() 25 | state = CharacterDetailViewState(dto) 26 | 27 | Assert.assertEquals(dto, state.character) 28 | } 29 | } -------------------------------------------------------------------------------- /features/episodes/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/episodes/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-feature") 5 | id("commons.android-compose") 6 | id("commons.dagger-hilt") 7 | id("com.google.devtools.ksp") 8 | } 9 | 10 | ksp { 11 | arg("compose-destinations.mode", "navgraphs") 12 | arg("compose-destinations.moduleName", "episodes") 13 | } 14 | 15 | dependencies { 16 | JETFRAMEWORK 17 | FRAMEWORK 18 | MODEL 19 | DOMAIN 20 | PROVIDER 21 | THEME 22 | COMPONENT 23 | 24 | addNavigationDependencies() 25 | 26 | // Dagger Hilt 27 | implementation(DaggerHiltLib.Android) 28 | kapt(DaggerHiltLib.Compiler) 29 | implementation(DaggerHiltLib.Compose) 30 | 31 | // Paging 32 | implementation(SupportLib.Paging) 33 | implementation(ComposeLib.Paging) 34 | } -------------------------------------------------------------------------------- /features/episodes/src/androidTest/java/com/developersancho/episodes/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.episodes 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.episodes.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /features/episodes/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /features/episodes/src/main/java/com/developersancho/episodes/detail/EpisodeDetailContract.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.episodes.detail 2 | 3 | import com.developersancho.model.dto.episode.EpisodeDto 4 | 5 | data class EpisodeDetailState( 6 | val episode: EpisodeDto? = null 7 | ) 8 | 9 | sealed class EpisodeDetailEvent { 10 | data class LoadDetail(val id: Int) : EpisodeDetailEvent() 11 | } -------------------------------------------------------------------------------- /features/episodes/src/main/java/com/developersancho/episodes/detail/EpisodeDetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.episodes.detail 2 | 3 | import com.developersancho.domain.usecase.episode.GetEpisodeDetail 4 | import com.developersancho.framework.base.mvi.BaseViewState 5 | import com.developersancho.framework.base.mvi.MviViewModel 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import javax.inject.Inject 8 | 9 | @HiltViewModel 10 | class EpisodeDetailViewModel @Inject constructor( 11 | private val getEpisodeDetail: GetEpisodeDetail 12 | ) : MviViewModel, EpisodeDetailEvent>() { 13 | 14 | override fun onTriggerEvent(eventType: EpisodeDetailEvent) { 15 | when (eventType) { 16 | is EpisodeDetailEvent.LoadDetail -> onLoadDetail(eventType.id) 17 | } 18 | } 19 | 20 | private fun onLoadDetail(id: Int) = safeLaunch { 21 | val params = GetEpisodeDetail.Params(detailId = id) 22 | execute(getEpisodeDetail(params)) { dto -> 23 | setState(BaseViewState.Data(EpisodeDetailState(dto))) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /features/episodes/src/main/java/com/developersancho/episodes/list/EpisodesContract.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.episodes.list 2 | 3 | import androidx.paging.PagingData 4 | import com.developersancho.model.dto.episode.EpisodeDto 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.emptyFlow 7 | 8 | data class EpisodesState( 9 | val pagedData: Flow> = emptyFlow(), 10 | val favorList: List = emptyList() 11 | ) 12 | 13 | sealed class EpisodesEvent { 14 | object LoadEpisodes : EpisodesEvent() 15 | data class AddOrRemoveFavorite(val episode: EpisodeDto) : EpisodesEvent() 16 | object LoadFavorites : EpisodesEvent() 17 | data class DeleteFavorite(val id: Int) : EpisodesEvent() 18 | } -------------------------------------------------------------------------------- /features/episodes/src/test/java/com/developersancho/episodes/detail/EpisodeDetailContractTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.episodes.detail 2 | 3 | import com.developersancho.domain.mockdata.MockData 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class EpisodeDetailContractTest { 8 | private lateinit var event: EpisodeDetailEvent 9 | 10 | private lateinit var state: EpisodeDetailState 11 | 12 | @Test 13 | fun verifyEventLoadDetail() { 14 | val id = 1 15 | event = EpisodeDetailEvent.LoadDetail(id) 16 | 17 | val eventLoadDetail = event as EpisodeDetailEvent.LoadDetail 18 | Assert.assertEquals(id, eventLoadDetail.id) 19 | } 20 | 21 | @Test 22 | fun verifyState() { 23 | val dto = MockData.getEpisodeDto() 24 | state = EpisodeDetailState(dto) 25 | 26 | Assert.assertEquals(dto, state.episode) 27 | } 28 | } -------------------------------------------------------------------------------- /features/home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/home/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-feature") 5 | id("commons.android-compose") 6 | id("com.google.devtools.ksp") 7 | } 8 | 9 | ksp { 10 | arg("compose-destinations.mode", "navgraphs") 11 | arg("compose-destinations.moduleName", "home") 12 | } 13 | 14 | dependencies { 15 | PROVIDER 16 | THEME 17 | COMPONENT 18 | 19 | FEATURE_CHARACTERS 20 | FEATURE_EPISODES 21 | FEATURE_LOCATIONS 22 | FEATURE_SETTINGS 23 | 24 | addNavigationDependencies() 25 | } -------------------------------------------------------------------------------- /features/home/src/androidTest/java/com/developersancho/home/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.home 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.home.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /features/home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /features/home/src/main/java/com/developersancho/home/BottomBarHomeItem.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.home 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.AccountCircle 6 | import androidx.compose.material.icons.filled.Dashboard 7 | import androidx.compose.material.icons.filled.LocationCity 8 | import androidx.compose.material.icons.filled.Settings 9 | import androidx.compose.ui.graphics.vector.ImageVector 10 | import com.developersancho.theme.R 11 | 12 | enum class BottomBarHomeItem( 13 | @StringRes val title: Int, 14 | val icon: ImageVector 15 | ) { 16 | CHARACTERS( 17 | title = R.string.bottom_menu_characters, 18 | icon = Icons.Filled.AccountCircle 19 | ), 20 | EPISODES( 21 | title = R.string.bottom_menu_episodes, 22 | icon = Icons.Filled.Dashboard 23 | ), 24 | LOCATIONS( 25 | title = R.string.bottom_menu_locations, 26 | icon = Icons.Filled.LocationCity 27 | ), 28 | SETTINGS( 29 | title = R.string.bottom_menu_settings, 30 | icon = Icons.Filled.Settings 31 | ); 32 | } -------------------------------------------------------------------------------- /features/home/src/test/java/com/developersancho/home/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.home 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /features/locations/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/locations/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-feature") 5 | id("commons.android-compose") 6 | id("commons.dagger-hilt") 7 | id("com.google.devtools.ksp") 8 | } 9 | 10 | ksp { 11 | arg("compose-destinations.mode", "navgraphs") 12 | arg("compose-destinations.moduleName", "locations") 13 | } 14 | 15 | dependencies { 16 | JETFRAMEWORK 17 | FRAMEWORK 18 | MODEL 19 | DOMAIN 20 | PROVIDER 21 | THEME 22 | COMPONENT 23 | 24 | addNavigationDependencies() 25 | 26 | // Dagger Hilt 27 | implementation(DaggerHiltLib.Android) 28 | kapt(DaggerHiltLib.Compiler) 29 | implementation(DaggerHiltLib.Compose) 30 | 31 | // Paging 32 | implementation(SupportLib.Paging) 33 | implementation(ComposeLib.Paging) 34 | } -------------------------------------------------------------------------------- /features/locations/src/androidTest/java/com/developersancho/locations/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.locations 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.locations.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /features/locations/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /features/locations/src/main/java/com/developersancho/locations/detail/LocationDetailContract.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.locations.detail 2 | 3 | import com.developersancho.model.dto.location.LocationDto 4 | 5 | data class LocationDetailState( 6 | val location: LocationDto? = null 7 | ) 8 | 9 | sealed class LocationDetailEvent { 10 | data class LoadDetail(val id: Int) : LocationDetailEvent() 11 | } -------------------------------------------------------------------------------- /features/locations/src/main/java/com/developersancho/locations/detail/LocationDetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.locations.detail 2 | 3 | import com.developersancho.domain.usecase.location.GetLocationDetail 4 | import com.developersancho.framework.base.mvi.BaseViewState 5 | import com.developersancho.framework.base.mvi.MviViewModel 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import javax.inject.Inject 8 | 9 | @HiltViewModel 10 | class LocationDetailViewModel @Inject constructor( 11 | private val getLocationDetail: GetLocationDetail 12 | ) : MviViewModel, LocationDetailEvent>() { 13 | 14 | override fun onTriggerEvent(eventType: LocationDetailEvent) { 15 | when (eventType) { 16 | is LocationDetailEvent.LoadDetail -> onLoadDetail(eventType.id) 17 | } 18 | } 19 | 20 | private fun onLoadDetail(id: Int) = safeLaunch { 21 | val params = GetLocationDetail.Params(detailId = id) 22 | execute(getLocationDetail(params)) { dto -> 23 | setState(BaseViewState.Data(LocationDetailState(dto))) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /features/locations/src/main/java/com/developersancho/locations/list/LocationsContract.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.locations.list 2 | 3 | import androidx.paging.PagingData 4 | import com.developersancho.model.dto.location.LocationDto 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.emptyFlow 7 | 8 | data class LocationsState( 9 | val pagedData: Flow> = emptyFlow(), 10 | val favorList: List = emptyList() 11 | ) 12 | 13 | sealed class LocationsEvent { 14 | object LoadLocations : LocationsEvent() 15 | data class AddOrRemoveFavorite(val location: LocationDto) : LocationsEvent() 16 | object LoadFavorites : LocationsEvent() 17 | data class DeleteFavorite(val id: Int) : LocationsEvent() 18 | } -------------------------------------------------------------------------------- /features/locations/src/test/java/com/developersancho/locations/detail/LocationDetailContractTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.locations.detail 2 | 3 | import com.developersancho.domain.mockdata.MockData 4 | import org.junit.Assert 5 | import org.junit.Test 6 | 7 | class LocationDetailContractTest { 8 | private lateinit var event: LocationDetailEvent 9 | 10 | private lateinit var state: LocationDetailState 11 | 12 | @Test 13 | fun verifyEventLoadDetail() { 14 | val id = 1 15 | event = LocationDetailEvent.LoadDetail(id) 16 | 17 | val eventLoadDetail = event as LocationDetailEvent.LoadDetail 18 | Assert.assertEquals(id, eventLoadDetail.id) 19 | } 20 | 21 | @Test 22 | fun verifyState() { 23 | val dto = MockData.getLocationDto() 24 | state = LocationDetailState(dto) 25 | 26 | Assert.assertEquals(dto, state.location) 27 | } 28 | } -------------------------------------------------------------------------------- /features/settings/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/settings/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-feature") 5 | id("commons.android-compose") 6 | id("commons.dagger-hilt") 7 | id("com.google.devtools.ksp") 8 | } 9 | 10 | ksp { 11 | arg("compose-destinations.mode", "navgraphs") 12 | arg("compose-destinations.moduleName", "settings") 13 | } 14 | 15 | dependencies { 16 | JETFRAMEWORK 17 | FRAMEWORK 18 | DOMAIN 19 | MODEL 20 | PROVIDER 21 | THEME 22 | COMPONENT 23 | 24 | addNavigationDependencies() 25 | 26 | // Dagger Hilt 27 | implementation(DaggerHiltLib.Android) 28 | kapt(DaggerHiltLib.Compiler) 29 | implementation(DaggerHiltLib.Compose) 30 | } -------------------------------------------------------------------------------- /features/settings/src/androidTest/java/com/developersancho/settings/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.settings 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.settings.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /features/settings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /features/settings/src/main/java/com/developersancho/settings/SettingsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.settings 2 | 3 | import androidx.lifecycle.ViewModel 4 | import com.developersancho.provider.ThemeProvider 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import javax.inject.Inject 7 | 8 | @HiltViewModel 9 | class SettingsViewModel @Inject constructor( 10 | private val themeProvider: ThemeProvider 11 | ) : ViewModel() { 12 | fun isNightMode() = themeProvider.isNightMode() 13 | 14 | fun saveThemeMode(isChecked: Boolean) { 15 | themeProvider.theme = if (isChecked) { 16 | ThemeProvider.Theme.DARK 17 | } else { 18 | ThemeProvider.Theme.LIGHT 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /features/settings/src/test/java/com/developersancho/settings/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.settings 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /features/splash/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/splash/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-library") 5 | id("commons.dagger-hilt") 6 | } 7 | 8 | dependencies { 9 | FRAMEWORK 10 | DOMAIN 11 | 12 | addCommonDependencies() 13 | 14 | implementation(SupportLib.Splashscreen) 15 | // Dagger Hilt 16 | implementation(DaggerHiltLib.Android) 17 | kapt(DaggerHiltLib.Compiler) 18 | } -------------------------------------------------------------------------------- /features/splash/src/androidTest/java/com/developersancho/splash/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.splash 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.splash.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /features/splash/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /features/splash/src/main/java/com/developersancho/splash/StartViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.splash 2 | 3 | import com.developersancho.domain.usecase.welcome.ReadOnBoarding 4 | import com.developersancho.framework.base.mvvm.MvvmViewModel 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import kotlinx.coroutines.flow.MutableStateFlow 7 | import kotlinx.coroutines.flow.asStateFlow 8 | import javax.inject.Inject 9 | 10 | @HiltViewModel 11 | class StartViewModel @Inject constructor( 12 | private val readOnBoarding: ReadOnBoarding 13 | ) : MvvmViewModel() { 14 | 15 | private val _startWelcome = MutableStateFlow(false) 16 | val startWelcome = _startWelcome.asStateFlow() 17 | 18 | init { 19 | readOnBoardingState() 20 | } 21 | 22 | private fun readOnBoardingState() = safeLaunch { 23 | call(readOnBoarding(Unit)) { completed -> 24 | _startWelcome.value = !completed 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /features/splash/src/test/java/com/developersancho/splash/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.splash 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /features/welcome/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /features/welcome/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import extensions.* 2 | 3 | plugins { 4 | id("commons.android-feature") 5 | id("commons.android-compose") 6 | id("commons.dagger-hilt") 7 | id("com.google.devtools.ksp") 8 | } 9 | 10 | dependencies { 11 | JETFRAMEWORK 12 | FRAMEWORK 13 | DOMAIN 14 | PROVIDER 15 | THEME 16 | COMPONENT 17 | 18 | addNavigationDependencies() 19 | 20 | // Dagger Hilt 21 | implementation(DaggerHiltLib.Android) 22 | kapt(DaggerHiltLib.Compiler) 23 | implementation(DaggerHiltLib.Compose) 24 | } -------------------------------------------------------------------------------- /features/welcome/src/androidTest/java/com/developersancho/welcome/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.welcome 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.welcome.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /features/welcome/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /features/welcome/src/main/java/com/developersancho/welcome/environment/EnvironmentScreen.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.welcome.environment 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | fun EnvironmentScreen() { 7 | 8 | } -------------------------------------------------------------------------------- /features/welcome/src/main/java/com/developersancho/welcome/navgraph/WelcomeNavGraph.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.welcome.navgraph 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.compose.NavHost 5 | import androidx.navigation.compose.composable 6 | import androidx.navigation.compose.rememberNavController 7 | import com.developersancho.welcome.onboarding.OnBoardingScreen 8 | 9 | @Composable 10 | fun WelcomeNavGraph() { 11 | val navController = rememberNavController() 12 | NavHost( 13 | navController = navController, 14 | startDestination = WelcomeScreen.OnBoarding.route 15 | ) { 16 | composable(route = WelcomeScreen.OnBoarding.route) { 17 | OnBoardingScreen() 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /features/welcome/src/main/java/com/developersancho/welcome/navgraph/WelcomeScreen.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.welcome.navgraph 2 | 3 | sealed class WelcomeScreen(val route: String) { 4 | object OnBoarding : WelcomeScreen(route = "onBoarding_screen") 5 | object Environment : WelcomeScreen(route = "environment_screen") 6 | } -------------------------------------------------------------------------------- /features/welcome/src/main/java/com/developersancho/welcome/onboarding/OnBoardingPage.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.welcome.onboarding 2 | 3 | import androidx.annotation.DrawableRes 4 | import com.developersancho.theme.R 5 | 6 | sealed class OnBoardingPage( 7 | @DrawableRes 8 | val image: Int, 9 | val title: String, 10 | val description: String 11 | ) { 12 | object First : OnBoardingPage( 13 | image = R.drawable.intro_1, 14 | title = "Characters", 15 | description = "You can access the list of characters and details." 16 | ) 17 | 18 | object Second : OnBoardingPage( 19 | image = R.drawable.intro_2, 20 | title = "Episodes", 21 | description = "You can access the list of episodes and details." 22 | ) 23 | 24 | object Third : OnBoardingPage( 25 | image = R.drawable.intro_3, 26 | title = "Locations", 27 | description = "You can access the list of locations and details." 28 | ) 29 | } -------------------------------------------------------------------------------- /features/welcome/src/test/java/com/developersancho/welcome/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.welcome 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Apr 10 00:25:43 TRT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /libraries/framework/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/framework/src/androidTest/java/com/developersancho/framework/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.framework.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/app/AppInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.app 2 | 3 | interface AppInitializer { 4 | fun init(application: CoreApplication) 5 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/app/AppInitializerImpl.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.app 2 | 3 | class AppInitializerImpl(private vararg val initializers: AppInitializer) : AppInitializer { 4 | override fun init(application: CoreApplication) { 5 | initializers.forEach { 6 | it.init(application) 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/app/CoreApplication.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.app 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.Lifecycle 5 | import androidx.lifecycle.LifecycleEventObserver 6 | import androidx.lifecycle.LifecycleOwner 7 | 8 | abstract class CoreApplication : Application(), LifecycleEventObserver { 9 | var isAppInForeground: Boolean = true 10 | 11 | override fun onCreate() { 12 | super.onCreate() 13 | registerActivityLifecycleCallbacks(ActivityLifecycleCallback()) 14 | } 15 | 16 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { 17 | when (event) { 18 | Lifecycle.Event.ON_CREATE -> Unit 19 | Lifecycle.Event.ON_START -> onAppForegrounded() 20 | Lifecycle.Event.ON_RESUME -> Unit 21 | Lifecycle.Event.ON_PAUSE -> Unit 22 | Lifecycle.Event.ON_STOP -> onAppBackgrounded() 23 | Lifecycle.Event.ON_DESTROY -> Unit 24 | Lifecycle.Event.ON_ANY -> Unit 25 | } 26 | } 27 | 28 | open fun onAppBackgrounded() { 29 | isAppInForeground = false 30 | } 31 | 32 | open fun onAppForegrounded() { 33 | isAppInForeground = true 34 | } 35 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/app/FirebaseCrashlyticsReportTree.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.app 2 | 3 | import android.util.Log 4 | import com.google.firebase.crashlytics.ktx.crashlytics 5 | import com.google.firebase.ktx.Firebase 6 | import timber.log.Timber 7 | 8 | class FirebaseCrashlyticsReportTree : Timber.Tree() { 9 | 10 | init { 11 | Firebase.crashlytics.setCrashlyticsCollectionEnabled(true) 12 | } 13 | 14 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { 15 | if (priority == Log.ERROR) { // only for error level 16 | /** 17 | * THROW FIREBASE CRASHLYTICS EXCEPTION 18 | * **/ 19 | // if google service firebase or huawei 20 | // val exception = t ?: Exception(message) 21 | // Firebase.crashlytics.log(message) 22 | // Firebase.crashlytics.setCustomKey(tag.toString(), message) 23 | // Firebase.crashlytics.recordException(exception) 24 | with(Firebase.crashlytics) { 25 | // optional: setCustomKey("CUSTOME_TAG", any) 26 | //recordException(it) 27 | } 28 | } 29 | } 30 | } 31 | 32 | //// Configure in Application() when app starts 33 | //Timber.plant(CrashlyticsReportTree()) -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/app/NetworkConfig.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.app 2 | 3 | abstract class NetworkConfig { 4 | abstract fun baseUrl(): String 5 | 6 | abstract fun timeOut(): Long 7 | 8 | open fun isDev() = false 9 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/app/TimberInitializer.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.app 2 | 3 | import com.developersancho.framework.platform.isHMS 4 | import timber.log.Timber 5 | 6 | class TimberInitializer(private val isDev: Boolean) : AppInitializer { 7 | override fun init(application: CoreApplication) { 8 | if (isDev) { 9 | Timber.plant(Timber.DebugTree()) 10 | } else { 11 | if (application.applicationContext.isHMS()) { 12 | Timber.plant(FirebaseCrashlyticsReportTree()) 13 | } else { 14 | Timber.plant(FirebaseCrashlyticsReportTree()) 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/mvi/BaseViewState.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.mvi 2 | 3 | sealed interface BaseViewState { 4 | object Loading : BaseViewState 5 | object Empty : BaseViewState 6 | data class Data(val value: T) : BaseViewState 7 | data class Error(val throwable: Throwable) : BaseViewState 8 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/base/mvi/MviViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.base.mvi 2 | 3 | import com.developersancho.framework.base.mvvm.MvvmViewModel 4 | import kotlinx.coroutines.flow.MutableStateFlow 5 | import kotlinx.coroutines.flow.asStateFlow 6 | 7 | abstract class MviViewModel, EVENT> : MvvmViewModel() { 8 | 9 | private val _uiState = MutableStateFlow>(BaseViewState.Empty) 10 | val uiState = _uiState.asStateFlow() 11 | 12 | abstract fun onTriggerEvent(eventType: EVENT) 13 | 14 | protected fun setState(state: STATE) = safeLaunch { 15 | _uiState.emit(state) 16 | } 17 | 18 | override fun startLoading() { 19 | super.startLoading() 20 | _uiState.value = BaseViewState.Loading 21 | } 22 | 23 | override fun handleError(exception: Throwable) { 24 | super.handleError(exception) 25 | _uiState.value = BaseViewState.Error(exception) 26 | } 27 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/extension/AnyExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.extension 2 | 3 | val Any.classTag: String get() = this.javaClass.canonicalName.orEmpty() 4 | 5 | val Any.methodTag get() = classTag + object : Any() {}.javaClass.enclosingMethod?.name 6 | 7 | fun Any.hashCodeAsString(): String { 8 | return hashCode().toString() 9 | } 10 | 11 | inline fun Any.cast(): T { 12 | return this as T 13 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/extension/MoshiExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.extension 2 | 3 | import com.squareup.moshi.JsonAdapter 4 | import com.squareup.moshi.Moshi 5 | import com.squareup.moshi.Types 6 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 7 | 8 | val moshi: Moshi = Moshi.Builder() 9 | .addLast(KotlinJsonAdapterFactory()) 10 | .build() 11 | 12 | inline fun String.fromJson(): T? { 13 | return try { 14 | val jsonAdapter = moshi.adapter(T::class.java) 15 | jsonAdapter.fromJson(this) 16 | } catch (ex: Exception) { 17 | null 18 | } 19 | } 20 | 21 | inline fun String.fromJsonList(): List? { 22 | return try { 23 | val type = Types.newParameterizedType(MutableList::class.java, T::class.java) 24 | val jsonAdapter: JsonAdapter> = moshi.adapter(type) 25 | jsonAdapter.fromJson(this) 26 | } catch (ex: Exception) { 27 | null 28 | } 29 | } 30 | 31 | inline fun T.toJson(): String { 32 | return try { 33 | val jsonAdapter = moshi.adapter(T::class.java).serializeNulls().lenient() 34 | jsonAdapter.toJson(this) 35 | } catch (ex: Exception) { 36 | "" 37 | } 38 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/extension/ToastExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.extension 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.widget.Toast 6 | import androidx.fragment.app.Fragment 7 | 8 | fun Activity.toast(message: String) { 9 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 10 | } 11 | 12 | fun Activity.toastLong(message: String) { 13 | Toast.makeText(this, message, Toast.LENGTH_LONG).show() 14 | } 15 | 16 | fun Fragment.toast(message: String) { 17 | Toast.makeText(context, message, Toast.LENGTH_SHORT).show() 18 | } 19 | 20 | fun Fragment.toastLong(message: String) { 21 | Toast.makeText(context, message, Toast.LENGTH_LONG).show() 22 | } 23 | 24 | fun Context.toast(message: String) { 25 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show() 26 | } 27 | 28 | fun Context.toastLong(message: String) { 29 | Toast.makeText(this, message, Toast.LENGTH_LONG).show() 30 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/extension/VariableExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.extension 2 | 3 | import java.math.BigDecimal 4 | 5 | fun Int?.orZero(): Int = this ?: 0 6 | fun Double?.orZero(): Double = this ?: 0.0 7 | fun Long?.orZero(): Long = this ?: 0L 8 | fun BigDecimal?.orZero(): BigDecimal = this ?: BigDecimal.ZERO 9 | 10 | fun Int?.orOne(): Int = this ?: 1 11 | fun Double?.orOne(): Double = this ?: 1.0 12 | fun Long?.orOne(): Long = this ?: 1L 13 | fun BigDecimal?.orOne(): BigDecimal = this ?: BigDecimal.ONE 14 | 15 | fun Int.greaterThan(number: Int): Boolean = this > number 16 | 17 | fun Boolean?.orFalse(): Boolean = this ?: false 18 | 19 | fun Long?.isNull(): Boolean = this == null 20 | fun Int?.isNull(): Boolean = this == null 21 | fun Double?.isNull(): Boolean = this == null 22 | fun BigDecimal?.isNull(): Boolean = this == null 23 | fun Boolean?.isNull(): Boolean = this == null 24 | fun Boolean?.isNotNull(): Boolean = this != null 25 | 26 | fun Boolean.toFloat() = if (this) 1f else 0f -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/extension/ViewExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.extension 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | 7 | fun ViewGroup.inflate(layoutRes: Int): View { 8 | return LayoutInflater.from(context).inflate(layoutRes, this, false) 9 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/network/ApiCallExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.network 2 | 3 | suspend fun apiCall(call: suspend () -> T): DataState { 4 | return try { 5 | val response = call() 6 | DataState.Success(response) 7 | } catch (ex: Throwable) { 8 | DataState.Error(ex.handleThrowable()) 9 | } 10 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/network/environment/Environment.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.network.environment 2 | 3 | import androidx.annotation.IntDef 4 | import com.developersancho.framework.network.environment.Environment.Companion.DEVELOPMENT 5 | import com.developersancho.framework.network.environment.Environment.Companion.PRODUCTION 6 | import com.developersancho.framework.network.environment.Environment.Companion.STAGING 7 | 8 | @IntDef(DEVELOPMENT, STAGING, PRODUCTION) 9 | @Retention(AnnotationRetention.SOURCE) 10 | annotation class Environment { 11 | companion object { 12 | const val DEVELOPMENT = 1 13 | const val STAGING = 2 14 | const val PRODUCTION = 3 15 | } 16 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/network/interceptor/HttpRequestInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.network.interceptor 2 | 3 | import okhttp3.Interceptor 4 | import okhttp3.Response 5 | import timber.log.Timber 6 | 7 | class HttpRequestInterceptor : Interceptor { 8 | override fun intercept(chain: Interceptor.Chain): Response { 9 | val originalRequest = chain.request() 10 | val request = originalRequest.newBuilder().url(originalRequest.url).build() 11 | Timber.d(request.toString()) 12 | return chain.proceed(request) 13 | } 14 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/network/sslpinning/SSLPinning.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.network.sslpinning 2 | 3 | import android.content.Context 4 | import androidx.annotation.RawRes 5 | import java.security.cert.Certificate 6 | import javax.net.ssl.SSLSocketFactory 7 | import javax.net.ssl.X509TrustManager 8 | 9 | interface SSLPinning { 10 | fun initSSLWithCertificateType( 11 | context: Context, 12 | @RawRes rawResourceId: Int, 13 | password: String, 14 | typeOfCertificate: String 15 | ) 16 | 17 | fun initSSLWithCertificate(context: Context, @RawRes rawResourceId: Int) 18 | 19 | fun getTrustManager(): X509TrustManager 20 | 21 | fun getCertificate(): Certificate? 22 | 23 | fun getSSLSocketFactory(): SSLSocketFactory 24 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/pref/CacheManagerExtension.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.pref 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import androidx.security.crypto.EncryptedSharedPreferences 6 | import androidx.security.crypto.MasterKey 7 | 8 | /** 9 | * @param fileName Name of the Shared Preferences 10 | * @return SharedPreferences 11 | */ 12 | fun Context.getPrefs(fileName: String? = null): SharedPreferences { 13 | val masterKey = MasterKey.Builder(this) 14 | .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) 15 | .build() 16 | 17 | val name = if (fileName.isNullOrEmpty()) { 18 | getDefaultSharedPrefName() 19 | } else { 20 | fileName.toString() 21 | } 22 | 23 | return EncryptedSharedPreferences.create( 24 | this, 25 | name, 26 | masterKey, 27 | EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, 28 | EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM 29 | ) 30 | } 31 | 32 | /** 33 | * @return Default SharedPreferences filename 34 | */ 35 | fun Context.getDefaultSharedPrefName(): String { 36 | return this.packageName + "_pref" 37 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/room/converter/StringConverter.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.room.converter 2 | 3 | import androidx.room.TypeConverter 4 | import com.developersancho.framework.extension.fromJson 5 | import com.developersancho.framework.extension.toJson 6 | 7 | class StringConverter { 8 | @TypeConverter 9 | fun toListOfStrings(stringValue: String): List? { 10 | return stringValue.fromJson() 11 | } 12 | 13 | @TypeConverter 14 | fun fromListOfStrings(listOfString: List?): String { 15 | return listOfString.toJson() 16 | } 17 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/room/dao/BaseDao.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.room.dao 2 | 3 | import androidx.room.* 4 | 5 | @Dao 6 | interface BaseDao { 7 | @Insert(onConflict = OnConflictStrategy.REPLACE) 8 | suspend fun insert(data: T) 9 | 10 | @Insert(onConflict = OnConflictStrategy.REPLACE) 11 | suspend fun insert(data: List) 12 | 13 | @Update 14 | suspend fun update(data: T) 15 | 16 | @Delete 17 | suspend fun delete(data: T) 18 | } -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/usecase/DataStateUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.usecase 2 | 3 | import com.developersancho.framework.network.DataState 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.flow.FlowCollector 6 | import kotlinx.coroutines.flow.flow 7 | import kotlinx.coroutines.flow.flowOn 8 | 9 | abstract class DataStateUseCase where ReturnType : Any { 10 | 11 | protected abstract suspend fun FlowCollector>.execute(params: Params) 12 | 13 | suspend operator fun invoke(params: Params) = flow { 14 | execute(params) 15 | }.flowOn(Dispatchers.IO) 16 | } 17 | -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/usecase/FlowPagingUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.usecase 2 | 3 | import androidx.paging.PagingData 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.flow.Flow 6 | import kotlinx.coroutines.flow.flowOn 7 | 8 | abstract class FlowPagingUseCase where ReturnType : Any { 9 | 10 | protected abstract fun execute(params: Params): Flow> 11 | 12 | operator fun invoke(params: Params): Flow> = execute(params) 13 | .flowOn(Dispatchers.IO) 14 | } 15 | -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/usecase/LocalUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.usecase 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.FlowCollector 5 | import kotlinx.coroutines.flow.flow 6 | import kotlinx.coroutines.flow.flowOn 7 | 8 | abstract class LocalUseCase where ReturnType : Any { 9 | 10 | protected abstract suspend fun FlowCollector.execute(params: Params) 11 | 12 | suspend operator fun invoke(params: Params) = flow { 13 | execute(params) 14 | }.flowOn(Dispatchers.IO) 15 | } 16 | -------------------------------------------------------------------------------- /libraries/framework/src/main/java/com/developersancho/framework/usecase/ReturnUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework.usecase 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.flow.Flow 5 | import kotlinx.coroutines.flow.flowOn 6 | 7 | abstract class ReturnUseCase where ReturnType : Any { 8 | 9 | protected abstract suspend fun execute(params: Params): Flow 10 | 11 | suspend operator fun invoke(params: Params): Flow = execute(params) 12 | .flowOn(Dispatchers.IO) 13 | } -------------------------------------------------------------------------------- /libraries/framework/src/test/java/com/developersancho/framework/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.framework 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /libraries/jetframework/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/jetframework/src/androidTest/java/com/developersancho/jetframework/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetframework 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.jetframework.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /libraries/jetframework/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/jetframework/src/main/java/com/developersancho/jetframework/FirebaseAnalytic.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetframework 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.SideEffect 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.platform.LocalContext 8 | import com.google.firebase.analytics.FirebaseAnalytics 9 | 10 | data class User( 11 | val userType: String 12 | ) 13 | 14 | @SuppressLint("MissingPermission") 15 | @Composable 16 | fun rememberAnalytics(user: User): FirebaseAnalytics { 17 | val context = LocalContext.current 18 | val analytics: FirebaseAnalytics = remember { 19 | FirebaseAnalytics.getInstance(context) 20 | } 21 | 22 | // On every successful composition, update FirebaseAnalytics with 23 | // the userType from the current User, ensuring that future analytics 24 | // events have this metadata attached 25 | SideEffect { 26 | analytics.setUserProperty("userType", user.userType) 27 | } 28 | return analytics 29 | } -------------------------------------------------------------------------------- /libraries/jetframework/src/main/java/com/developersancho/jetframework/LanguageHelper.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetframework 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.platform.LocalConfiguration 5 | import androidx.compose.ui.platform.LocalContext 6 | import java.util.* 7 | 8 | @Suppress("DEPRECATION") 9 | @Composable 10 | fun SetLanguage(languageCode: String) { 11 | val locale = Locale(languageCode) 12 | val configuration = LocalConfiguration.current 13 | configuration.setLocale(locale) 14 | val resources = LocalContext.current.resources 15 | resources.updateConfiguration(configuration, resources.displayMetrics) 16 | } -------------------------------------------------------------------------------- /libraries/jetframework/src/main/java/com/developersancho/jetframework/SystemUiController.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetframework 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.SideEffect 5 | import androidx.compose.ui.graphics.Color 6 | import com.google.accompanist.systemuicontroller.SystemUiController 7 | 8 | @Composable 9 | fun SetupSystemUi( 10 | systemUiController: SystemUiController, 11 | systemColor: Color 12 | ) { 13 | SideEffect { 14 | systemUiController.setSystemBarsColor(color = systemColor) 15 | } 16 | } -------------------------------------------------------------------------------- /libraries/jetframework/src/main/java/com/developersancho/jetframework/htmltext/CharacterStyle.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetframework.htmltext 2 | 3 | import android.text.style.ForegroundColorSpan 4 | import android.text.style.StrikethroughSpan 5 | import android.text.style.UnderlineSpan 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.text.SpanStyle 8 | import androidx.compose.ui.text.style.TextDecoration 9 | 10 | internal fun UnderlineSpan.spanStyle(): SpanStyle = 11 | SpanStyle(textDecoration = TextDecoration.Underline) 12 | 13 | internal fun ForegroundColorSpan.spanStyle(): SpanStyle = 14 | SpanStyle(color = Color(foregroundColor)) 15 | 16 | internal fun StrikethroughSpan.spanStyle(): SpanStyle = 17 | SpanStyle(textDecoration = TextDecoration.LineThrough) -------------------------------------------------------------------------------- /libraries/jetframework/src/test/java/com/developersancho/jetframework/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.jetframework 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /libraries/testutils/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /libraries/testutils/src/androidTest/java/com/developersancho/testutils/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.testutils 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.developersancho.testutils.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /libraries/testutils/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /libraries/testutils/src/main/java/com/developersancho/testutils/MockkUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.testutils 2 | 3 | import io.mockk.MockKAnnotations 4 | import io.mockk.clearAllMocks 5 | import io.mockk.unmockkAll 6 | import org.junit.After 7 | import org.junit.Before 8 | import org.junit.Rule 9 | 10 | open class MockkUnitTest { 11 | 12 | open fun onCreate() {} 13 | 14 | open fun onDestroy() {} 15 | 16 | @get:Rule 17 | var testCoroutineRule = TestCoroutineRule() 18 | 19 | @Before 20 | fun setUp() { 21 | MockKAnnotations.init(this) 22 | onCreate() 23 | } 24 | 25 | @After 26 | fun tearDown() { 27 | onDestroy() 28 | unmockkAll() 29 | clearAllMocks() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /libraries/testutils/src/main/java/com/developersancho/testutils/TestCoroutineRule.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.testutils 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.test.* 5 | import org.junit.rules.TestRule 6 | import org.junit.runner.Description 7 | import org.junit.runners.model.Statement 8 | 9 | class TestCoroutineRule : TestRule { 10 | 11 | private val testCoroutineDispatcher = UnconfinedTestDispatcher() 12 | 13 | val testCoroutineScope = TestScope(testCoroutineDispatcher) 14 | 15 | override fun apply(base: Statement, description: Description?) = object : Statement() { 16 | @Throws(Throwable::class) 17 | override fun evaluate() { 18 | Dispatchers.setMain(testCoroutineDispatcher) 19 | 20 | base.evaluate() 21 | 22 | Dispatchers.resetMain() 23 | } 24 | } 25 | 26 | fun runTest(block: suspend TestScope.() -> Unit) = 27 | testCoroutineScope.runTest { block() } 28 | } 29 | -------------------------------------------------------------------------------- /libraries/testutils/src/main/java/com/developersancho/testutils/TestRobolectric.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, developersancho 3 | * All rights reserved. 4 | */ 5 | package com.developersancho.testutils 6 | 7 | import android.app.Application 8 | import android.content.Context 9 | import android.os.Build 10 | import androidx.test.core.app.ApplicationProvider 11 | import org.junit.runner.RunWith 12 | import org.robolectric.RobolectricTestRunner 13 | import org.robolectric.annotation.Config 14 | 15 | @RunWith(RobolectricTestRunner::class) 16 | @Config( 17 | manifest = "AndroidManifest.xml", 18 | application = TestRobolectric.ApplicationStub::class, 19 | sdk = [Build.VERSION_CODES.M] 20 | ) 21 | open class TestRobolectric : MockkUnitTest() { 22 | 23 | protected val application: Application by lazy { 24 | ApplicationProvider.getApplicationContext() 25 | } 26 | protected val context: Context by lazy { 27 | application 28 | } 29 | 30 | internal class ApplicationStub : Application() 31 | } 32 | -------------------------------------------------------------------------------- /libraries/testutils/src/test/java/com/developersancho/testutils/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.developersancho.testutils 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven("https://jitpack.io") 7 | maven("https://plugins.gradle.org/m2/") 8 | maven("https://developer.huawei.com/repo/") 9 | } 10 | } 11 | dependencyResolutionManagement { 12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 13 | repositories { 14 | google() 15 | mavenCentral() 16 | maven("https://jitpack.io") 17 | maven("https://plugins.gradle.org/m2/") 18 | maven("https://developer.huawei.com/repo/") 19 | } 20 | } 21 | rootProject.name = "JetRortyV2" 22 | include(":app") 23 | include(":domain") 24 | include(":data:model") 25 | include(":data:local") 26 | include(":data:remote") 27 | include(":data:repository") 28 | include(":common:provider") 29 | include(":common:component") 30 | include(":common:theme") 31 | include(":libraries:framework") 32 | include(":libraries:jetframework") 33 | include(":libraries:testutils") 34 | include(":features:characters") 35 | include(":features:episodes") 36 | include(":features:locations") 37 | include(":features:splash") 38 | include(":features:welcome") 39 | include(":features:settings") 40 | include(":features:home") 41 | -------------------------------------------------------------------------------- /signing/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/signing/debug.keystore -------------------------------------------------------------------------------- /signing/jetrorty-release.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersancho/JetRorty.Android/53d96f038e70712387f26a0099949c26a2aa2311/signing/jetrorty-release.jks -------------------------------------------------------------------------------- /signing/release.signing.properties: -------------------------------------------------------------------------------- 1 | storeFile = signing/jetrorty-release.jks 2 | storePassword = 123456 3 | keyPassword = 123456 4 | keyAlias = jetrorty --------------------------------------------------------------------------------