├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── android ├── annotation │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── com │ │ └── bael │ │ └── dads │ │ └── annotation │ │ └── RenderWith.kt ├── app │ ├── .gitignore │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ ├── google-services.json │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── bael │ │ │ │ └── dads │ │ │ │ └── DadsDebugApplication.kt │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── bael │ │ │ │ └── dads │ │ │ │ ├── DadsApplication.kt │ │ │ │ ├── activity │ │ │ │ └── MainActivity.kt │ │ │ │ └── di │ │ │ │ └── module │ │ │ │ └── MainActivityModule.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── font │ │ │ └── product_sans.ttf │ │ │ ├── layout │ │ │ └── activity_main.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── provider_paths.xml │ │ └── release │ │ ├── AndroidManifest.xml │ │ ├── google-services.enc │ │ └── kotlin │ │ └── com │ │ └── bael │ │ └── dads │ │ └── DadsReleaseApplication.kt ├── feature │ └── home │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ ├── androidTest │ │ └── kotlin │ │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── feature │ │ │ └── home │ │ │ ├── screen │ │ │ ├── home │ │ │ │ └── UITest.kt │ │ │ └── seen │ │ │ │ └── UITest.kt │ │ │ ├── sheet │ │ │ ├── detail │ │ │ │ └── UITest.kt │ │ │ ├── settings │ │ │ │ └── UITest.kt │ │ │ └── sharepreview │ │ │ │ └── UITest.kt │ │ │ └── worker │ │ │ ├── FetchDadJokeFeedWorkerTest.kt │ │ │ └── factory │ │ │ └── FakeFetchDadJokeFeedWorkerFactory.kt │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── anim_reminder.json │ │ ├── kotlin │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── feature │ │ │ └── home │ │ │ ├── adapter │ │ │ ├── DadJokeFeedAdapter.kt │ │ │ ├── SeenDadJokeAdapter.kt │ │ │ ├── cell │ │ │ │ ├── DadJokeFeedCell.kt │ │ │ │ └── SeenDadJokeCell.kt │ │ │ └── diffcallback │ │ │ │ └── DadJokeDiffCallback.kt │ │ │ ├── animation │ │ │ └── Animation.kt │ │ │ ├── di │ │ │ └── module │ │ │ │ ├── screen │ │ │ │ ├── FeedScreenModule.kt │ │ │ │ ├── HomeScreenModule.kt │ │ │ │ └── SeenScreenModule.kt │ │ │ │ └── sheet │ │ │ │ ├── DetailSheetModule.kt │ │ │ │ ├── SettingsSheetModule.kt │ │ │ │ └── SharePreviewSheetModule.kt │ │ │ ├── notification │ │ │ ├── NewFeedReminderNotification.kt │ │ │ └── factory │ │ │ │ └── NewFeedReminderNotificationFactory.kt │ │ │ ├── screen │ │ │ ├── feed │ │ │ │ ├── Event.kt │ │ │ │ ├── Renderer.kt │ │ │ │ ├── State.kt │ │ │ │ ├── UI.kt │ │ │ │ └── ViewModel.kt │ │ │ ├── home │ │ │ │ ├── Event.kt │ │ │ │ ├── Renderer.kt │ │ │ │ ├── State.kt │ │ │ │ ├── UI.kt │ │ │ │ └── ViewModel.kt │ │ │ └── seen │ │ │ │ ├── Event.kt │ │ │ │ ├── Renderer.kt │ │ │ │ ├── State.kt │ │ │ │ ├── UI.kt │ │ │ │ └── ViewModel.kt │ │ │ ├── sheet │ │ │ ├── detail │ │ │ │ ├── Event.kt │ │ │ │ ├── Renderer.kt │ │ │ │ ├── State.kt │ │ │ │ ├── UI.kt │ │ │ │ └── ViewModel.kt │ │ │ ├── settings │ │ │ │ ├── Event.kt │ │ │ │ ├── Renderer.kt │ │ │ │ ├── State.kt │ │ │ │ ├── UI.kt │ │ │ │ └── ViewModel.kt │ │ │ └── sharepreview │ │ │ │ ├── Event.kt │ │ │ │ ├── Renderer.kt │ │ │ │ ├── State.kt │ │ │ │ ├── UI.kt │ │ │ │ └── ViewModel.kt │ │ │ └── worker │ │ │ └── FetchDadJokeFeedWorker.kt │ │ └── res │ │ ├── drawable │ │ ├── bg_border.xml │ │ ├── bg_round.xml │ │ ├── ic_clear.xml │ │ ├── ic_feed.xml │ │ ├── ic_like.xml │ │ ├── ic_like_outline.xml │ │ ├── ic_logo.xml │ │ ├── ic_no_internet.xml │ │ ├── ic_search.xml │ │ ├── ic_seen.xml │ │ ├── ic_settings.xml │ │ └── ic_share.xml │ │ ├── layout │ │ ├── cell_feed.xml │ │ ├── cell_seen.xml │ │ ├── item_group_settings.xml │ │ ├── item_menu.xml │ │ ├── item_setting.xml │ │ ├── screen_feed.xml │ │ ├── screen_home.xml │ │ ├── screen_seen.xml │ │ ├── sheet_detail.xml │ │ ├── sheet_settings.xml │ │ ├── sheet_share_preview.xml │ │ └── toolbar_home.xml │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── values │ │ └── strings.xml │ │ └── xml │ │ ├── scene_favorite_menu.xml │ │ └── scene_feed.xml ├── library │ ├── instrumentation │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── bael │ │ │ │ └── dads │ │ │ │ └── library │ │ │ │ └── instrumentation │ │ │ │ ├── activity │ │ │ │ └── MainTestActivity.kt │ │ │ │ ├── di │ │ │ │ └── module │ │ │ │ │ └── MainTestActivityModule.kt │ │ │ │ ├── fragment │ │ │ │ ├── BaseFragmentTest.kt │ │ │ │ └── FragmentExt.kt │ │ │ │ ├── matcher │ │ │ │ └── MatcherParams.kt │ │ │ │ ├── runner │ │ │ │ └── HiltTestRunner.kt │ │ │ │ ├── sheet │ │ │ │ └── BaseSheetTest.kt │ │ │ │ ├── ui │ │ │ │ └── BaseUITest.kt │ │ │ │ └── worker │ │ │ │ └── BaseWorkerTest.kt │ │ │ └── res │ │ │ └── values │ │ │ └── styles.xml │ ├── preference │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── library │ │ │ └── preference │ │ │ ├── DataStorePreference.kt │ │ │ ├── Preference.kt │ │ │ └── di │ │ │ └── module │ │ │ └── PreferenceModule.kt │ ├── preference_test │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── library │ │ │ └── preference │ │ │ └── test │ │ │ ├── FakePreference.kt │ │ │ └── di │ │ │ └── module │ │ │ └── PreferenceTestModule.kt │ ├── presentation │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ ├── anim_empty.json │ │ │ ├── anim_error.json │ │ │ ├── anim_loading.json │ │ │ └── anim_no_internet.json │ │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── bael │ │ │ │ └── dads │ │ │ │ └── library │ │ │ │ └── presentation │ │ │ │ ├── di │ │ │ │ └── qualifier │ │ │ │ │ └── ActivityNameQualifier.kt │ │ │ │ ├── event │ │ │ │ └── EventStore.kt │ │ │ │ ├── ext │ │ │ │ ├── ContextExt.kt │ │ │ │ ├── DrawableExt.kt │ │ │ │ ├── FragmentExt.kt │ │ │ │ ├── SoftKeyboardExt.kt │ │ │ │ ├── StateExt.kt │ │ │ │ └── StringExt.kt │ │ │ │ ├── fragment │ │ │ │ └── BaseFragment.kt │ │ │ │ ├── notification │ │ │ │ ├── NotificationConfiguration.kt │ │ │ │ └── NotificationPublisher.kt │ │ │ │ ├── renderer │ │ │ │ ├── BaseRenderExecutor.kt │ │ │ │ ├── BaseRenderer.kt │ │ │ │ └── RendererInitializer.kt │ │ │ │ ├── sheet │ │ │ │ └── BaseSheet.kt │ │ │ │ ├── state │ │ │ │ ├── BaseState.kt │ │ │ │ └── StateStore.kt │ │ │ │ ├── store │ │ │ │ └── Store.kt │ │ │ │ ├── viewmodel │ │ │ │ └── BaseViewModel.kt │ │ │ │ └── widget │ │ │ │ ├── animation │ │ │ │ └── Animation.kt │ │ │ │ ├── listener │ │ │ │ ├── OnPageSnapListener.kt │ │ │ │ └── OnTextChangedListener.kt │ │ │ │ ├── recyclerview │ │ │ │ └── adapter │ │ │ │ │ ├── BaseAdapter.kt │ │ │ │ │ ├── BaseListAdapter.kt │ │ │ │ │ ├── LiveListAdapter.kt │ │ │ │ │ ├── ResponseStateAdapter.kt │ │ │ │ │ ├── SingleItemAdapter.kt │ │ │ │ │ ├── cell │ │ │ │ │ ├── BaseCell.kt │ │ │ │ │ └── ResponseStateCell.kt │ │ │ │ │ └── data │ │ │ │ │ └── ResponseState.kt │ │ │ │ ├── tab │ │ │ │ ├── adapter │ │ │ │ │ └── BottomTabAdapter.kt │ │ │ │ └── data │ │ │ │ │ └── BottomTab.kt │ │ │ │ └── viewpager │ │ │ │ ├── NestedScrollableHost.kt │ │ │ │ ├── adapter │ │ │ │ └── ScreenPagerAdapter.kt │ │ │ │ └── transformer │ │ │ │ └── StackTransformer.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ ├── bg_circle.xml │ │ │ └── bg_sheet.xml │ │ │ ├── layout │ │ │ ├── item_bottom_tab.xml │ │ │ └── item_response_state.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ └── scene_bottom_tab.xml │ ├── threading │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── library │ │ │ └── threading │ │ │ ├── DefaultThread.kt │ │ │ ├── Thread.kt │ │ │ └── di │ │ │ └── module │ │ │ └── ThreadingModule.kt │ ├── threading_test │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── library │ │ │ └── threading │ │ │ └── test │ │ │ ├── FakeThread.kt │ │ │ └── di │ │ │ └── module │ │ │ └── ThreadingTestModule.kt │ └── worker │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── consumer-rules.pro │ │ ├── proguard-rules.pro │ │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── bael │ │ └── dads │ │ └── library │ │ └── worker │ │ ├── BaseWorker.kt │ │ ├── di │ │ ├── entry │ │ │ └── EntryPoint.kt │ │ └── module │ │ │ └── WorkerModule.kt │ │ ├── factory │ │ └── NoOpWorkerFactory.kt │ │ └── initializer │ │ └── WorkManagerInitializer.kt └── processor │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ └── main │ └── kotlin │ └── com │ └── bael │ └── dads │ └── processor │ ├── ext │ ├── AnnotationMirrorExt.kt │ ├── ClassNameExt.kt │ ├── ElementExt.kt │ ├── NameExt.kt │ └── TypeElementExt.kt │ ├── generator │ ├── BaseGenerator.kt │ ├── render │ │ ├── RenderGenerator.kt │ │ └── file │ │ │ ├── DefaultRendererInitializerFile.kt │ │ │ └── RenderExecutorFile.kt │ └── util │ │ └── CustomEquals.kt │ └── logger │ └── Logger.kt ├── assets ├── architecture.png ├── demo.gif ├── graphql.png ├── kmm.png ├── logo.png ├── mad_scorecard.png └── playstore.png ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── buildSrc │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── Application.kt │ │ ├── Library.kt │ │ ├── Plugin.kt │ │ └── Version.kt └── src │ └── main │ └── kotlin │ └── plugin │ ├── android │ ├── AppModulePlugin.kt │ ├── FeatureModulePlugin.kt │ └── LibraryModulePlugin.kt │ ├── shared │ ├── DataModulePlugin.kt │ ├── DomainModulePlugin.kt │ └── SharedModulePlugin.kt │ └── test │ └── JacocoTestReportPlugin.kt ├── data ├── database │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── data │ │ │ └── database │ │ │ └── di │ │ │ └── module │ │ │ ├── DatabaseModule.kt │ │ │ └── repository │ │ │ └── RepositoryModule.kt │ │ └── commonMain │ │ ├── kotlin │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── data │ │ │ └── database │ │ │ ├── constant │ │ │ └── Database.kt │ │ │ └── repository │ │ │ ├── DadJokeRepository.kt │ │ │ ├── DefaultDadJokeRepository.kt │ │ │ ├── DefaultRemoteMetaRepository.kt │ │ │ └── RemoteMetaRepository.kt │ │ └── sqldelight │ │ └── com │ │ └── bael │ │ └── dads │ │ └── data │ │ └── database │ │ └── entity │ │ ├── DadJoke.sq │ │ └── RemoteMeta.sq ├── database_test │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ └── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ └── com │ │ └── bael │ │ └── dads │ │ └── data │ │ └── database │ │ └── test │ │ └── di │ │ └── module │ │ └── DatabaseTestModule.kt ├── remote │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidMain │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── data │ │ │ └── remote │ │ │ ├── di │ │ │ └── module │ │ │ │ ├── client │ │ │ │ └── ApolloClientModule.kt │ │ │ │ ├── interceptor │ │ │ │ └── InterceptorModule.kt │ │ │ │ ├── mapper │ │ │ │ ├── ListMapperModule.kt │ │ │ │ ├── MapperModule.kt │ │ │ │ └── ResultMapperModule.kt │ │ │ │ └── service │ │ │ │ └── ServiceModule.kt │ │ │ └── network │ │ │ └── Network.kt │ │ └── commonMain │ │ ├── graphql │ │ └── com │ │ │ └── bael │ │ │ └── dads │ │ │ └── data │ │ │ └── remote │ │ │ ├── fragment │ │ │ └── DadJokeFragment.graphql │ │ │ └── query │ │ │ └── DadJokesQuery.graphql │ │ └── kotlin │ │ └── com │ │ └── bael │ │ └── dads │ │ └── data │ │ └── remote │ │ ├── constant │ │ └── Server.kt │ │ ├── interceptor │ │ └── NetworkInterceptor.kt │ │ ├── mapper │ │ ├── DadJokeMapper.kt │ │ └── DadJokesResponseMapper.kt │ │ ├── model │ │ └── DadJoke.kt │ │ ├── network │ │ └── Network.kt │ │ ├── response │ │ └── DadJokesResponse.kt │ │ └── service │ │ ├── BaseApolloService.kt │ │ ├── DadsApolloService.kt │ │ └── DadsService.kt └── remote_test │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── com │ │ └── bael │ │ └── dads │ │ └── data │ │ └── remote │ │ └── test │ │ └── di │ │ └── module │ │ └── service │ │ └── ServiceTestModule.kt │ └── commonMain │ └── kotlin │ └── com │ └── bael │ └── dads │ └── data │ └── remote │ └── test │ └── service │ ├── FakeBaseService.kt │ ├── FakeDadsService.kt │ └── RemoteService.kt ├── domain └── home │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidMain │ ├── AndroidManifest.xml │ └── kotlin │ │ └── com │ │ └── bael │ │ └── dads │ │ └── domain │ │ └── home │ │ ├── di │ │ └── module │ │ │ ├── mapper │ │ │ ├── ListMapperModule.kt │ │ │ └── MapperModule.kt │ │ │ └── usecase │ │ │ └── UseCaseModule.kt │ │ └── model │ │ └── DadJoke.kt │ └── commonMain │ └── kotlin │ └── com │ └── bael │ └── dads │ └── domain │ └── home │ ├── interactor │ ├── FavorDadJokeInteractor.kt │ ├── LoadDadJokeFeedInteractor.kt │ ├── LoadDadJokeInteractor.kt │ ├── LoadFavoredDadJokeInteractor.kt │ ├── LoadSeenDadJokeInteractor.kt │ ├── ObserveDadJokeInteractor.kt │ └── SetDadJokeSeenInteractor.kt │ ├── mapper │ ├── DadJokeDBMapper.kt │ ├── DadJokeRemoteMapper.kt │ └── RemoteMetaMapper.kt │ ├── model │ └── DadJoke.kt │ └── usecase │ ├── FavorDadJokeUseCase.kt │ ├── LoadDadJokeFeedUseCase.kt │ ├── LoadDadJokeUseCase.kt │ ├── LoadFavoredDadJokeUseCase.kt │ ├── LoadSeenDadJokeUseCase.kt │ ├── ObserveDadJokeUseCase.kt │ └── SetDadJokeSeenUseCase.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── keys.properties ├── settings.gradle.kts └── shared ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src ├── androidMain ├── AndroidManifest.xml └── kotlin │ └── com │ └── bael │ └── dads │ └── shared │ └── response │ └── Response.kt └── commonMain └── kotlin └── com └── bael └── dads └── shared ├── exception └── NoNetworkException.kt ├── ext └── ListExt.kt ├── mapper ├── ListMapper.kt ├── Mapper.kt └── ResultMapper.kt ├── response └── Response.kt └── time └── DateTime.kt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://www.buymeacoffee.com/ErickSumargo"] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | initialize: 9 | runs-on: macos-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v2 13 | - name: Setup GraphQL 14 | run: ./gradlew downloadApolloSchema --endpoint="https://dads-engine.herokuapp.com" --schema="data/remote/src/commonMain/graphql/com/bael/dads/data/remote/schema.json" 15 | - name: Cache GraphQL setup 16 | uses: actions/cache@v2 17 | with: 18 | path: data/remote/src/commonMain/graphql/com/bael/dads/data/remote 19 | key: ${{ github.sha }} 20 | build: 21 | runs-on: macos-latest 22 | needs: initialize 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v2 26 | - name: Cache GraphQL setup 27 | uses: actions/cache@v2 28 | with: 29 | path: data/remote/src/commonMain/graphql/com/bael/dads/data/remote 30 | key: ${{ github.sha }} 31 | - name: Build application 32 | run: ./gradlew assembleDebug --stacktrace 33 | test: 34 | runs-on: macos-latest 35 | needs: build 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | - name: Cache GraphQL setup 40 | uses: actions/cache@v2 41 | with: 42 | path: data/remote/src/commonMain/graphql/com/bael/dads/data/remote 43 | key: ${{ github.sha }} 44 | - name: Test application 45 | uses: reactivecircus/android-emulator-runner@v2 46 | with: 47 | api-level: 29 48 | script: ./gradlew jacocoTestReport --stacktrace 49 | - name: Upload JaCoCo report 50 | uses: codecov/codecov-action@v1 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | captures/ 5 | 6 | *.aab 7 | *.apk 8 | *.cxx 9 | *.DS_Store 10 | *.externalNativeBuild 11 | *.iml 12 | *.jks 13 | *.pepk 14 | local.properties -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ErickSumargo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android/annotation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/annotation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("kotlin") 3 | } 4 | -------------------------------------------------------------------------------- /android/annotation/src/main/kotlin/com/bael/dads/annotation/RenderWith.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.annotation 2 | 3 | import kotlin.annotation.AnnotationRetention.SOURCE 4 | import kotlin.annotation.AnnotationTarget.CLASS 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | @Target(CLASS) 12 | @Retention(SOURCE) 13 | @MustBeDocumented 14 | annotation class RenderWith(val state: KClass<*>) 15 | -------------------------------------------------------------------------------- /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | /src/release/google-services.json -------------------------------------------------------------------------------- /android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.AndroidX.hiltCompiler 2 | import Library.AndroidX.hiltNavigation 3 | import Library.AndroidX.hiltWork 4 | import Library.AndroidX.navigationFragment 5 | import Library.AndroidX.navigationUi 6 | import Library.AndroidX.work 7 | import Library.Apollo.apolloKotlin 8 | import Library.Google.dagger 9 | import Library.Google.daggerCompiler 10 | import Library.Google.firebaseBom 11 | import Library.Square.leakCanary 12 | import Library.Google.firebaseAnalytics as analytics 13 | import Library.Google.firebaseCrashlytics as crashlytics 14 | 15 | plugins { 16 | id("androidApp") 17 | } 18 | 19 | dependencies { 20 | // AndroidX 21 | implementation(hiltNavigation) 22 | implementation(hiltWork) 23 | kapt(hiltCompiler) 24 | 25 | implementation(navigationFragment) 26 | implementation(navigationUi) 27 | 28 | implementation(work) 29 | 30 | // Apollo 31 | implementation(apolloKotlin) 32 | 33 | // Google 34 | implementation(dagger) 35 | kapt(daggerCompiler) 36 | 37 | implementation(platform(firebaseBom)) 38 | implementation(analytics) 39 | implementation(crashlytics) 40 | 41 | // Square 42 | debugImplementation(leakCanary) 43 | } 44 | 45 | dependencies { 46 | // Shared 47 | implementation(project(":shared")) 48 | 49 | // Data 50 | implementation(project(":data:database")) 51 | implementation(project(":data:remote")) 52 | 53 | // Domain 54 | implementation(project(":domain:home")) 55 | implementation(project(":shared")) 56 | 57 | // Feature 58 | implementation(project(":android:feature:home")) 59 | 60 | // Library 61 | implementation(project(":android:library:preference")) 62 | implementation(project(":android:library:presentation")) 63 | implementation(project(":android:library:threading")) 64 | implementation(project(":android:library:worker")) 65 | } 66 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # ProGuard Configuration file 2 | # 3 | # See http://proguard.sourceforge.net/index.html#manual/usage.html 4 | 5 | # Firebase Crashlytics 6 | -keepattributes SourceFile,LineNumberTable 7 | -keep public class * extends java.lang.Exception 8 | -keep class com.google.firebase.crashlytics.** { *; } 9 | -dontwarn com.google.firebase.crashlytics.** 10 | 11 | # Jetpack - Navigation Component 12 | -keep class androidx.navigation.fragment.NavHostFragment 13 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/debug/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "", 4 | "project_id": "", 5 | "storage_bucket": "" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:0123456789:android:abcdefghijklmnopqrstuvwxyz", 11 | "android_client_info": { 12 | "package_name": "com.bael.dads.debug" 13 | } 14 | }, 15 | "oauth_client": [ 16 | { 17 | "client_id": "", 18 | "client_type": 1, 19 | "android_info": { 20 | "package_name": "com.bael.dads.debug", 21 | "certificate_hash": "" 22 | } 23 | }, 24 | { 25 | "client_id": "", 26 | "client_type": 3 27 | } 28 | ], 29 | "api_key": [ 30 | { 31 | "current_key": "" 32 | } 33 | ], 34 | "services": { 35 | "appinvite_service": { 36 | "other_platform_oauth_client": [ 37 | { 38 | "client_id": "", 39 | "client_type": 3 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | ], 46 | "configuration_version": "1" 47 | } -------------------------------------------------------------------------------- /android/app/src/debug/kotlin/com/bael/dads/DadsDebugApplication.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads 2 | 3 | import android.os.StrictMode.ThreadPolicy 4 | import android.os.StrictMode.VmPolicy 5 | import android.os.StrictMode.setThreadPolicy 6 | import android.os.StrictMode.setVmPolicy 7 | import dagger.hilt.android.HiltAndroidApp 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | @HiltAndroidApp 14 | internal class DadsDebugApplication : DadsApplication() { 15 | 16 | override fun onCreate() { 17 | super.onCreate() 18 | setStrictMode() 19 | } 20 | 21 | private fun setStrictMode() { 22 | setThreadPolicy( 23 | ThreadPolicy.Builder() 24 | .detectAll() 25 | .penaltyLog() 26 | .build() 27 | ) 28 | 29 | setVmPolicy( 30 | VmPolicy.Builder() 31 | .detectAll() 32 | .penaltyLog() 33 | .build() 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /android/app/src/debug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dads (Debug) 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/bael/dads/DadsApplication.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads 2 | 3 | import android.app.Application 4 | import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO 5 | import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES 6 | import androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode 7 | import com.bael.dads.library.preference.Preference 8 | import kotlinx.coroutines.runBlocking 9 | import javax.inject.Inject 10 | 11 | /** 12 | * Created by ErickSumargo on 01/01/21. 13 | */ 14 | 15 | abstract class DadsApplication : Application() { 16 | @Inject 17 | lateinit var preference: Preference 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | setTheme() 22 | } 23 | 24 | private fun setTheme() { 25 | runBlocking { 26 | val isNightTheme = preference.read( 27 | key = NIGHT_THEME_PREFERENCE, 28 | defaultValue = false 29 | ) 30 | setDefaultNightMode(MODE_NIGHT_YES.takeIf { isNightTheme } ?: MODE_NIGHT_NO) 31 | } 32 | } 33 | 34 | private companion object { 35 | const val NIGHT_THEME_PREFERENCE: String = "night_theme" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/bael/dads/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.activity 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.bael.dads.databinding.ActivityMainBinding 6 | import dagger.hilt.android.AndroidEntryPoint 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | @AndroidEntryPoint 13 | internal class MainActivity : AppCompatActivity() { 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | ActivityMainBinding.inflate(layoutInflater).also { viewBinding -> 18 | setContentView(viewBinding.root) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/bael/dads/di/module/MainActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.di.module 2 | 3 | import com.bael.dads.activity.MainActivity 4 | import com.bael.dads.library.presentation.di.qualifier.ActivityNameQualifier 5 | import com.bael.dads.library.presentation.di.qualifier.ActivityNameQualifier.Companion.ACTIVITY_MAIN 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | /** 13 | * Created by stef_ang on 24/04/21. 14 | */ 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | internal class MainActivityModule { 19 | 20 | @Provides 21 | @Singleton 22 | @ActivityNameQualifier(name = ACTIVITY_MAIN) 23 | fun provideMainActivityName(): String { 24 | return MainActivity::class.java.name 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/app/src/main/res/font/product_sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/android/app/src/main/res/font/product_sans.ttf -------------------------------------------------------------------------------- /android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dads 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 31 | 32 | 33 | 36 | 37 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/provider_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/release/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/release/google-services.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/android/app/src/release/google-services.enc -------------------------------------------------------------------------------- /android/app/src/release/kotlin/com/bael/dads/DadsReleaseApplication.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads 2 | 3 | import dagger.hilt.android.HiltAndroidApp 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | @HiltAndroidApp 10 | internal class DadsReleaseApplication : DadsApplication() 11 | -------------------------------------------------------------------------------- /android/feature/home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/feature/home/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.Airbnb.lottie 2 | import Library.AndroidX.constraintLayout 3 | import Library.AndroidX.swipeRefreshLayout 4 | import Library.AndroidX.viewPager2 5 | import Library.AndroidX.work 6 | 7 | plugins { 8 | id("androidFeature") 9 | } 10 | 11 | dependencies { 12 | // AndroidX 13 | implementation(constraintLayout) 14 | implementation(swipeRefreshLayout) 15 | implementation(viewPager2) 16 | implementation(work) 17 | 18 | // Airbnb 19 | implementation(lottie) 20 | } 21 | 22 | dependencies { 23 | // Shared 24 | implementation(project(":shared")) 25 | 26 | // Data 27 | androidTestImplementation(project(":data:database")) 28 | androidTestImplementation(project(":data:database_test")) 29 | 30 | androidTestImplementation(project(":data:remote")) 31 | androidTestImplementation(project(":data:remote_test")) 32 | 33 | // Domain 34 | implementation(project(":domain:home")) 35 | 36 | // Library 37 | implementation(project(":android:library:preference")) 38 | androidTestImplementation(project(":android:library:preference_test")) 39 | 40 | implementation(project(":android:library:worker")) 41 | } 42 | -------------------------------------------------------------------------------- /android/feature/home/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/android/feature/home/consumer-rules.pro -------------------------------------------------------------------------------- /android/feature/home/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /android/feature/home/src/androidTest/kotlin/com/bael/dads/feature/home/screen/home/UITest.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.home 2 | 3 | import com.bael.dads.feature.home.R 4 | import com.bael.dads.library.instrumentation.fragment.BaseFragmentTest 5 | import com.bael.dads.library.instrumentation.matcher.MatcherParams 6 | import com.bael.dads.library.presentation.ext.readText 7 | import dagger.hilt.android.testing.HiltAndroidTest 8 | import org.junit.Test 9 | 10 | /** 11 | * Created by ErickSumargo on 01/04/21. 12 | */ 13 | 14 | @HiltAndroidTest 15 | internal class UITest : BaseFragmentTest() { 16 | 17 | override fun setupTest() {} 18 | 19 | @Test 20 | fun whenScreenDisplayed_contentShouldShow() { 21 | runTest { 22 | // when 23 | launch(graphResId = R.navigation.nav_graph) 24 | 25 | // then 26 | assertViewDisplayed( 27 | params = MatcherParams( 28 | id = R.id.logoIcon 29 | ) 30 | ) 31 | assertViewDisplayed( 32 | params = MatcherParams( 33 | id = R.id.settingsIcon 34 | ) 35 | ) 36 | assertViewDisplayed( 37 | params = MatcherParams( 38 | text = context.readText(resId = R.string.feed) 39 | ) 40 | ) 41 | assertViewDisplayed( 42 | params = MatcherParams( 43 | text = context.readText(resId = R.string.seen) 44 | ) 45 | ) 46 | } 47 | } 48 | 49 | override fun clearTest() {} 50 | } 51 | -------------------------------------------------------------------------------- /android/feature/home/src/androidTest/kotlin/com/bael/dads/feature/home/sheet/sharepreview/UITest.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.sharepreview 2 | 3 | import androidx.core.os.bundleOf 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.library.instrumentation.matcher.MatcherParams 6 | import com.bael.dads.library.instrumentation.sheet.BaseSheetTest 7 | import com.bael.dads.shared.time.DateTime.now 8 | import dagger.hilt.android.testing.HiltAndroidTest 9 | import org.junit.Test 10 | 11 | /** 12 | * Created by ErickSumargo on 01/05/21. 13 | */ 14 | 15 | @HiltAndroidTest 16 | internal class UITest : BaseSheetTest() { 17 | 18 | override fun setupTest() {} 19 | 20 | @Test 21 | fun whenSheetDisplayed_shareMaterialShouldShow() { 22 | runTest { 23 | // given 24 | val dadJoke = DadJoke( 25 | id = 1, 26 | setup = "Setup 1", 27 | punchline = "Punchline 1", 28 | favored = false, 29 | seen = false, 30 | updatedAt = now 31 | ) 32 | 33 | // when 34 | launch(args = bundleOf("dadJoke" to dadJoke)) 35 | 36 | // then 37 | assertViewDisplayed( 38 | params = MatcherParams( 39 | text = "Setup 1" 40 | ) 41 | ) 42 | assertViewDisplayed( 43 | params = MatcherParams( 44 | text = "Punchline 1" 45 | ) 46 | ) 47 | } 48 | } 49 | 50 | override fun clearTest() {} 51 | } 52 | -------------------------------------------------------------------------------- /android/feature/home/src/androidTest/kotlin/com/bael/dads/feature/home/worker/factory/FakeFetchDadJokeFeedWorkerFactory.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.worker.factory 2 | 3 | import android.content.Context 4 | import androidx.work.ListenableWorker 5 | import androidx.work.WorkerFactory 6 | import androidx.work.WorkerParameters 7 | import com.bael.dads.domain.home.usecase.LoadDadJokeFeedUseCase 8 | import com.bael.dads.domain.home.usecase.LoadDadJokeUseCase 9 | import com.bael.dads.feature.home.notification.factory.NewFeedReminderNotificationFactory 10 | import com.bael.dads.feature.home.worker.FetchDadJokeFeedWorker 11 | import com.bael.dads.library.preference.Preference 12 | import com.bael.dads.library.presentation.notification.NotificationPublisher 13 | import javax.inject.Inject 14 | import javax.inject.Singleton 15 | 16 | /** 17 | * Created by ErickSumargo on 15/05/21. 18 | */ 19 | 20 | @Singleton 21 | internal class FakeFetchDadJokeFeedWorkerFactory @Inject constructor( 22 | private val loadDadJokeUseCase: LoadDadJokeUseCase, 23 | private val loadDadJokeFeedUseCase: LoadDadJokeFeedUseCase, 24 | private val preference: Preference, 25 | private val notificationPublisher: NotificationPublisher, 26 | private val newFeedReminderNotificationFactory: NewFeedReminderNotificationFactory 27 | ) : WorkerFactory() { 28 | 29 | override fun createWorker( 30 | appContext: Context, 31 | workerClassName: String, 32 | workerParameters: WorkerParameters 33 | ): ListenableWorker { 34 | return FetchDadJokeFeedWorker( 35 | appContext, 36 | workerParameters, 37 | loadDadJokeUseCase, 38 | loadDadJokeFeedUseCase, 39 | preference, 40 | notificationPublisher, 41 | newFeedReminderNotificationFactory 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/feature/home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/adapter/DadJokeFeedAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.lifecycle.LifecycleOwner 6 | import com.bael.dads.feature.home.adapter.cell.DadJokeFeedCell 7 | import com.bael.dads.feature.home.adapter.diffcallback.DadJokeDiffCallback 8 | import com.bael.dads.feature.home.databinding.CellFeedBinding.inflate 9 | import com.bael.dads.domain.home.model.DadJoke 10 | import com.bael.dads.library.presentation.widget.recyclerview.adapter.LiveListAdapter 11 | 12 | /** 13 | * Created by ErickSumargo on 01/01/21. 14 | */ 15 | 16 | internal class DadJokeFeedAdapter( 17 | diffCallback: DadJokeDiffCallback, 18 | lifecycleOwner: LifecycleOwner, 19 | private val onClickLikeListener: (DadJoke, Boolean) -> Unit, 20 | private val onClickShareListener: (DadJoke) -> Unit, 21 | private val onReachEndOfItemsListener: (DadJoke) -> Unit, 22 | private val onObserveItemListener: suspend (DadJoke) -> Unit 23 | ) : LiveListAdapter(diffCallback, lifecycleOwner) { 24 | private val transitionCache: MutableMap = mutableMapOf() 25 | 26 | override fun createCell( 27 | inflater: LayoutInflater, 28 | viewGroup: ViewGroup 29 | ): DadJokeFeedCell { 30 | val viewBinding = inflate(inflater, viewGroup, false) 31 | return DadJokeFeedCell( 32 | viewBinding, 33 | transitionCache, 34 | onClickLikeListener, 35 | onClickShareListener 36 | ) 37 | } 38 | 39 | override fun onReachEndOfItems(item: DadJoke) { 40 | onReachEndOfItemsListener(item) 41 | } 42 | 43 | override fun getItemKey(item: DadJoke): Int { 44 | return item.id 45 | } 46 | 47 | override suspend fun observeItem(item: DadJoke) { 48 | onObserveItemListener(item) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/adapter/SeenDadJokeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.lifecycle.LifecycleOwner 6 | import com.bael.dads.feature.home.adapter.cell.SeenDadJokeCell 7 | import com.bael.dads.feature.home.adapter.diffcallback.DadJokeDiffCallback 8 | import com.bael.dads.feature.home.databinding.CellSeenBinding.inflate 9 | import com.bael.dads.domain.home.model.DadJoke 10 | import com.bael.dads.library.presentation.widget.recyclerview.adapter.LiveListAdapter 11 | 12 | /** 13 | * Created by ErickSumargo on 01/01/21. 14 | */ 15 | 16 | internal class SeenDadJokeAdapter( 17 | diffCallback: DadJokeDiffCallback, 18 | lifecycleOwner: LifecycleOwner, 19 | private val onClickItemListener: (DadJoke) -> Unit, 20 | private val onReachEndOfItemsListener: (DadJoke) -> Unit, 21 | private val onObserveItemListener: suspend (DadJoke) -> Unit 22 | ) : LiveListAdapter(diffCallback, lifecycleOwner) { 23 | 24 | override fun createCell( 25 | inflater: LayoutInflater, 26 | viewGroup: ViewGroup 27 | ): SeenDadJokeCell { 28 | val viewBinding = inflate(inflater, viewGroup, false) 29 | val cellHeight = viewGroup.height / 2 30 | 31 | return SeenDadJokeCell(viewBinding, cellHeight, onClickItemListener) 32 | } 33 | 34 | override fun getItemKey(item: DadJoke): Int { 35 | return item.id 36 | } 37 | 38 | override fun onReachEndOfItems(item: DadJoke) { 39 | onReachEndOfItemsListener(item) 40 | } 41 | 42 | override suspend fun observeItem(item: DadJoke) { 43 | onObserveItemListener(item) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/adapter/cell/SeenDadJokeCell.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.adapter.cell 2 | 3 | import com.bael.dads.feature.home.databinding.CellSeenBinding 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.library.presentation.ext.toRichText 6 | import com.bael.dads.library.presentation.widget.recyclerview.adapter.cell.BaseCell 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | internal class SeenDadJokeCell( 13 | viewBinding: CellSeenBinding, 14 | private val cellHeight: Int, 15 | private val onClickItemListener: (DadJoke) -> Unit 16 | ) : BaseCell(viewBinding) { 17 | 18 | override fun render(state: DadJoke) { 19 | renderContent(state) 20 | renderSetup(state) 21 | } 22 | 23 | private fun renderContent(state: DadJoke) { 24 | viewBinding.contentLayout.also { layout -> 25 | layout.minHeight = cellHeight 26 | 27 | layout.setOnClickListener { 28 | onClickItemListener(state) 29 | } 30 | } 31 | } 32 | 33 | private fun renderSetup(state: DadJoke) { 34 | viewBinding.setupText.also { view -> 35 | view.text = state.setup.toRichText() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/adapter/diffcallback/DadJokeDiffCallback.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.adapter.diffcallback 2 | 3 | import androidx.recyclerview.widget.DiffUtil.ItemCallback 4 | import com.bael.dads.domain.home.model.DadJoke 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | internal class DadJokeDiffCallback : ItemCallback() { 11 | 12 | override fun areItemsTheSame( 13 | oldItem: DadJoke, 14 | newItem: DadJoke 15 | ): Boolean { 16 | return oldItem.id == newItem.id 17 | } 18 | 19 | override fun areContentsTheSame( 20 | oldItem: DadJoke, 21 | newItem: DadJoke 22 | ): Boolean { 23 | return oldItem == newItem 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/animation/Animation.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.animation 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | internal val reminder: String 8 | get() = "anim_reminder.json" 9 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/di/module/screen/FeedScreenModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.di.module.screen 2 | 3 | import com.bael.dads.feature.home.screen.feed.DefaultRendererInitializer 4 | import com.bael.dads.feature.home.screen.feed.Renderer 5 | import com.bael.dads.feature.home.screen.feed.State 6 | import com.bael.dads.feature.home.screen.feed.ViewModel 7 | import com.bael.dads.library.presentation.renderer.RendererInitializer 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.components.FragmentComponent 13 | import dagger.hilt.android.components.ViewModelComponent 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(value = [ViewModelComponent::class, FragmentComponent::class]) 21 | internal interface FeedScreenModule { 22 | 23 | @Binds 24 | fun bindRendererInitializer( 25 | rendererInitializer: DefaultRendererInitializer 26 | ): RendererInitializer 27 | 28 | companion object { 29 | 30 | @Provides 31 | fun provideState(): State { 32 | return State(responses = listOf()) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/di/module/screen/SeenScreenModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.di.module.screen 2 | 3 | import com.bael.dads.feature.home.screen.seen.DefaultRendererInitializer 4 | import com.bael.dads.feature.home.screen.seen.Renderer 5 | import com.bael.dads.feature.home.screen.seen.State 6 | import com.bael.dads.feature.home.screen.seen.ViewModel 7 | import com.bael.dads.library.presentation.renderer.RendererInitializer 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.components.FragmentComponent 13 | import dagger.hilt.android.components.ViewModelComponent 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(value = [ViewModelComponent::class, FragmentComponent::class]) 21 | internal interface SeenScreenModule { 22 | 23 | @Binds 24 | fun bindRendererInitializer( 25 | rendererInitializer: DefaultRendererInitializer 26 | ): RendererInitializer 27 | 28 | companion object { 29 | 30 | @Provides 31 | fun provideState(): State { 32 | return State( 33 | isFavoriteFilterActivated = false, 34 | responses = listOf() 35 | ) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/di/module/sheet/DetailSheetModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.di.module.sheet 2 | 3 | import com.bael.dads.feature.home.sheet.detail.DefaultRendererInitializer 4 | import com.bael.dads.feature.home.sheet.detail.Renderer 5 | import com.bael.dads.feature.home.sheet.detail.State 6 | import com.bael.dads.feature.home.sheet.detail.ViewModel 7 | import com.bael.dads.library.presentation.renderer.RendererInitializer 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.components.FragmentComponent 13 | import dagger.hilt.android.components.ViewModelComponent 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(value = [ViewModelComponent::class, FragmentComponent::class]) 21 | internal interface DetailSheetModule { 22 | 23 | @Binds 24 | fun bindRendererInitializer( 25 | rendererInitializer: DefaultRendererInitializer 26 | ): RendererInitializer 27 | 28 | companion object { 29 | 30 | @Provides 31 | fun provideState(): State { 32 | return State(dadJoke = null) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/di/module/sheet/SettingsSheetModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.di.module.sheet 2 | 3 | import com.bael.dads.feature.home.sheet.settings.DefaultRendererInitializer 4 | import com.bael.dads.feature.home.sheet.settings.Renderer 5 | import com.bael.dads.feature.home.sheet.settings.State 6 | import com.bael.dads.feature.home.sheet.settings.ViewModel 7 | import com.bael.dads.library.presentation.renderer.RendererInitializer 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.components.FragmentComponent 13 | import dagger.hilt.android.components.ViewModelComponent 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(value = [ViewModelComponent::class, FragmentComponent::class]) 21 | internal interface SettingsSheetModule { 22 | 23 | @Binds 24 | fun bindRendererInitializer( 25 | rendererInitializer: DefaultRendererInitializer 26 | ): RendererInitializer 27 | 28 | companion object { 29 | 30 | @Provides 31 | fun provideState(): State { 32 | return State() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/di/module/sheet/SharePreviewSheetModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.di.module.sheet 2 | 3 | import com.bael.dads.feature.home.sheet.sharepreview.DefaultRendererInitializer 4 | import com.bael.dads.feature.home.sheet.sharepreview.Renderer 5 | import com.bael.dads.feature.home.sheet.sharepreview.State 6 | import com.bael.dads.feature.home.sheet.sharepreview.ViewModel 7 | import com.bael.dads.library.presentation.renderer.RendererInitializer 8 | import dagger.Binds 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.android.components.FragmentComponent 13 | import dagger.hilt.android.components.ViewModelComponent 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(value = [ViewModelComponent::class, FragmentComponent::class]) 21 | internal interface SharePreviewSheetModule { 22 | 23 | @Binds 24 | fun bindRendererInitializer( 25 | rendererInitializer: DefaultRendererInitializer 26 | ): RendererInitializer 27 | 28 | companion object { 29 | 30 | @Provides 31 | fun provideState(): State { 32 | return State(dadJoke = null) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/notification/factory/NewFeedReminderNotificationFactory.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.notification.factory 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.feature.home.notification.NewFeedReminderNotification 5 | import dagger.assisted.AssistedFactory 6 | 7 | /** 8 | * Created by stef_ang on 24/04/21. 9 | */ 10 | 11 | @AssistedFactory 12 | internal interface NewFeedReminderNotificationFactory { 13 | 14 | fun create(dadJokes: List): NewFeedReminderNotification 15 | } 16 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/feed/Event.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.feed 2 | 3 | /** 4 | * Created by ErickSumargo on 01/04/21. 5 | */ 6 | 7 | internal sealed class Event 8 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/feed/Renderer.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.feed 2 | 3 | import com.bael.dads.annotation.RenderWith 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.library.presentation.renderer.BaseRenderer 6 | import com.bael.dads.shared.response.Response 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | @RenderWith(State::class) 13 | internal interface Renderer : BaseRenderer { 14 | 15 | fun renderDadJokeFeed(responses: List>>) 16 | } 17 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/feed/State.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.feed 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.library.presentation.state.BaseState 5 | import com.bael.dads.shared.response.Response 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | internal data class State( 12 | val responses: List>> 13 | ) : BaseState() 14 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/home/Event.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.home 2 | 3 | /** 4 | * Created by ErickSumargo on 01/04/21. 5 | */ 6 | 7 | internal sealed class Event 8 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/home/Renderer.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.home 2 | 3 | import com.bael.dads.annotation.RenderWith 4 | import com.bael.dads.library.presentation.renderer.BaseRenderer 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | @RenderWith(State::class) 11 | internal interface Renderer : BaseRenderer 12 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/home/State.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.home 2 | 3 | import com.bael.dads.library.presentation.state.BaseState 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | internal data class State( 10 | val query: String 11 | ) : BaseState() 12 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/home/ViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.home 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import com.bael.dads.library.presentation.ext.reduce 5 | import com.bael.dads.library.presentation.viewmodel.BaseViewModel 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import kotlinx.coroutines.flow.MutableStateFlow 8 | import javax.inject.Inject 9 | 10 | /** 11 | * Created by ErickSumargo on 01/01/21. 12 | */ 13 | 14 | @HiltViewModel 15 | internal class ViewModel @Inject constructor( 16 | initState: State, 17 | savedStateHandle: SavedStateHandle 18 | ) : BaseViewModel(initState, savedStateHandle) { 19 | val queryFlow: MutableStateFlow = MutableStateFlow(state.query) 20 | 21 | fun submitQuery(query: String) { 22 | queryFlow.value = query 23 | renderQuery(query) 24 | } 25 | 26 | private fun renderQuery(query: String) { 27 | val newState = state.reduce { 28 | copy(query = query) 29 | } 30 | render(newState) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/seen/Event.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.seen 2 | 3 | /** 4 | * Created by ErickSumargo on 01/04/21. 5 | */ 6 | 7 | internal sealed class Event 8 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/seen/Renderer.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.seen 2 | 3 | import com.bael.dads.annotation.RenderWith 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.library.presentation.renderer.BaseRenderer 6 | import com.bael.dads.shared.response.Response 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | @RenderWith(State::class) 13 | internal interface Renderer : BaseRenderer { 14 | 15 | fun renderSeenDadJoke(responses: List>>) 16 | 17 | fun renderFavoriteFilter(isFavoriteFilterActivated: Boolean) 18 | } 19 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/screen/seen/State.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.screen.seen 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.library.presentation.state.BaseState 5 | import com.bael.dads.shared.response.Response 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | internal data class State( 12 | val responses: List>>, 13 | val isFavoriteFilterActivated: Boolean, 14 | ) : BaseState() 15 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/detail/Event.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.detail 2 | 3 | /** 4 | * Created by ErickSumargo on 01/04/21. 5 | */ 6 | 7 | sealed class Event 8 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/detail/Renderer.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.detail 2 | 3 | import com.bael.dads.annotation.RenderWith 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.library.presentation.renderer.BaseRenderer 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | @RenderWith(State::class) 12 | internal interface Renderer : BaseRenderer { 13 | 14 | fun renderDetail(dadJoke: DadJoke?) 15 | } 16 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/detail/State.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.detail 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.library.presentation.state.BaseState 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | internal data class State( 11 | val dadJoke: DadJoke? 12 | ) : BaseState() 13 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/detail/ViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.detail 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.library.presentation.ext.reduce 6 | import com.bael.dads.library.presentation.viewmodel.BaseViewModel 7 | import dagger.hilt.android.lifecycle.HiltViewModel 8 | import javax.inject.Inject 9 | 10 | /** 11 | * Created by ErickSumargo on 01/01/21. 12 | */ 13 | 14 | @HiltViewModel 15 | internal class ViewModel @Inject constructor( 16 | initState: State, 17 | savedStateHandle: SavedStateHandle 18 | ) : BaseViewModel(initState, savedStateHandle) { 19 | val dadJoke: DadJoke? 20 | get() = state.dadJoke 21 | 22 | fun receiveDadJoke() { 23 | val newState = state.reduce { 24 | copy(dadJoke = savedStateHandle.get("dadJoke")) 25 | } 26 | render(newState) 27 | } 28 | 29 | fun favorDadJoke(favored: Boolean) { 30 | val newState = state.reduce { 31 | copy(dadJoke = dadJoke?.copy(favored = favored)) 32 | } 33 | render(newState) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/settings/Event.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.settings 2 | 3 | /** 4 | * Created by ErickSumargo on 01/04/21. 5 | */ 6 | 7 | internal sealed class Event 8 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/settings/Renderer.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.settings 2 | 3 | import com.bael.dads.annotation.RenderWith 4 | import com.bael.dads.library.presentation.renderer.BaseRenderer 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | @RenderWith(State::class) 11 | internal interface Renderer : BaseRenderer 12 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/settings/State.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.settings 2 | 3 | import com.bael.dads.library.presentation.state.BaseState 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | internal class State : BaseState() 10 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/settings/ViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.settings 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import com.bael.dads.library.presentation.viewmodel.BaseViewModel 5 | import dagger.hilt.android.lifecycle.HiltViewModel 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | @HiltViewModel 13 | internal class ViewModel @Inject constructor( 14 | initState: State, 15 | savedStateHandle: SavedStateHandle 16 | ) : BaseViewModel(initState, savedStateHandle) 17 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/sharepreview/Event.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.sharepreview 2 | 3 | /** 4 | * Created by ErickSumargo on 01/04/21. 5 | */ 6 | 7 | sealed class Event 8 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/sharepreview/Renderer.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.sharepreview 2 | 3 | import com.bael.dads.annotation.RenderWith 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.library.presentation.renderer.BaseRenderer 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | @RenderWith(State::class) 12 | internal interface Renderer : BaseRenderer { 13 | 14 | fun renderPreview(dadJoke: DadJoke?) 15 | } 16 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/sharepreview/State.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.sharepreview 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.library.presentation.state.BaseState 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | internal data class State( 11 | val dadJoke: DadJoke? 12 | ) : BaseState() 13 | -------------------------------------------------------------------------------- /android/feature/home/src/main/kotlin/com/bael/dads/feature/home/sheet/sharepreview/ViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.feature.home.sheet.sharepreview 2 | 3 | import androidx.lifecycle.SavedStateHandle 4 | import com.bael.dads.library.presentation.ext.reduce 5 | import com.bael.dads.library.presentation.viewmodel.BaseViewModel 6 | import dagger.hilt.android.lifecycle.HiltViewModel 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | @HiltViewModel 14 | internal class ViewModel @Inject constructor( 15 | initState: State, 16 | savedStateHandle: SavedStateHandle 17 | ) : BaseViewModel(initState, savedStateHandle) { 18 | 19 | fun receiveDadJoke() { 20 | val newState = state.reduce { 21 | copy(dadJoke = savedStateHandle.get("dadJoke")) 22 | } 23 | render(newState) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/bg_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/bg_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_clear.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_feed.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_like.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_like_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_no_internet.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_seen.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/layout/cell_seen.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/layout/item_group_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/layout/item_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | 19 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/layout/screen_feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/layout/sheet_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/feature/home/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dads 4 | 5 | Logo 6 | Find joke 7 | Search 8 | Clear 9 | Settings 10 | 11 | Notification 12 | New feed available reminder 13 | New feed available 14 | %1$s new feed recently added 15 | Theme 16 | Night mode 17 | 18 | Feed 19 | Seen 20 | 21 | Like 22 | Dislike 23 | Share 24 | Preview 25 | 26 | Wow you hit the end! We\'ll notify back as soon the latest avails. Stay tuned! 27 | Hmm no joke found. Different keyword perhaps? 28 | 29 | Favorite filter 30 | Filter favorite only 31 | 32 | -------------------------------------------------------------------------------- /android/library/instrumentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/library/instrumentation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.AndroidX.appCompat 2 | import Library.AndroidX.archTesting 3 | import Library.AndroidX.espresso 4 | import Library.AndroidX.fragment 5 | import Library.AndroidX.fragmentTesting 6 | import Library.AndroidX.navigationTesting 7 | import Library.AndroidX.runner 8 | import Library.AndroidX.uiAutomator 9 | import Library.AndroidX.workTesting 10 | import Library.Google.daggerTesting 11 | import Library.Google.material 12 | import Library.Google.truth 13 | import Library.KotlinX.coroutinesTest 14 | 15 | plugins { 16 | id("androidLibrary") 17 | } 18 | 19 | dependencies { 20 | // AndroidX 21 | implementation(appCompat) 22 | implementation(archTesting) 23 | implementation(espresso) 24 | implementation(fragment) 25 | implementation(fragmentTesting) 26 | implementation(navigationTesting) 27 | implementation(runner) 28 | implementation(uiAutomator) 29 | implementation(workTesting) 30 | 31 | // Google 32 | implementation(daggerTesting) 33 | implementation(material) 34 | implementation(truth) 35 | 36 | // KotlinX 37 | implementation(coroutinesTest) 38 | } 39 | 40 | dependencies { 41 | // Library 42 | implementation(project(":android:library:presentation")) 43 | implementation(project(":android:library:threading")) 44 | implementation(project(":android:library:worker")) 45 | } 46 | -------------------------------------------------------------------------------- /android/library/instrumentation/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/android/library/instrumentation/consumer-rules.pro -------------------------------------------------------------------------------- /android/library/instrumentation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /android/library/instrumentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/library/instrumentation/src/main/kotlin/com/bael/dads/library/instrumentation/activity/MainTestActivity.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.instrumentation.activity 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import dagger.hilt.android.AndroidEntryPoint 5 | 6 | /** 7 | * Created by ErickSumargo on 01/04/21. 8 | */ 9 | 10 | @AndroidEntryPoint 11 | class MainTestActivity : AppCompatActivity() 12 | -------------------------------------------------------------------------------- /android/library/instrumentation/src/main/kotlin/com/bael/dads/library/instrumentation/di/module/MainTestActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.instrumentation.di.module 2 | 3 | import com.bael.dads.library.instrumentation.activity.MainTestActivity 4 | import com.bael.dads.library.presentation.di.qualifier.ActivityNameQualifier 5 | import com.bael.dads.library.presentation.di.qualifier.ActivityNameQualifier.Companion.ACTIVITY_MAIN 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | /** 13 | * Created by stef_ang on 26/04/21. 14 | */ 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | internal class MainTestActivityModule { 19 | 20 | @Provides 21 | @Singleton 22 | @ActivityNameQualifier(name = ACTIVITY_MAIN) 23 | fun provideMainTestActivityName(): String { 24 | return MainTestActivity::class.java.name 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/library/instrumentation/src/main/kotlin/com/bael/dads/library/instrumentation/matcher/MatcherParams.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.instrumentation.matcher 2 | 3 | /** 4 | * Created by ErickSumargo on 15/05/21. 5 | */ 6 | 7 | data class MatcherParams( 8 | val id: Int = -1, 9 | val text: String = "", 10 | val parent: MatcherParams? = null, 11 | val sibling: MatcherParams? = null 12 | ) 13 | -------------------------------------------------------------------------------- /android/library/instrumentation/src/main/kotlin/com/bael/dads/library/instrumentation/runner/HiltTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.instrumentation.runner 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | import dagger.hilt.android.testing.HiltTestApplication 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | class HiltTestRunner : AndroidJUnitRunner() { 13 | 14 | override fun newApplication( 15 | cl: ClassLoader?, 16 | className: String?, 17 | context: Context? 18 | ): Application { 19 | return super.newApplication(cl, HiltTestApplication::class.java.name, context) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/library/instrumentation/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/library/presentation/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #404552 4 | #515868 5 | 6 | #A1A9B3 7 | #686d75 8 | #D6D6D6 9 | #F1F2F5 10 | 11 | #EF3A25 12 | 13 | #01AAC8 14 | 15 | #FFFFFF 16 | 17 | -------------------------------------------------------------------------------- /android/library/presentation/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Icon 4 | 5 | Hmm, is your connection fine there? 6 | Oops! There\'s a temporary issue with the service. Would you please, 7 | Try again 8 | 9 | -------------------------------------------------------------------------------- /android/library/presentation/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/library/threading/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/library/threading/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("androidLibrary") 3 | } 4 | -------------------------------------------------------------------------------- /android/library/threading/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/android/library/threading/consumer-rules.pro -------------------------------------------------------------------------------- /android/library/threading/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /android/library/threading/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/library/threading/src/main/kotlin/com/bael/dads/library/threading/DefaultThread.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.threading 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers.Default 5 | import kotlinx.coroutines.Dispatchers.IO 6 | import kotlinx.coroutines.Dispatchers.Main 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | class DefaultThread @Inject constructor() : Thread { 14 | override val main: CoroutineDispatcher 15 | get() = Main 16 | 17 | override val default: CoroutineDispatcher 18 | get() = Default 19 | 20 | override val io: CoroutineDispatcher 21 | get() = IO 22 | 23 | override fun reset() {} 24 | } 25 | -------------------------------------------------------------------------------- /android/library/threading/src/main/kotlin/com/bael/dads/library/threading/Thread.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.threading 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | interface Thread { 10 | val main: CoroutineDispatcher 11 | 12 | val default: CoroutineDispatcher 13 | 14 | val io: CoroutineDispatcher 15 | 16 | fun reset() 17 | } 18 | -------------------------------------------------------------------------------- /android/library/threading/src/main/kotlin/com/bael/dads/library/threading/di/module/ThreadingModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.threading.di.module 2 | 3 | import com.bael.dads.library.threading.DefaultThread 4 | import com.bael.dads.library.threading.Thread 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * Created by ErickSumargo on 01/01/21. 13 | */ 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | interface ThreadingModule { 18 | 19 | @Binds 20 | @Singleton 21 | fun bindThread(thread: DefaultThread): Thread 22 | } 23 | -------------------------------------------------------------------------------- /android/library/threading_test/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/library/threading_test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.Google.daggerTesting 2 | import Library.KotlinX.coroutinesTest 3 | 4 | plugins { 5 | id("androidLibrary") 6 | } 7 | 8 | dependencies { 9 | // Google 10 | implementation(daggerTesting) 11 | 12 | // KotlinX 13 | implementation(coroutinesTest) 14 | } 15 | 16 | dependencies { 17 | // Library 18 | implementation(project(":android:library:threading")) 19 | } 20 | -------------------------------------------------------------------------------- /android/library/threading_test/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/android/library/threading_test/consumer-rules.pro -------------------------------------------------------------------------------- /android/library/threading_test/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /android/library/threading_test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /android/library/threading_test/src/main/kotlin/com/bael/dads/library/threading/test/FakeThread.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.threading.test 2 | 3 | import com.bael.dads.library.threading.Thread 4 | import kotlinx.coroutines.CoroutineDispatcher 5 | import kotlinx.coroutines.test.TestCoroutineDispatcher 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by ErickSumargo on 01/04/21. 10 | */ 11 | 12 | internal class FakeThread @Inject constructor() : Thread { 13 | private val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher() 14 | 15 | override val main: CoroutineDispatcher 16 | get() = testDispatcher 17 | 18 | override val default: CoroutineDispatcher 19 | get() = testDispatcher 20 | 21 | override val io: CoroutineDispatcher 22 | get() = testDispatcher 23 | 24 | override fun reset() { 25 | testDispatcher.cleanupTestCoroutines() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/library/threading_test/src/main/kotlin/com/bael/dads/library/threading/test/di/module/ThreadingTestModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.threading.test.di.module 2 | 3 | import com.bael.dads.library.threading.Thread 4 | import com.bael.dads.library.threading.di.module.ThreadingModule 5 | import com.bael.dads.library.threading.test.FakeThread 6 | import dagger.Binds 7 | import dagger.Module 8 | import dagger.hilt.components.SingletonComponent 9 | import dagger.hilt.testing.TestInstallIn 10 | import javax.inject.Singleton 11 | 12 | /** 13 | * Created by ErickSumargo on 01/04/21. 14 | */ 15 | 16 | @Module 17 | @TestInstallIn( 18 | components = [SingletonComponent::class], 19 | replaces = [ThreadingModule::class] 20 | ) 21 | internal interface ThreadingTestModule { 22 | 23 | @Binds 24 | @Singleton 25 | fun bindThread(thread: FakeThread): Thread 26 | } 27 | -------------------------------------------------------------------------------- /android/library/worker/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/library/worker/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.AndroidX.hiltWork 2 | import Library.AndroidX.startup 3 | import Library.AndroidX.work 4 | 5 | plugins { 6 | id("androidLibrary") 7 | } 8 | 9 | dependencies { 10 | // AndroidX 11 | implementation(hiltWork) 12 | implementation(startup) 13 | implementation(work) 14 | } 15 | -------------------------------------------------------------------------------- /android/library/worker/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/android/library/worker/consumer-rules.pro -------------------------------------------------------------------------------- /android/library/worker/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /android/library/worker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 11 | 12 | 17 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/library/worker/src/main/kotlin/com/bael/dads/library/worker/BaseWorker.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.worker 2 | 3 | import android.content.Context 4 | import androidx.work.CoroutineWorker 5 | import androidx.work.WorkManager 6 | import androidx.work.WorkerParameters 7 | import javax.inject.Inject 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | abstract class BaseWorker constructor( 14 | appContext: Context, 15 | params: WorkerParameters 16 | ) : CoroutineWorker(appContext, params) { 17 | @Inject 18 | lateinit var workManager: WorkManager 19 | 20 | protected fun cancelWork() { 21 | workManager.cancelWorkById(id) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/library/worker/src/main/kotlin/com/bael/dads/library/worker/di/entry/EntryPoint.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.worker.di.entry 2 | 3 | import androidx.hilt.work.HiltWorkerFactory 4 | import dagger.hilt.EntryPoint 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | @EntryPoint 13 | @InstallIn(SingletonComponent::class) 14 | internal interface EntryPoint { 15 | 16 | fun accessWorkerFactory(): HiltWorkerFactory 17 | } 18 | -------------------------------------------------------------------------------- /android/library/worker/src/main/kotlin/com/bael/dads/library/worker/di/module/WorkerModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.worker.di.module 2 | 3 | import android.content.Context 4 | import androidx.work.WorkManager 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | /** 13 | * Created by ErickSumargo on 01/01/21. 14 | */ 15 | 16 | @Module 17 | @InstallIn(SingletonComponent::class) 18 | internal object WorkerModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideWorkManager(@ApplicationContext context: Context): WorkManager { 23 | return WorkManager.getInstance(context) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/library/worker/src/main/kotlin/com/bael/dads/library/worker/factory/NoOpWorkerFactory.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.library.worker.factory 2 | 3 | import android.content.Context 4 | import androidx.work.ListenableWorker 5 | import androidx.work.WorkerFactory 6 | import androidx.work.WorkerParameters 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | internal class NoOpWorkerFactory : WorkerFactory() { 13 | 14 | override fun createWorker( 15 | appContext: Context, 16 | workerClassName: String, 17 | workerParameters: WorkerParameters 18 | ): ListenableWorker? { 19 | TODO("Not yet implemented") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/processor/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /android/processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.Google.autoService 2 | import Library.JavaX.inject 3 | import Library.Square.javaPoet 4 | 5 | plugins { 6 | id("kotlin") 7 | kotlin("kapt") 8 | } 9 | 10 | dependencies { 11 | // Google 12 | implementation(autoService) 13 | kapt(autoService) 14 | 15 | // JavaX 16 | implementation(inject) 17 | 18 | // Square 19 | implementation(javaPoet) 20 | } 21 | 22 | dependencies { 23 | // Internal 24 | implementation(project(":android:annotation")) 25 | } 26 | -------------------------------------------------------------------------------- /android/processor/src/main/kotlin/com/bael/dads/processor/ext/AnnotationMirrorExt.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.processor.ext 2 | 3 | import javax.lang.model.element.AnnotationMirror 4 | import javax.lang.model.element.Element 5 | import javax.lang.model.element.TypeElement 6 | import javax.lang.model.type.TypeMirror 7 | import javax.lang.model.util.Types 8 | import kotlin.reflect.KProperty 9 | 10 | /** 11 | * Created by ErickSumargo on 01/01/21. 12 | */ 13 | 14 | fun AnnotationMirror.toElement(): Element { 15 | return annotationType.asElement() 16 | } 17 | 18 | fun AnnotationMirror.parameterOf(typeUtils: Types, parameter: KProperty<*>): TypeElement? { 19 | val typeMirror = elementValues?.entries?.firstOrNull { (element, _) -> 20 | element.simpleName.toString() == parameter.name 21 | }?.value?.value as? TypeMirror 22 | return typeUtils.asElement(typeMirror) as TypeElement 23 | } 24 | -------------------------------------------------------------------------------- /android/processor/src/main/kotlin/com/bael/dads/processor/ext/ClassNameExt.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.processor.ext 2 | 3 | import com.squareup.javapoet.ClassName 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | val ClassName.varName: String 10 | get() = simpleName().let { name -> 11 | name[0].toLowerCase() + name.substring(1, name.length) 12 | } 13 | -------------------------------------------------------------------------------- /android/processor/src/main/kotlin/com/bael/dads/processor/ext/ElementExt.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.processor.ext 2 | 3 | import javax.lang.model.element.AnnotationMirror 4 | import javax.lang.model.element.Element 5 | import javax.lang.model.element.ElementKind 6 | import javax.lang.model.element.TypeElement 7 | import kotlin.reflect.KClass 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | fun Element.annotationOf(annotation: KClass<*>): AnnotationMirror? { 14 | return annotationMirrors.find { annotationMirror -> 15 | val typeElement = annotationMirror.toElement() as TypeElement 16 | typeElement.qualifiedName.toString() == annotation.java.name 17 | } 18 | } 19 | 20 | fun Element.members(kind: ElementKind): List { 21 | return enclosedElements?.filter { element -> 22 | element.kind == kind 23 | }.orEmpty() 24 | } 25 | 26 | val Element.isNullable: Boolean 27 | get() = annotationMirrors.any { annotationMirror -> 28 | annotationMirror.toElement().simpleName.endsWith("Nullable") 29 | } 30 | -------------------------------------------------------------------------------- /android/processor/src/main/kotlin/com/bael/dads/processor/ext/NameExt.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.processor.ext 2 | 3 | import javax.lang.model.element.Name 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | fun Name.toGetter(): String { 10 | return toString().let { name -> 11 | if (name.take(2) == "is") "$name()" 12 | else "get${name.capitalize()}()" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/processor/src/main/kotlin/com/bael/dads/processor/ext/TypeElementExt.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.processor.ext 2 | 3 | import com.squareup.javapoet.TypeName 4 | import javax.lang.model.element.ElementKind.FIELD 5 | import javax.lang.model.element.ExecutableElement 6 | import javax.lang.model.element.Name 7 | import javax.lang.model.element.TypeElement 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | val TypeElement.fields: Map 14 | get() = members(kind = FIELD).associate { field -> 15 | field.simpleName to TypeName.get(field.asType()) 16 | } 17 | 18 | val TypeElement.methods: List 19 | get() = enclosedElements.map { element -> 20 | val method = element as? ExecutableElement 21 | method ?: return emptyList() 22 | method 23 | } 24 | -------------------------------------------------------------------------------- /android/processor/src/main/kotlin/com/bael/dads/processor/logger/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.processor.logger 2 | 3 | import javax.annotation.processing.Messager 4 | import javax.tools.Diagnostic.Kind.ERROR 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | internal class Logger(messager: Messager) : Messager by messager { 11 | 12 | fun error(message: String) { 13 | printMessage(ERROR, message) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/assets/architecture.png -------------------------------------------------------------------------------- /assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/assets/demo.gif -------------------------------------------------------------------------------- /assets/graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/assets/graphql.png -------------------------------------------------------------------------------- /assets/kmm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/assets/kmm.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/assets/logo.png -------------------------------------------------------------------------------- /assets/mad_scorecard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/assets/mad_scorecard.png -------------------------------------------------------------------------------- /assets/playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/assets/playstore.png -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | allprojects { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | maven { 8 | setUrl("https://www.jetbrains.com/intellij-repository/releases") 9 | setUrl("https://jetbrains.bintray.com/intellij-third-party-dependencies") 10 | } 11 | } 12 | } 13 | 14 | tasks.register("clean", Delete::class) { 15 | delete(rootProject.buildDir) 16 | } 17 | -------------------------------------------------------------------------------- /buildSrc/buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/buildSrc/src/main/kotlin/Application.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by ErickSumargo on 15/04/21. 3 | */ 4 | 5 | object Application { 6 | const val id: String = "com.bael.dads" 7 | 8 | const val versionCode: Int = 2 9 | 10 | const val versionName: String = "1.1" 11 | 12 | const val compileSdk: Int = 30 13 | 14 | const val targetSdk: Int = 30 15 | 16 | const val minSdk: Int = 23 17 | } 18 | -------------------------------------------------------------------------------- /buildSrc/buildSrc/src/main/kotlin/Plugin.kt: -------------------------------------------------------------------------------- 1 | import Version.Android.gradle as androidGradleVersion 2 | import Version.Apollo.apollo as apolloVersion 3 | import Version.Google.dagger as daggerVersion 4 | import Version.Google.firebaseCrashlytics as firebaseCrashlyticsVersion 5 | import Version.Google.gms as gmsVersion 6 | import Version.KotlinX.kotlin as kotlinVersion 7 | import Version.Square.sqlDelight as sqlDelightVersion 8 | 9 | /** 10 | * Created by ErickSumargo on 15/04/21. 11 | */ 12 | 13 | object Plugin { 14 | 15 | object Android { 16 | val gradle: String 17 | get() = "com.android.tools.build:gradle:$androidGradleVersion" 18 | } 19 | 20 | object Apollo { 21 | val apollo: String 22 | get() = "com.apollographql.apollo:apollo-gradle-plugin:$apolloVersion" 23 | } 24 | 25 | object Google { 26 | val firebaseCrashlytics: String 27 | get() = "com.google.firebase:firebase-crashlytics-gradle:$firebaseCrashlyticsVersion" 28 | 29 | val dagger: String 30 | get() = "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" 31 | 32 | val gms: String 33 | get() = "com.google.gms:google-services:$gmsVersion" 34 | } 35 | 36 | object KotlinX { 37 | val kotlin: String 38 | get() = "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 39 | } 40 | 41 | object Square { 42 | val sqlDelight: String 43 | get() = "com.squareup.sqldelight:gradle-plugin:$sqlDelightVersion" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/plugin/shared/DataModulePlugin.kt: -------------------------------------------------------------------------------- 1 | package plugin.shared 2 | 3 | /** 4 | * Created by ErickSumargo on 01/05/21. 5 | */ 6 | 7 | class DataModulePlugin : SharedModulePlugin() 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/plugin/shared/DomainModulePlugin.kt: -------------------------------------------------------------------------------- 1 | package plugin.shared 2 | 3 | /** 4 | * Created by ErickSumargo on 15/04/21. 5 | */ 6 | 7 | class DomainModulePlugin : SharedModulePlugin() 8 | -------------------------------------------------------------------------------- /data/database/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.db -------------------------------------------------------------------------------- /data/database/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.Square.sqlDelightAndroidDriver 2 | import Library.Square.sqlDelightCoroutines 3 | 4 | plugins { 5 | id("data") 6 | id("com.squareup.sqldelight") 7 | } 8 | 9 | kotlin { 10 | sourceSets { 11 | val commonMain by getting { 12 | dependencies { 13 | // Square 14 | implementation(sqlDelightCoroutines) 15 | } 16 | 17 | dependencies { 18 | // Shared 19 | implementation(project(":shared")) 20 | } 21 | } 22 | 23 | val androidMain by getting { 24 | dependencies { 25 | // Square 26 | implementation(sqlDelightAndroidDriver) 27 | } 28 | } 29 | } 30 | } 31 | 32 | sqldelight { 33 | database("DadsDatabase") { 34 | packageName = "${Application.id}.data.database" 35 | schemaOutputDirectory = file("schemas") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /data/database/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/data/database/consumer-rules.pro -------------------------------------------------------------------------------- /data/database/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/database/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/database/src/androidMain/kotlin/com/bael/dads/data/database/di/module/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.database.di.module 2 | 3 | import android.content.Context 4 | import com.bael.dads.data.database.DadsDatabase 5 | import com.bael.dads.data.database.constant.Database.name 6 | import com.bael.dads.data.database.constant.Database.schema 7 | import com.squareup.sqldelight.android.AndroidSqliteDriver 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.android.qualifiers.ApplicationContext 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | object DatabaseModule { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideDadsDatabase(@ApplicationContext context: Context): DadsDatabase { 26 | val driver = AndroidSqliteDriver(schema, context, name) 27 | return DadsDatabase(driver) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /data/database/src/androidMain/kotlin/com/bael/dads/data/database/di/module/repository/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.database.di.module.repository 2 | 3 | import com.bael.dads.data.database.DadsDatabase 4 | import com.bael.dads.data.database.repository.DadJokeRepository 5 | import com.bael.dads.data.database.repository.DefaultDadJokeRepository 6 | import com.bael.dads.data.database.repository.DefaultRemoteMetaRepository 7 | import com.bael.dads.data.database.repository.RemoteMetaRepository 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | /** 15 | * Created by ErickSumargo on 01/04/21. 16 | */ 17 | 18 | @Module 19 | @InstallIn(SingletonComponent::class) 20 | internal object RepositoryModule { 21 | 22 | @Provides 23 | @Singleton 24 | fun provideDadJokeRepository(database: DadsDatabase): DadJokeRepository { 25 | return DefaultDadJokeRepository(database) 26 | } 27 | 28 | @Provides 29 | @Singleton 30 | fun provideRemoteMetaRepository(database: DadsDatabase): RemoteMetaRepository { 31 | return DefaultRemoteMetaRepository(database) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /data/database/src/commonMain/kotlin/com/bael/dads/data/database/constant/Database.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.database.constant 2 | 3 | import com.squareup.sqldelight.db.SqlDriver.Schema 4 | import com.bael.dads.data.database.DadsDatabase.Companion.Schema as DadsDatabaseSchema 5 | 6 | /** 7 | * Created by ErickSumargo on 01/05/21. 8 | */ 9 | 10 | internal object Database { 11 | const val name: String = "dads.db" 12 | 13 | val schema: Schema 14 | get() = DadsDatabaseSchema 15 | } 16 | -------------------------------------------------------------------------------- /data/database/src/commonMain/kotlin/com/bael/dads/data/database/repository/DadJokeRepository.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.database.repository 2 | 3 | import com.bael.dads.data.database.entity.DadJoke 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | /** 7 | * Created by ErickSumargo on 01/04/21. 8 | */ 9 | 10 | interface DadJokeRepository { 11 | 12 | suspend fun insertDadJokes(dadJokes: List) 13 | 14 | suspend fun loadDadJokeFeed(id: Int, limit: Int): List 15 | 16 | suspend fun loadSeenDadJoke(term: String, cursor: Int, limit: Int): List 17 | 18 | suspend fun loadFavoredDadJoke(term: String, cursor: Long, limit: Int): List 19 | 20 | suspend fun loadDadJoke(id: Int): DadJoke? 21 | 22 | suspend fun loadLatestDadJoke(): DadJoke? 23 | 24 | suspend fun observeDadJoke(id: Int): Flow 25 | 26 | suspend fun setDadJokeSeen(id: Int): Int 27 | 28 | suspend fun favorDadJoke(id: Int, favored: Boolean): Int 29 | 30 | suspend fun deleteAllDadJokes(): Int 31 | } 32 | -------------------------------------------------------------------------------- /data/database/src/commonMain/kotlin/com/bael/dads/data/database/repository/DefaultRemoteMetaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.database.repository 2 | 3 | import com.bael.dads.data.database.DadsDatabase 4 | import com.bael.dads.data.database.entity.RemoteMeta 5 | 6 | /** 7 | * Created by ErickSumargo on 01/04/21. 8 | */ 9 | 10 | internal class DefaultRemoteMetaRepository(database: DadsDatabase) : 11 | RemoteMetaRepository, 12 | DadsDatabase by database { 13 | 14 | override suspend fun loadRemoteMeta(): RemoteMeta? { 15 | return remoteMetaQueries.loadRemoteMeta() 16 | .executeAsOneOrNull() 17 | } 18 | 19 | override suspend fun insertRemoteMeta(remoteMeta: RemoteMeta): Int { 20 | remoteMetaQueries.insertRemoteMeta(cursor = remoteMeta.cursor) 21 | return 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /data/database/src/commonMain/kotlin/com/bael/dads/data/database/repository/RemoteMetaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.database.repository 2 | 3 | import com.bael.dads.data.database.entity.RemoteMeta 4 | 5 | /** 6 | * Created by ErickSumargo on 01/04/21. 7 | */ 8 | 9 | interface RemoteMetaRepository { 10 | 11 | suspend fun loadRemoteMeta(): RemoteMeta? 12 | 13 | suspend fun insertRemoteMeta(remoteMeta: RemoteMeta): Int 14 | } 15 | -------------------------------------------------------------------------------- /data/database/src/commonMain/sqldelight/com/bael/dads/data/database/entity/DadJoke.sq: -------------------------------------------------------------------------------- 1 | CREATE TABLE dadJoke ( 2 | id INTEGER AS Int PRIMARY KEY AUTOINCREMENT, 3 | jokeId TEXT NOT NULL, 4 | setup TEXT NOT NULL, 5 | punchline TEXT NOT NULL, 6 | favored INTEGER AS Boolean NOT NULL, 7 | seen INTEGER AS Boolean NOT NULL, 8 | createdAt INTEGER NOT NULL, 9 | updatedAt INTEGER NOT NULL 10 | ); 11 | 12 | insertDadJoke: 13 | INSERT INTO dadJoke (jokeId, setup, punchline, favored, seen, createdAt, updatedAt) 14 | VALUES (?, ?, ?, ?, ?, ?, ?); 15 | 16 | loadDadJokeFeed: 17 | SELECT * FROM dadJoke WHERE id > :id AND seen == 0 LIMIT :limit; 18 | 19 | loadSeenDadJoke: 20 | SELECT * FROM dadJoke WHERE id < :cursor AND setup LIKE '%' || :term || '%' AND seen ORDER BY id DESC LIMIT :limit; 21 | 22 | loadFavoredDadJoke: 23 | SELECT * FROM dadJoke WHERE updatedAt < :updatedAt AND setup LIKE '%' || :term || '%' AND favored ORDER BY updatedAt DESC LIMIT :limit; 24 | 25 | loadDadJoke: 26 | SELECT * FROM dadJoke WHERE id = :id; 27 | 28 | loadLatestDadJoke: 29 | SELECT * FROM dadJoke ORDER BY id DESC LIMIT 1; 30 | 31 | observeDadJoke: 32 | SELECT * FROM dadJoke WHERE id = :id; 33 | 34 | setDadJokeSeen: 35 | UPDATE dadJoke SET seen = 1, updatedAt = :updatedAt WHERE id = :id; 36 | 37 | favorDadJoke: 38 | UPDATE dadJoke SET favored = :favored, updatedAt = :updatedAt WHERE id = :id; 39 | 40 | deleteAllDadJokes: 41 | DELETE FROM dadJoke; 42 | -------------------------------------------------------------------------------- /data/database/src/commonMain/sqldelight/com/bael/dads/data/database/entity/RemoteMeta.sq: -------------------------------------------------------------------------------- 1 | CREATE TABLE remoteMeta ( 2 | id INTEGER AS Int PRIMARY KEY AUTOINCREMENT, 3 | cursor TEXT 4 | ); 5 | 6 | loadRemoteMeta: 7 | SELECT * FROM remoteMeta ORDER BY id DESC LIMIT 1; 8 | 9 | insertRemoteMeta: 10 | INSERT INTO remoteMeta (cursor) 11 | VALUES (?); 12 | -------------------------------------------------------------------------------- /data/database_test/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/database_test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.Google.daggerTesting 2 | import Library.Square.sqlDelightAndroidDriver 3 | 4 | plugins { 5 | id("data") 6 | } 7 | 8 | kotlin { 9 | sourceSets { 10 | val commonMain by getting { 11 | dependencies { 12 | // Data 13 | implementation(project(":data:database")) 14 | } 15 | } 16 | 17 | val androidMain by getting { 18 | dependencies { 19 | // Google 20 | implementation(daggerTesting) 21 | 22 | // Square 23 | implementation(sqlDelightAndroidDriver) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/database_test/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/data/database_test/consumer-rules.pro -------------------------------------------------------------------------------- /data/database_test/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/database_test/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/database_test/src/androidMain/kotlin/com/bael/dads/data/database/test/di/module/DatabaseTestModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.database.test.di.module 2 | 3 | import android.content.Context 4 | import com.bael.dads.data.database.DadsDatabase 5 | import com.bael.dads.data.database.di.module.DatabaseModule 6 | import com.squareup.sqldelight.android.AndroidSqliteDriver 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import dagger.hilt.testing.TestInstallIn 12 | import javax.inject.Singleton 13 | import com.bael.dads.data.database.DadsDatabase.Companion.Schema as DadsTestSchema 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @TestInstallIn( 21 | components = [SingletonComponent::class], 22 | replaces = [DatabaseModule::class] 23 | ) 24 | internal object DatabaseTestModule { 25 | 26 | /** 27 | * SqlDelight current version doesn't support in-memory database for 28 | * Android's instrumentation testing requirement. 29 | * 30 | * Workaround is to create persistent database as production one. 31 | */ 32 | @Provides 33 | @Singleton 34 | fun provideDadsDatabase(@ApplicationContext context: Context): DadsDatabase { 35 | val driver = AndroidSqliteDriver(schema = DadsTestSchema, context, name = "dads_test.db") 36 | return DadsDatabase(driver) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/remote/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | schema.json -------------------------------------------------------------------------------- /data/remote/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.Apollo.apolloKotlin 2 | 3 | plugins { 4 | id("data") 5 | id("com.apollographql.apollo") 6 | id("com.google.secrets_gradle_plugin") version Version.Google.secrets 7 | } 8 | 9 | kotlin { 10 | sourceSets { 11 | all { 12 | languageSettings.apply { 13 | useExperimentalAnnotation("com.apollographql.apollo.api.ApolloExperimental") 14 | } 15 | } 16 | 17 | val commonMain by getting { 18 | dependencies { 19 | // Apollo 20 | implementation(apolloKotlin) 21 | } 22 | 23 | dependencies { 24 | // Shared 25 | implementation(project(":shared")) 26 | } 27 | } 28 | 29 | val androidMain by getting 30 | } 31 | } 32 | 33 | apollo { 34 | generateKotlinModels.set(true) 35 | } 36 | 37 | secrets { 38 | propertiesFileName = "keys.properties" 39 | } 40 | -------------------------------------------------------------------------------- /data/remote/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/data/remote/consumer-rules.pro -------------------------------------------------------------------------------- /data/remote/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/remote/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /data/remote/src/androidMain/kotlin/com/bael/dads/data/remote/di/module/client/ApolloClientModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.di.module.client 2 | 3 | import com.apollographql.apollo.ApolloClient 4 | import com.apollographql.apollo.network.http.ApolloHttpNetworkTransport 5 | import com.bael.dads.data.remote.BuildConfig.JWT 6 | import com.bael.dads.data.remote.constant.Server.url 7 | import com.bael.dads.data.remote.interceptor.NetworkInterceptor 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | /** 15 | * Created by ErickSumargo on 01/05/21. 16 | */ 17 | 18 | @Module 19 | @InstallIn(SingletonComponent::class) 20 | internal object ApolloClientModule { 21 | 22 | @Provides 23 | @Singleton 24 | fun provideApolloClient(networkInterceptor: NetworkInterceptor): ApolloClient { 25 | return ApolloClient( 26 | networkTransport = ApolloHttpNetworkTransport( 27 | serverUrl = url, 28 | headers = mapOf( 29 | "Accept" to "application/json", 30 | "Authorization" to "Bearer $JWT" 31 | ) 32 | ), 33 | interceptors = listOf( 34 | networkInterceptor 35 | ) 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/remote/src/androidMain/kotlin/com/bael/dads/data/remote/di/module/interceptor/InterceptorModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.di.module.interceptor 2 | 3 | import com.bael.dads.data.remote.interceptor.NetworkInterceptor 4 | import com.bael.dads.data.remote.network.Network 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * Created by ErickSumargo on 01/05/21. 13 | */ 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | internal object InterceptorModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideNetworkInterceptor(network: Network): NetworkInterceptor { 22 | return NetworkInterceptor(network) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/remote/src/androidMain/kotlin/com/bael/dads/data/remote/di/module/mapper/ListMapperModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.di.module.mapper 2 | 3 | import com.bael.dads.data.remote.model.DadJoke 4 | import com.bael.dads.shared.mapper.ListMapper 5 | import com.bael.dads.shared.mapper.Mapper 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | import com.bael.dads.data.remote.query.DadJokesQuery.Joke as DadJokeQL 12 | 13 | /** 14 | * Created by ErickSumargo on 01/05/21. 15 | */ 16 | 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | internal object ListMapperModule { 20 | 21 | @Provides 22 | @Singleton 23 | fun provideDadJokeListMapper( 24 | mapper: Mapper 25 | ): ListMapper { 26 | return ListMapper(mapper) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /data/remote/src/androidMain/kotlin/com/bael/dads/data/remote/di/module/mapper/MapperModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.di.module.mapper 2 | 3 | import com.bael.dads.data.remote.mapper.DadJokeMapper 4 | import com.bael.dads.data.remote.mapper.DadJokesResponseMapper 5 | import com.bael.dads.data.remote.model.DadJoke 6 | import com.bael.dads.data.remote.response.DadJokesResponse 7 | import com.bael.dads.shared.mapper.ListMapper 8 | import com.bael.dads.shared.mapper.Mapper 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | import com.bael.dads.data.remote.query.DadJokesQuery.Data as DadJokesQueryData 15 | import com.bael.dads.data.remote.query.DadJokesQuery.Joke as DadJokeQL 16 | 17 | /** 18 | * Created by ErickSumargo on 01/01/21. 19 | */ 20 | 21 | @Module 22 | @InstallIn(SingletonComponent::class) 23 | internal object MapperModule { 24 | 25 | @Provides 26 | @Singleton 27 | fun provideDadJokeMapper(): Mapper { 28 | return DadJokeMapper() 29 | } 30 | 31 | @Provides 32 | @Singleton 33 | fun provideDadJokesResponseMapper( 34 | listMapper: ListMapper 35 | ): Mapper { 36 | return DadJokesResponseMapper(listMapper) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/remote/src/androidMain/kotlin/com/bael/dads/data/remote/di/module/mapper/ResultMapperModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.di.module.mapper 2 | 3 | import com.bael.dads.data.remote.model.DadJoke 4 | import com.bael.dads.data.remote.response.DadJokesResponse 5 | import com.bael.dads.shared.mapper.Mapper 6 | import com.bael.dads.shared.mapper.ResultMapper 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 | import com.bael.dads.data.remote.query.DadJokesQuery.Data as DadJokesQueryData 13 | import com.bael.dads.data.remote.query.DadJokesQuery.Joke as DadJokeQL 14 | 15 | /** 16 | * Created by ErickSumargo on 01/05/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | internal object ResultMapperModule { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideDadJokeResultMapper( 26 | mapper: Mapper 27 | ): ResultMapper { 28 | return ResultMapper(mapper) 29 | } 30 | 31 | @Provides 32 | @Singleton 33 | fun provideDadJokeResponseResultMapper( 34 | mapper: Mapper 35 | ): ResultMapper { 36 | return ResultMapper(mapper) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/remote/src/androidMain/kotlin/com/bael/dads/data/remote/di/module/service/ServiceModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.di.module.service 2 | 3 | import com.apollographql.apollo.ApolloClient 4 | import com.bael.dads.data.remote.response.DadJokesResponse 5 | import com.bael.dads.data.remote.service.DadsApolloService 6 | import com.bael.dads.data.remote.service.DadsService 7 | import com.bael.dads.shared.mapper.ResultMapper 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | import com.bael.dads.data.remote.query.DadJokesQuery.Data as DadJokesQueryData 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | @Module 20 | @InstallIn(SingletonComponent::class) 21 | object ServiceModule { 22 | 23 | @Provides 24 | @Singleton 25 | fun provideDadsService( 26 | client: ApolloClient, 27 | mapper: ResultMapper 28 | ): DadsService { 29 | return DadsApolloService(client, mapper) 30 | } 31 | } -------------------------------------------------------------------------------- /data/remote/src/androidMain/kotlin/com/bael/dads/data/remote/network/Network.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION", "RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") 2 | 3 | package com.bael.dads.data.remote.network 4 | 5 | import android.content.Context 6 | import android.content.Context.CONNECTIVITY_SERVICE 7 | import android.net.ConnectivityManager 8 | import android.net.ConnectivityManager.TYPE_MOBILE 9 | import android.net.ConnectivityManager.TYPE_WIFI 10 | import android.net.NetworkInfo 11 | import android.net.NetworkInfo.State.CONNECTED 12 | import dagger.hilt.android.qualifiers.ApplicationContext 13 | import javax.inject.Inject 14 | 15 | /** 16 | * Created by ErickSumargo on 01/01/21. 17 | */ 18 | 19 | actual class Network @Inject constructor(@ApplicationContext context: Context) { 20 | actual val isConnected: Boolean 21 | get() = isNetworkConnected(TYPE_MOBILE) || isNetworkConnected(TYPE_WIFI) 22 | 23 | private val connectivityManager: ConnectivityManager by lazy { 24 | context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager 25 | } 26 | 27 | private fun isNetworkConnected(networkType: Int): Boolean { 28 | return networkInfo(networkType) == CONNECTED 29 | } 30 | 31 | private fun networkInfo(type: Int): NetworkInfo.State? { 32 | return connectivityManager.getNetworkInfo(type)?.state 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/graphql/com/bael/dads/data/remote/fragment/DadJokeFragment.graphql: -------------------------------------------------------------------------------- 1 | fragment DadJokeFragment on DadJoke { 2 | id 3 | setup 4 | punchline 5 | } 6 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/graphql/com/bael/dads/data/remote/query/DadJokesQuery.graphql: -------------------------------------------------------------------------------- 1 | query DadJokes($cursor: String, $limit: Int) { 2 | dadJokes( 3 | cursor: $cursor 4 | limit: $limit 5 | ) { 6 | jokes { 7 | ...DadJokeFragment 8 | } 9 | cursor 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/constant/Server.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.constant 2 | 3 | /** 4 | * Created by ErickSumargo on 01/05/21. 5 | */ 6 | 7 | internal object Server { 8 | const val url: String = "https://dads-engine.herokuapp.com/" 9 | } 10 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/interceptor/NetworkInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.interceptor 2 | 3 | import com.apollographql.apollo.api.Operation 4 | import com.apollographql.apollo.interceptor.ApolloInterceptorChain 5 | import com.apollographql.apollo.interceptor.ApolloRequest 6 | import com.apollographql.apollo.interceptor.ApolloRequestInterceptor 7 | import com.apollographql.apollo.interceptor.ApolloResponse 8 | import com.bael.dads.data.remote.network.Network 9 | import com.bael.dads.shared.exception.NoNetworkException 10 | import kotlinx.coroutines.flow.Flow 11 | 12 | /** 13 | * Created by ErickSumargo on 01/05/21. 14 | */ 15 | 16 | internal class NetworkInterceptor(private val network: Network) : ApolloRequestInterceptor { 17 | 18 | override fun intercept( 19 | request: ApolloRequest, 20 | chain: ApolloInterceptorChain 21 | ): Flow> { 22 | if (!network.isConnected) throw NoNetworkException() 23 | return chain.proceed(request) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/mapper/DadJokeMapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.mapper 2 | 3 | import com.bael.dads.data.remote.model.DadJoke 4 | import com.bael.dads.shared.mapper.Mapper 5 | import com.bael.dads.data.remote.query.DadJokesQuery.Joke as DadJokeQL 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | internal class DadJokeMapper : Mapper { 12 | 13 | override fun map(data: DadJokeQL): DadJoke { 14 | return data.fragments.dadJokeFragment.let { fragment -> 15 | DadJoke( 16 | id = fragment.id, 17 | setup = fragment.setup, 18 | punchline = fragment.punchline 19 | ) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/mapper/DadJokesResponseMapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.mapper 2 | 3 | import com.bael.dads.data.remote.model.DadJoke 4 | import com.bael.dads.data.remote.response.DadJokesResponse 5 | import com.bael.dads.shared.mapper.ListMapper 6 | import com.bael.dads.shared.mapper.Mapper 7 | import com.bael.dads.data.remote.query.DadJokesQuery.Data as DadJokesQueryData 8 | import com.bael.dads.data.remote.query.DadJokesQuery.Joke as DadJokeQL 9 | 10 | /** 11 | * Created by ErickSumargo on 01/01/21. 12 | */ 13 | 14 | internal class DadJokesResponseMapper( 15 | private val dadJokesMapper: ListMapper, 16 | ) : Mapper { 17 | 18 | override fun map(data: DadJokesQueryData): DadJokesResponse { 19 | return DadJokesResponse( 20 | dadJokes = dadJokesMapper.map(data.dadJokes.jokes), 21 | cursor = data.dadJokes.cursor 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/model/DadJoke.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.model 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | data class DadJoke( 8 | val id: String, 9 | val setup: String, 10 | val punchline: String 11 | ) 12 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/network/Network.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.network 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | expect class Network { 8 | val isConnected: Boolean 9 | } 10 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/response/DadJokesResponse.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.response 2 | 3 | import com.bael.dads.data.remote.model.DadJoke 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | data class DadJokesResponse( 10 | val dadJokes: List, 11 | val cursor: String? 12 | ) 13 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/service/BaseApolloService.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.service 2 | 3 | import com.apollographql.apollo.ApolloClient 4 | import com.apollographql.apollo.api.Operation.Data 5 | import com.apollographql.apollo.api.Operation.Variables 6 | import com.apollographql.apollo.api.Query 7 | import kotlinx.coroutines.flow.first 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | internal abstract class BaseApolloService(private val client: ApolloClient) { 14 | 15 | protected suspend fun query(script: Query): Result { 16 | return runCatching { 17 | val response = client.query(script).execute().first() 18 | if (response.hasErrors()) { 19 | throw Exception(response.errors!![0].message) 20 | } 21 | response.data!! 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/service/DadsApolloService.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.service 2 | 3 | import com.apollographql.apollo.ApolloClient 4 | import com.apollographql.apollo.api.Input.Companion.fromNullable 5 | import com.bael.dads.data.remote.query.DadJokesQuery 6 | import com.bael.dads.data.remote.response.DadJokesResponse 7 | import com.bael.dads.shared.mapper.ResultMapper 8 | import com.bael.dads.data.remote.query.DadJokesQuery.Data as DadJokesQueryData 9 | 10 | /** 11 | * Created by ErickSumargo on 01/01/21. 12 | */ 13 | 14 | internal class DadsApolloService( 15 | client: ApolloClient, 16 | private val mapper: ResultMapper 17 | ) : BaseApolloService(client), 18 | DadsService { 19 | 20 | override suspend fun fetchDadJokes(cursor: String?, limit: Int): Result { 21 | return query( 22 | script = DadJokesQuery( 23 | cursor = fromNullable(cursor), 24 | limit = fromNullable(limit) 25 | ) 26 | ).let(mapper::map) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /data/remote/src/commonMain/kotlin/com/bael/dads/data/remote/service/DadsService.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.service 2 | 3 | import com.bael.dads.data.remote.response.DadJokesResponse 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | interface DadsService { 10 | 11 | suspend fun fetchDadJokes(cursor: String?, limit: Int): Result 12 | } 13 | -------------------------------------------------------------------------------- /data/remote_test/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /data/remote_test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import Library.Google.daggerTesting 2 | 3 | plugins { 4 | id("data") 5 | } 6 | 7 | kotlin { 8 | sourceSets { 9 | val commonMain by getting { 10 | dependencies { 11 | // Shared 12 | implementation(project(":shared")) 13 | 14 | // Data 15 | implementation(project(":data:remote")) 16 | } 17 | } 18 | 19 | val androidMain by getting { 20 | dependencies { 21 | // Google 22 | implementation(daggerTesting) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /data/remote_test/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/data/remote_test/consumer-rules.pro -------------------------------------------------------------------------------- /data/remote_test/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /data/remote_test/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /data/remote_test/src/androidMain/kotlin/com/bael/dads/data/remote/test/di/module/service/ServiceTestModule.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UNCHECKED_CAST") 2 | 3 | package com.bael.dads.data.remote.test.di.module.service 4 | 5 | import com.bael.dads.data.remote.di.module.service.ServiceModule 6 | import com.bael.dads.data.remote.response.DadJokesResponse 7 | import com.bael.dads.data.remote.service.DadsService 8 | import com.bael.dads.data.remote.test.service.FakeDadsService 9 | import com.bael.dads.data.remote.test.service.RemoteService 10 | import dagger.Module 11 | import dagger.Provides 12 | import dagger.hilt.components.SingletonComponent 13 | import dagger.hilt.testing.TestInstallIn 14 | import javax.inject.Singleton 15 | 16 | /** 17 | * Created by ErickSumargo on 01/01/21. 18 | */ 19 | 20 | @Module 21 | @TestInstallIn( 22 | components = [SingletonComponent::class], 23 | replaces = [ServiceModule::class] 24 | ) 25 | internal object ServiceTestModule { 26 | 27 | @Provides 28 | @Singleton 29 | fun provideDadsService(): DadsService { 30 | return FakeDadsService() 31 | } 32 | 33 | @Provides 34 | @Singleton 35 | fun provideFakeDadsService(service: DadsService): RemoteService { 36 | return service as RemoteService 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /data/remote_test/src/commonMain/kotlin/com/bael/dads/data/remote/test/service/FakeBaseService.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.test.service 2 | 3 | import com.bael.dads.shared.response.Response 4 | import com.bael.dads.shared.response.Response.Error 5 | import com.bael.dads.shared.response.Response.Success 6 | import kotlin.Result.Companion.failure 7 | import kotlin.Result.Companion.success 8 | 9 | /** 10 | * Created by ErickSumargo on 01/01/21. 11 | */ 12 | 13 | internal abstract class FakeBaseService : RemoteService { 14 | private var responses: Array> = arrayOf() 15 | 16 | private var count: Int = 0 17 | 18 | protected val result: Result 19 | get() { 20 | return when (val response = responses[count++]) { 21 | is Error -> failure(response.error as Throwable) 22 | is Success -> success(response.data) 23 | else -> failure(Exception("Connection error")) 24 | } 25 | } 26 | 27 | override fun submitResponses(vararg responses: Response) { 28 | this.responses = responses 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /data/remote_test/src/commonMain/kotlin/com/bael/dads/data/remote/test/service/FakeDadsService.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.test.service 2 | 3 | import com.bael.dads.data.remote.response.DadJokesResponse 4 | import com.bael.dads.data.remote.service.DadsService 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | internal class FakeDadsService : 11 | FakeBaseService(), 12 | DadsService { 13 | 14 | override suspend fun fetchDadJokes(cursor: String?, limit: Int): Result { 15 | return result 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /data/remote_test/src/commonMain/kotlin/com/bael/dads/data/remote/test/service/RemoteService.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.data.remote.test.service 2 | 3 | import com.bael.dads.shared.response.Response 4 | 5 | /** 6 | * Created by ErickSumargo on 01/01/21. 7 | */ 8 | 9 | interface RemoteService { 10 | 11 | fun submitResponses(vararg responses: Response) 12 | } 13 | -------------------------------------------------------------------------------- /domain/home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /domain/home/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("domain") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | val commonMain by getting { 8 | dependencies { 9 | // Shared 10 | implementation(project(":shared")) 11 | 12 | // Data 13 | implementation(project(":data:database")) 14 | implementation(project(":data:remote")) 15 | } 16 | } 17 | 18 | val androidMain by getting 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /domain/home/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/domain/home/consumer-rules.pro -------------------------------------------------------------------------------- /domain/home/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /domain/home/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /domain/home/src/androidMain/kotlin/com/bael/dads/domain/home/di/module/mapper/ListMapperModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.di.module.mapper 2 | 3 | import com.bael.dads.shared.mapper.ListMapper 4 | import com.bael.dads.shared.mapper.Mapper 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 11 | import com.bael.dads.data.remote.model.DadJoke as DadJokeRemote 12 | 13 | /** 14 | * Created by ErickSumargo on 01/05/21. 15 | */ 16 | 17 | @Module 18 | @InstallIn(SingletonComponent::class) 19 | internal object ListMapperModule { 20 | 21 | @Provides 22 | @Singleton 23 | fun provideDadJokesRemoteMapper( 24 | mapper: Mapper 25 | ): ListMapper { 26 | return ListMapper(mapper) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /domain/home/src/androidMain/kotlin/com/bael/dads/domain/home/di/module/mapper/MapperModule.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.di.module.mapper 2 | 3 | import com.bael.dads.data.database.entity.RemoteMeta 4 | import com.bael.dads.data.remote.response.DadJokesResponse 5 | import com.bael.dads.domain.home.mapper.DadJokeDBMapper 6 | import com.bael.dads.domain.home.mapper.DadJokeRemoteMapper 7 | import com.bael.dads.domain.home.mapper.RemoteMetaMapper 8 | import com.bael.dads.domain.home.model.DadJoke 9 | import com.bael.dads.shared.mapper.Mapper 10 | import dagger.Module 11 | import dagger.Provides 12 | import dagger.hilt.InstallIn 13 | import dagger.hilt.components.SingletonComponent 14 | import javax.inject.Singleton 15 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 16 | import com.bael.dads.data.remote.model.DadJoke as DadJokeRemote 17 | 18 | /** 19 | * Created by ErickSumargo on 01/01/21. 20 | */ 21 | 22 | @Module 23 | @InstallIn(SingletonComponent::class) 24 | internal object MapperModule { 25 | 26 | @Provides 27 | @Singleton 28 | fun provideDadJokeRemoteMapper(): Mapper { 29 | return DadJokeRemoteMapper() 30 | } 31 | 32 | @Provides 33 | @Singleton 34 | fun provideDadJokeDBMapper(): Mapper { 35 | return DadJokeDBMapper() 36 | } 37 | 38 | @Provides 39 | @Singleton 40 | fun provideRemoteMetaMapper(): Mapper { 41 | return RemoteMetaMapper() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /domain/home/src/androidMain/kotlin/com/bael/dads/domain/home/model/DadJoke.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.model 2 | 3 | import java.io.Serializable 4 | 5 | /** 6 | * Created by ErickSumargo on 01/05/21. 7 | */ 8 | 9 | actual data class DadJoke actual constructor( 10 | val id: Int, 11 | val setup: String, 12 | val punchline: String, 13 | val favored: Boolean, 14 | val seen: Boolean, 15 | val updatedAt: Long 16 | ) : Serializable 17 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/interactor/FavorDadJokeInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.interactor 2 | 3 | import com.bael.dads.data.database.repository.DadJokeRepository 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.domain.home.usecase.FavorDadJokeUseCase 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.flow 8 | 9 | /** 10 | * Created by ErickSumargo on 01/04/21. 11 | */ 12 | 13 | internal class FavorDadJokeInteractor( 14 | private val repository: DadJokeRepository 15 | ) : FavorDadJokeUseCase { 16 | 17 | override fun invoke(dadJoke: DadJoke, favored: Boolean): Flow { 18 | return flow { 19 | val updates = repository.favorDadJoke( 20 | id = dadJoke.id, 21 | favored = favored 22 | ) 23 | emit(updates > 0) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/interactor/LoadDadJokeInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.interactor 2 | 3 | import com.bael.dads.data.database.repository.DadJokeRepository 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.domain.home.usecase.LoadDadJokeUseCase 6 | import com.bael.dads.shared.mapper.Mapper 7 | import com.bael.dads.shared.response.Response 8 | import com.bael.dads.shared.response.Response.Loading 9 | import com.bael.dads.shared.response.Response.Success 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.flow 12 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 13 | 14 | /** 15 | * Created by ErickSumargo on 01/04/21. 16 | */ 17 | 18 | internal class LoadDadJokeInteractor( 19 | private val repository: DadJokeRepository, 20 | private val mapper: Mapper 21 | ) : LoadDadJokeUseCase { 22 | 23 | override fun invoke(id: Int): Flow> { 24 | return flow { 25 | emit(Loading) 26 | 27 | repository.loadDadJoke(id) 28 | ?.let(mapper::map) 29 | ?.let(::Success) 30 | ?.also { response -> 31 | emit(response) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/interactor/LoadFavoredDadJokeInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.interactor 2 | 3 | import com.bael.dads.data.database.repository.DadJokeRepository 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.domain.home.usecase.LoadFavoredDadJokeUseCase 6 | import com.bael.dads.shared.mapper.Mapper 7 | import com.bael.dads.shared.response.Response 8 | import com.bael.dads.shared.response.Response.Empty 9 | import com.bael.dads.shared.response.Response.Loading 10 | import com.bael.dads.shared.response.Response.Success 11 | import com.bael.dads.shared.time.DateTime.now 12 | import kotlinx.coroutines.flow.Flow 13 | import kotlinx.coroutines.flow.flow 14 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 15 | 16 | /** 17 | * Created by ErickSumargo on 01/04/21. 18 | */ 19 | 20 | internal class LoadFavoredDadJokeInteractor( 21 | private val repository: DadJokeRepository, 22 | private val mapper: Mapper 23 | ) : LoadFavoredDadJokeUseCase { 24 | 25 | override fun invoke(term: String, cursor: DadJoke?, limit: Int): Flow>> { 26 | return flow { 27 | emit(Loading) 28 | 29 | val dadJokes = loadFavoredDadJokeDB(term, cursor, limit) 30 | if (dadJokes.isEmpty()) { 31 | emit(Empty) 32 | } else { 33 | emit(Success(data = dadJokes)) 34 | } 35 | } 36 | } 37 | 38 | private suspend fun loadFavoredDadJokeDB( 39 | term: String, 40 | cursor: DadJoke?, 41 | limit: Int 42 | ): List { 43 | return repository.loadFavoredDadJoke(term, cursor = cursor?.updatedAt ?: now, limit) 44 | .map(mapper::map) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/interactor/LoadSeenDadJokeInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.interactor 2 | 3 | import com.bael.dads.data.database.repository.DadJokeRepository 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.domain.home.usecase.LoadSeenDadJokeUseCase 6 | import com.bael.dads.shared.mapper.Mapper 7 | import com.bael.dads.shared.response.Response 8 | import com.bael.dads.shared.response.Response.Empty 9 | import com.bael.dads.shared.response.Response.Loading 10 | import com.bael.dads.shared.response.Response.Success 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.flow 13 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 14 | 15 | /** 16 | * Created by ErickSumargo on 01/04/21. 17 | */ 18 | 19 | internal class LoadSeenDadJokeInteractor( 20 | private val repository: DadJokeRepository, 21 | private val mapper: Mapper 22 | ) : LoadSeenDadJokeUseCase { 23 | 24 | override fun invoke(term: String, cursor: DadJoke?, limit: Int): Flow>> { 25 | return flow { 26 | emit(Loading) 27 | 28 | val dadJokes = loadSeenDadJokeDB(term, cursor, limit) 29 | if (dadJokes.isEmpty()) { 30 | emit(Empty) 31 | } else { 32 | emit(Success(data = dadJokes)) 33 | } 34 | } 35 | } 36 | 37 | private suspend fun loadSeenDadJokeDB( 38 | term: String, 39 | cursor: DadJoke?, 40 | limit: Int 41 | ): List { 42 | return repository.loadSeenDadJoke( 43 | term = term, 44 | cursor = cursor?.id ?: (repository.loadLatestDadJoke()?.id ?: 0) + 1, 45 | limit = limit 46 | ).map(mapper::map) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/interactor/ObserveDadJokeInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.interactor 2 | 3 | import com.bael.dads.data.database.repository.DadJokeRepository 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.domain.home.usecase.ObserveDadJokeUseCase 6 | import com.bael.dads.shared.mapper.Mapper 7 | import com.bael.dads.shared.response.Response 8 | import com.bael.dads.shared.response.Response.Success 9 | import kotlinx.coroutines.flow.Flow 10 | import kotlinx.coroutines.flow.distinctUntilChanged 11 | import kotlinx.coroutines.flow.filterNotNull 12 | import kotlinx.coroutines.flow.map 13 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 14 | 15 | /** 16 | * Created by ErickSumargo on 01/04/21. 17 | */ 18 | 19 | internal class ObserveDadJokeInteractor( 20 | private val repository: DadJokeRepository, 21 | private val mapper: Mapper 22 | ) : ObserveDadJokeUseCase { 23 | 24 | override suspend fun invoke(dadJoke: DadJoke): Flow> { 25 | return repository.observeDadJoke(id = dadJoke.id) 26 | .filterNotNull() 27 | .map(mapper::map) 28 | .distinctUntilChanged() 29 | .map(::Success) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/interactor/SetDadJokeSeenInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.interactor 2 | 3 | import com.bael.dads.data.database.repository.DadJokeRepository 4 | import com.bael.dads.domain.home.model.DadJoke 5 | import com.bael.dads.domain.home.usecase.SetDadJokeSeenUseCase 6 | import kotlinx.coroutines.flow.Flow 7 | import kotlinx.coroutines.flow.flow 8 | 9 | /** 10 | * Created by ErickSumargo on 01/04/21. 11 | */ 12 | 13 | internal class SetDadJokeSeenInteractor( 14 | private val repository: DadJokeRepository 15 | ) : SetDadJokeSeenUseCase { 16 | 17 | override fun invoke(dadJoke: DadJoke): Flow { 18 | return flow { 19 | val updates = repository.setDadJokeSeen(id = dadJoke.id) 20 | emit(updates > 0) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/mapper/DadJokeDBMapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.mapper 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.shared.mapper.Mapper 5 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | internal class DadJokeDBMapper : Mapper { 12 | 13 | override fun map(data: DadJokeDB): DadJoke { 14 | return DadJoke( 15 | id = data.id, 16 | setup = data.setup, 17 | punchline = data.punchline, 18 | favored = data.favored, 19 | seen = data.seen, 20 | updatedAt = data.updatedAt 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/mapper/DadJokeRemoteMapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.mapper 2 | 3 | import com.bael.dads.shared.mapper.Mapper 4 | import com.bael.dads.shared.time.DateTime.now 5 | import com.bael.dads.data.database.entity.DadJoke as DadJokeDB 6 | import com.bael.dads.data.remote.model.DadJoke as DadJokeRemote 7 | 8 | /** 9 | * Created by ErickSumargo on 01/01/21. 10 | */ 11 | 12 | internal class DadJokeRemoteMapper : Mapper { 13 | 14 | override fun map(data: DadJokeRemote): DadJokeDB { 15 | return DadJokeDB( 16 | id = 0, // to be overridden by auto-increment key 17 | jokeId = data.id, 18 | setup = data.setup, 19 | punchline = data.punchline, 20 | favored = false, 21 | seen = false, 22 | createdAt = now, 23 | updatedAt = now 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/mapper/RemoteMetaMapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.mapper 2 | 3 | import com.bael.dads.data.database.entity.RemoteMeta 4 | import com.bael.dads.data.remote.response.DadJokesResponse 5 | import com.bael.dads.shared.mapper.Mapper 6 | 7 | /** 8 | * Created by ErickSumargo on 01/01/21. 9 | */ 10 | 11 | internal class RemoteMetaMapper : Mapper { 12 | 13 | override fun map(data: DadJokesResponse): RemoteMeta { 14 | return RemoteMeta( 15 | id = 0, // to be overridden by auto-increment key 16 | cursor = data.cursor 17 | ) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/model/DadJoke.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.model 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | expect class DadJoke( 8 | id: Int, 9 | setup: String, 10 | punchline: String, 11 | favored: Boolean, 12 | seen: Boolean, 13 | updatedAt: Long 14 | ) 15 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/usecase/FavorDadJokeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.usecase 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | /** 7 | * Created by ErickSumargo on 01/04/21. 8 | */ 9 | 10 | interface FavorDadJokeUseCase { 11 | 12 | operator fun invoke(dadJoke: DadJoke, favored: Boolean): Flow 13 | } 14 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/usecase/LoadDadJokeFeedUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.usecase 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.shared.response.Response 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** 8 | * Created by ErickSumargo on 01/04/21. 9 | */ 10 | 11 | interface LoadDadJokeFeedUseCase { 12 | 13 | operator fun invoke(cursor: DadJoke?, limit: Int): Flow>> 14 | } 15 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/usecase/LoadDadJokeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.usecase 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.shared.response.Response 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** 8 | * Created by ErickSumargo on 01/04/21. 9 | */ 10 | 11 | interface LoadDadJokeUseCase { 12 | 13 | operator fun invoke(id: Int): Flow> 14 | } 15 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/usecase/LoadFavoredDadJokeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.usecase 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.shared.response.Response 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** 8 | * Created by ErickSumargo on 01/04/21. 9 | */ 10 | 11 | interface LoadFavoredDadJokeUseCase { 12 | 13 | operator fun invoke(term: String, cursor: DadJoke?, limit: Int): Flow>> 14 | } 15 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/usecase/LoadSeenDadJokeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.usecase 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.shared.response.Response 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** 8 | * Created by ErickSumargo on 01/04/21. 9 | */ 10 | 11 | interface LoadSeenDadJokeUseCase { 12 | 13 | operator fun invoke(term: String, cursor: DadJoke?, limit: Int): Flow>> 14 | } 15 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/usecase/ObserveDadJokeUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.usecase 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import com.bael.dads.shared.response.Response 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | /** 8 | * Created by ErickSumargo on 01/04/21. 9 | */ 10 | 11 | interface ObserveDadJokeUseCase { 12 | 13 | suspend operator fun invoke(dadJoke: DadJoke): Flow> 14 | } 15 | -------------------------------------------------------------------------------- /domain/home/src/commonMain/kotlin/com/bael/dads/domain/home/usecase/SetDadJokeSeenUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.domain.home.usecase 2 | 3 | import com.bael.dads.domain.home.model.DadJoke 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | /** 7 | * Created by ErickSumargo on 01/04/21. 8 | */ 9 | 10 | interface SetDadJokeSeenUseCase { 11 | 12 | operator fun invoke(dadJoke: DadJoke): Flow 13 | } 14 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=false 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 09:16:22 WIB 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /keys.properties: -------------------------------------------------------------------------------- 1 | JWT="" -------------------------------------------------------------------------------- /shared/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /shared/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("shared") 3 | } 4 | -------------------------------------------------------------------------------- /shared/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErickSumargo/Dads/8eb44894518e23f535a69cbe0207ad034f6a282f/shared/consumer-rules.pro -------------------------------------------------------------------------------- /shared/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /shared/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /shared/src/androidMain/kotlin/com/bael/dads/shared/response/Response.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.response 2 | 3 | import java.io.Serializable 4 | 5 | /** 6 | * Created by ErickSumargo on 01/05/21. 7 | */ 8 | 9 | actual sealed class Response : Serializable { 10 | 11 | actual object Loading : Response() 12 | 13 | actual data class Error actual constructor( 14 | val error: Exception 15 | ) : Response() 16 | 17 | actual object Empty : Response() 18 | 19 | actual data class Success actual constructor( 20 | val data: T 21 | ) : Response() 22 | } 23 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/bael/dads/shared/exception/NoNetworkException.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.exception 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | class NoNetworkException : Exception() 8 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/bael/dads/shared/ext/ListExt.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.ext 2 | 3 | import com.bael.dads.shared.response.Response 4 | import com.bael.dads.shared.response.Response.Success 5 | 6 | /** 7 | * Created by ErickSumargo on 01/01/21. 8 | */ 9 | 10 | fun List>.findSuccess(): Success? { 11 | return firstOrNull { it is Success } as? Success 12 | } 13 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/bael/dads/shared/mapper/ListMapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.mapper 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | class ListMapper( 8 | private val mapper: Mapper 9 | ) : Mapper, List> { 10 | 11 | override fun map(data: List): List { 12 | return data.map(mapper::map) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/bael/dads/shared/mapper/Mapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.mapper 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | interface Mapper { 8 | 9 | fun map(data: I): O 10 | } 11 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/bael/dads/shared/mapper/ResultMapper.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.mapper 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | class ResultMapper( 8 | private val mapper: Mapper 9 | ) : Mapper, Result> { 10 | 11 | override fun map(data: Result): Result { 12 | return data.map(mapper::map) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/bael/dads/shared/response/Response.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.response 2 | 3 | /** 4 | * Created by ErickSumargo on 01/01/21. 5 | */ 6 | 7 | expect sealed class Response { 8 | 9 | object Loading : Response 10 | 11 | class Error(error: Exception) : Response 12 | 13 | object Empty : Response 14 | 15 | class Success(data: T) : Response 16 | } 17 | -------------------------------------------------------------------------------- /shared/src/commonMain/kotlin/com/bael/dads/shared/time/DateTime.kt: -------------------------------------------------------------------------------- 1 | package com.bael.dads.shared.time 2 | 3 | import kotlinx.datetime.Clock.System.now 4 | 5 | /** 6 | * Created by ErickSumargo on 01/05/21. 7 | */ 8 | 9 | object DateTime { 10 | val now: Long 11 | get() = now().toEpochMilliseconds() 12 | } 13 | --------------------------------------------------------------------------------