├── .circleci
└── config.yml
├── .gitignore
├── .idea
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── dictionaries
│ └── abdullah.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── keystores
│ └── debug.keystore
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── mobiaxe
│ │ └── wasd
│ │ └── ExampleInstrumentedTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── mobiaxe
│ │ └── wasd
│ │ ├── WASDApplication.kt
│ │ ├── dashboard
│ │ └── DashboardActivity.kt
│ │ ├── koin
│ │ └── Modules.kt
│ │ ├── navigation
│ │ ├── IntentLoader.kt
│ │ └── Navigation.kt
│ │ ├── splash
│ │ ├── data
│ │ │ ├── datasource
│ │ │ │ └── SplashCachedDataSource.kt
│ │ │ └── repository
│ │ │ │ └── SplashDataRepository.kt
│ │ ├── domain
│ │ │ ├── datasource
│ │ │ │ └── SplashCachedDataSourceImpl.kt
│ │ │ ├── repository
│ │ │ │ └── SplashRepository.kt
│ │ │ └── usecase
│ │ │ │ └── OnboardingPassed.kt
│ │ └── presentation
│ │ │ ├── SplashActivity.kt
│ │ │ └── SplashViewModel.kt
│ │ └── util
│ │ └── BindingUtils.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_launcher_background.xml
│ └── splash_screen.jpg
│ ├── font
│ ├── moon_bold_v2.otf
│ ├── moon_light_v2.otf
│ ├── panton_black.otf
│ ├── panton_bold.otf
│ ├── panton_light.otf
│ └── panton_regular.otf
│ ├── layout
│ └── activity_dashboard.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── navigation
│ └── nav_app_graph.xml
│ ├── values-v28
│ └── styles.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── text_appearances.xml
├── build.gradle.kts
├── buildSrc
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ ├── AndroidConfig.kt
│ ├── CoreVersion.kt
│ ├── GradleDependency.kt
│ ├── LibraryDependency.kt
│ ├── ModuleDependency.kt
│ ├── extension
│ ├── ProjectExt.kt
│ └── RepositoryHandler.kt
│ └── utils
│ └── BuildUtils.kt
├── core
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── mobiaxe
│ │ └── core
│ │ └── ExampleInstrumentedTest.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── mobiaxe
│ │ └── core
│ │ ├── cache
│ │ ├── PreferencesHelper.kt
│ │ ├── WASDCacheImpl.kt
│ │ ├── WASDCachedDataStore.kt
│ │ └── db
│ │ │ ├── CachedWASDDao.kt
│ │ │ ├── CoverConverter.kt
│ │ │ ├── ListConverter.kt
│ │ │ ├── WASDConstants.kt
│ │ │ └── WASDDatabase.kt
│ │ ├── data
│ │ ├── Resource.kt
│ │ ├── Status.kt
│ │ └── WASDModel.kt
│ │ ├── domain
│ │ ├── SimpleUseCase.kt
│ │ ├── SuspendUseCase.kt
│ │ └── SynchronouosUseCase.kt
│ │ ├── extension
│ │ ├── ContextExt.kt
│ │ ├── LifecycleExt.kt
│ │ └── UtilExt.kt
│ │ ├── koin
│ │ └── Modules.kt
│ │ ├── presentation
│ │ ├── BaseActivity.kt
│ │ ├── BaseFragment.kt
│ │ ├── BaseViewModel.kt
│ │ ├── DataBindingAdapter.kt
│ │ └── ListItemViewModel.kt
│ │ └── remote
│ │ ├── IGDBRequest.kt
│ │ ├── WASDRemoteDataStore.kt
│ │ ├── WASDRemoteImpl.kt
│ │ └── service
│ │ ├── WASDService.kt
│ │ └── WASDServiceFactory.kt
│ └── res
│ ├── layout
│ └── list_item.xml
│ └── values
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── home
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── mobiaxe
│ │ └── home
│ │ ├── data
│ │ ├── SectionData.kt
│ │ ├── datasource
│ │ │ ├── GamesPagingDataSource.kt
│ │ │ └── HomeRemoteDataSource.kt
│ │ └── repository
│ │ │ └── HomeDataRepository.kt
│ │ ├── domain
│ │ ├── datasource
│ │ │ ├── GamesDataSourceFactory.kt
│ │ │ └── HomeRemoteDataSourceImpl.kt
│ │ ├── repository
│ │ │ └── HomeRepository.kt
│ │ └── usecase
│ │ │ └── GetPopularGames.kt
│ │ ├── koin
│ │ └── Modules.kt
│ │ ├── presentation
│ │ ├── GameListAdapter.kt
│ │ ├── HomeFragment.kt
│ │ ├── HomeViewModel.kt
│ │ ├── RecentlyReleasedListAdapter.kt
│ │ └── SectionListDataAdapter.kt
│ │ └── util
│ │ └── BindingUtils.kt
│ └── res
│ ├── layout
│ ├── fragment_home.xml
│ ├── item_game.xml
│ ├── item_network_state.xml
│ ├── item_recently_released.xml
│ └── item_section.xml
│ └── values
│ └── strings.xml
├── keystore.properties
├── misc
├── home.jpg
├── onboarding_1.jpg
├── onboarding_2.jpg
├── onboarding_3.jpg
└── select_platform.jpg
├── onboarding
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── mobiaxe
│ │ └── onboarding
│ │ ├── data
│ │ ├── OnboardingData.kt
│ │ ├── datasource
│ │ │ └── OnboardingCachedDataSource.kt
│ │ └── repository
│ │ │ └── OnboardingDataRepository.kt
│ │ ├── domain
│ │ ├── datasource
│ │ │ └── OnboardingCachedDataSourceImpl.kt
│ │ ├── repository
│ │ │ └── OnboardingRepository.kt
│ │ └── usecase
│ │ │ └── SavePlatform.kt
│ │ ├── koin
│ │ └── Modules.kt
│ │ ├── presentation
│ │ ├── OnboardingActivity.kt
│ │ ├── OnboardingAdapter.kt
│ │ ├── OnboardingFragment.kt
│ │ ├── OnboardingViewModel.kt
│ │ ├── ParallaxPageTransformer.kt
│ │ └── SelectPlatformFragment.kt
│ │ └── util
│ │ └── BindingUtils.kt
│ └── res
│ ├── color
│ └── onboarding_continue_btn_color.xml
│ ├── drawable-xxxhdpi
│ ├── godofwar_onboarding_two.jpg
│ ├── platform_background.jpg
│ ├── rdr_onboarding_three.jpg
│ └── witcher_onboarding_one.jpg
│ ├── drawable
│ ├── ic_nintendo.xml
│ ├── ic_nintendo_colored.xml
│ ├── ic_nintendo_white.xml
│ ├── ic_onboarding_left.xml
│ ├── ic_onboarding_right.xml
│ ├── ic_pc_colored.xml
│ ├── ic_pc_white.xml
│ ├── ic_playstation.xml
│ ├── ic_ps4_colored.xml
│ ├── ic_ps4_white.xml
│ ├── ic_tick.xml
│ ├── ic_windows.xml
│ ├── ic_xbox.xml
│ ├── ic_xbox_colored.xml
│ ├── ic_xbox_white.xml
│ ├── tab_page_four_selector.xml
│ ├── tab_page_one_selector.xml
│ ├── tab_page_three_selector.xml
│ └── tab_page_two_selector.xml
│ ├── layout
│ ├── activity_onboarding.xml
│ ├── fragment_onboarding.xml
│ └── fragment_platform.xml
│ ├── values
│ ├── arrays.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── onboarding_scene.xml
└── settings.gradle.kts
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 |
3 | orbs:
4 | android: circleci/android@0.2.0
5 |
6 | jobs:
7 | build:
8 | filters:
9 | branches:
10 | only:
11 | - master
12 |
13 | working_directory: ~/Modular
14 |
15 | docker:
16 | - image: circleci/android:api-29
17 |
18 | environment:
19 | GRADLE_OPTS: -Xmx4g -Dorg.gradle.daemon=false
20 | JVM_OPTS: -Xmx4g
21 |
22 | executor: android/android
23 |
24 | steps:
25 | - checkout
26 | - run:
27 | name: Build Debug
28 | command: ./gradlew assembleDebug
29 | - run:
30 | name: Build
31 | command: ./gradlew build
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | WASD
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/dictionaries/abdullah.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | wasd
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Project description
2 | [](https://circleci.com/gh/abalta/WASD-Modular)
3 | [](https://kotlinlang.org)
4 | [](https://android-arsenal.com/api?level=21)
5 | [](https://developer.android.com/studio)
6 |
7 | This project is open source part for my ongoing development. I present modern Android application architecture that is modular with dynamic feature.
8 |
9 | ## Project characteristics
10 |
11 | This project brings to table set of best practices, tools, and solutions:
12 |
13 | * Modern architecture (feature modules, Clean Architecture, Model-View-ViewModel)
14 | * [Android Jetpack](https://developer.android.com/jetpack)
15 | * CI pipeline
16 | * Dependency Injection with Koin
17 | * Material design
18 |
19 | ## Tech-stack
20 |
21 | * Libraries and Frameworks
22 | * [Kotlin](https://kotlinlang.org/) + [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) - perform background operations
23 | * [Koin](https://insert-koin.io/) - dependency injection
24 | * [Retrofit](https://square.github.io/retrofit/) - networking
25 | * [Jetpack](https://developer.android.com/jetpack)
26 | * [Navigation](https://developer.android.com/topic/libraries/architecture/navigation/) - deal with whole in-app navigation
27 | * [LiveData](https://developer.android.com/topic/libraries/architecture/livedata) - notify views about database change
28 | * [Lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle) - perform action when lifecycle state changes
29 | * [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - store and manage UI-related data in a lifecycle conscious way
30 | * [Room](https://developer.android.com/topic/libraries/architecture/room) - persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.
31 | * [Pagination](https://developer.android.com/topic/libraries/architecture/paging) - helps you load and display small chunks of data at a time. Loading partial data on demand reduces usage of network bandwidth and system resources.
32 | * [Data Binding](https://developer.android.com/topic/libraries/data-binding) - allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.
33 | * [Stetho](http://facebook.github.io/stetho/) - application debugging tool
34 | * Architecture
35 | * Clean Architecture (at module level)
36 | * MVVM Pattern
37 | * [Dynamic feature modules](https://developer.android.com/studio/projects/dynamic-delivery)
38 | * Gradle
39 | * [Gradle Kotlin DSL](https://docs.gradle.org/current/userguide/kotlin_dsl.html)
40 |
41 | ## Getting Started
42 |
43 | This project uses IGDB Api for fetching datas. You need to get Api key for services the app uses.
44 |
45 | - IGDB: https://api-docs.igdb.com/#about
46 |
47 | When you obtain the key, you can provide them to the app by putting the following in the `gradle.properties` project root file:
48 |
49 | ```properties
50 | #IGDB API KEY
51 | apiToken =
52 | ```
53 |
54 | ## Architecture
55 |
56 | Each module has own Clean Architecture layers.
57 |
58 | - `:core` module only responsible for implement common dependencies, holds remote (Retrofit) and cache (Room) repositories.
59 | - `:onboarding` module is install-time delivery feature module, this module install when app is downloaded. (Uninstall this module is upcoming development progress.)
60 | - `:home` module is install-time delivery feature module, this module install when app is downloaded.
61 | - `:app` this is simple application module, this is responsible for navigate feature modules.
62 |
63 | ## Showroom - UI
64 |
65 | [OnboardingActivity](https://github.com/abalta/Kotlin-DynamicFeature-Clean/blob/master/onboarding/src/main/java/com/mobiaxe/onboarding/presentation/OnboardingActivity.kt)
66 |
67 | | Module | OnboardingFragments | ViewPager2 - TabLayout | MaterialAndroid | SelectPlatformFragment |
68 | |---------------|-----------------------------------------------|-----------------------------------------------|-----------------------------------------------|---------------------------------------------------------|
69 | | `:onboarding` |
|
|
|
|
70 |
71 | [DashboardActivity](https://github.com/abalta/Kotlin-DynamicFeature-Clean/blob/master/app/src/main/java/com/mobiaxe/wasd/dashboard/DashboardActivity.kt)
72 |
73 | | Module | HomeFragment - BottomNavigationView | Pagination - Horizontal RecyclerView | NavigationComponent |
74 | |---------------|-----------------------------------------------|-----------------------------------------------|-----------------------------------------------|
75 | | `:home` |
| | |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extension.getLocalProperty
2 |
3 | plugins {
4 | id(GradlePluginId.ANDROID_APPLICATION)
5 | id(GradlePluginId.KOTLIN_ANDROID)
6 | id(GradlePluginId.KOTLIN_ANDROID_EXTENSIONS)
7 | id(GradlePluginId.KOTLIN_KAPT)
8 | }
9 |
10 | android {
11 | compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION)
12 |
13 | defaultConfig {
14 | applicationId = AndroidConfig.APPLICATION_ID
15 | minSdkVersion(AndroidConfig.MIN_SDK_VERSION)
16 | targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION)
17 | buildToolsVersion(AndroidConfig.BUILD_TOOLS_VERSION)
18 |
19 | versionCode = AndroidConfig.VERSION_CODE
20 | versionName = AndroidConfig.VERSION_NAME
21 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
22 | }
23 |
24 | signingConfigs {
25 | create(BuildType.RELEASE) {
26 | keyAlias = getLocalProperty("keyAlias")
27 | keyPassword = getLocalProperty("keyPassword")
28 | storeFile = file(getLocalProperty("storeFile"))
29 | storePassword = getLocalProperty("storePassword")
30 | }
31 | getByName(BuildType.DEBUG) {
32 | keyAlias = SigningConfig.DEBUG.KEY_ALIAS
33 | keyPassword = SigningConfig.DEBUG.KEY_PASSWORD
34 | storeFile = file(SigningConfig.DEBUG.STORE_FILE)
35 | storePassword = SigningConfig.DEBUG.STORE_PASSWORD
36 | }
37 | }
38 |
39 | buildTypes {
40 | getByName(BuildType.DEBUG) {
41 | applicationIdSuffix = ".debug"
42 | versionNameSuffix = "-debug"
43 | isMinifyEnabled = false
44 | isDebuggable = true
45 | signingConfig = signingConfigs.getByName(BuildType.DEBUG)
46 | }
47 |
48 | getByName(BuildType.RELEASE) {
49 | isMinifyEnabled = true
50 | signingConfig = signingConfigs.getByName(BuildType.RELEASE)
51 | proguardFile(getDefaultProguardFile("proguard-android.txt"))
52 | proguardFile(file("proguard-rules.pro"))
53 | }
54 | }
55 |
56 | dataBinding {
57 | isEnabled = true
58 | }
59 |
60 | dynamicFeatures = mutableSetOf(
61 | ModuleDependency.FEATURE_ONBOARDING,
62 | ModuleDependency.FEATURE_HOME)
63 | }
64 |
65 | dependencies {
66 | api(project(ModuleDependency.CORE))
67 |
68 | implementation(Library.NAVIGATION_FRAGMENT_KTX)
69 | implementation(Library.NAVIGATION_DYNAMIC_FEATURE)
70 | implementation(Library.NAVIGATION_UI_KTX)
71 |
72 | implementation(Library.STETHO)
73 | }
74 |
--------------------------------------------------------------------------------
/app/keystores/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/keystores/debug.keystore
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.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
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/mobiaxe/wasd/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mobiaxe.wasd", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/WASDApplication.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd
2 |
3 | import android.app.Application
4 | import com.facebook.stetho.Stetho
5 | import com.mobiaxe.core.koin.coreModule
6 | import com.mobiaxe.wasd.koin.repositoryModule
7 | import com.mobiaxe.wasd.koin.useCaseModule
8 | import com.mobiaxe.wasd.koin.viewModelModule
9 | import org.koin.android.ext.koin.androidContext
10 | import org.koin.core.context.startKoin
11 |
12 | class WASDApplication: Application() {
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 | startKoin {
17 | androidContext(this@WASDApplication)
18 | modules(listOf(
19 | coreModule,
20 | viewModelModule,
21 | useCaseModule,
22 | repositoryModule
23 | ))
24 | }
25 | Stetho.initializeWithDefaults(this)
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/dashboard/DashboardActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.dashboard
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.databinding.DataBindingUtil
6 | import androidx.navigation.Navigation
7 | import androidx.navigation.ui.setupWithNavController
8 | import com.mobiaxe.wasd.R
9 | import com.mobiaxe.wasd.databinding.ActivityDashboardBinding
10 |
11 | class DashboardActivity : AppCompatActivity() {
12 |
13 | private var binding: ActivityDashboardBinding? = null
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | binding = DataBindingUtil.setContentView(this, R.layout.activity_dashboard)
18 | setupBottomNavigation()
19 | }
20 |
21 | private fun setupBottomNavigation() {
22 | binding?.let {
23 | Navigation.findNavController(this, R.id.nav_host_fragment).apply {
24 | it.bottomNav.setupWithNavController(this)
25 | }
26 | }
27 | }
28 |
29 | override fun onBackPressed() {
30 | finishAfterTransition()
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/koin/Modules.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.koin
2 |
3 | import com.mobiaxe.wasd.splash.data.datasource.SplashCachedDataSource
4 | import com.mobiaxe.wasd.splash.data.repository.SplashDataRepository
5 | import com.mobiaxe.wasd.splash.domain.datasource.SplashCachedDataSourceImpl
6 | import com.mobiaxe.wasd.splash.domain.repository.SplashRepository
7 | import com.mobiaxe.wasd.splash.domain.usecase.OnboardingPassed
8 | import com.mobiaxe.wasd.splash.presentation.SplashViewModel
9 | import org.koin.androidx.viewmodel.dsl.viewModel
10 | import org.koin.dsl.module
11 |
12 | val viewModelModule = module {
13 | viewModel { SplashViewModel(get()) }
14 | }
15 |
16 | val useCaseModule = module {
17 | factory { OnboardingPassed(get()) }
18 | }
19 |
20 | val repositoryModule = module {
21 | factory { SplashDataRepository(get()) }
22 | factory { SplashCachedDataSourceImpl(get()) }
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/navigation/IntentLoader.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.navigation
2 |
3 | import android.content.Intent
4 | import com.mobiaxe.wasd.BuildConfig
5 |
6 | private fun intentTo(className: String): Intent =
7 | Intent(Intent.ACTION_VIEW).setClassName(BuildConfig.APPLICATION_ID, className)
8 |
9 | internal fun String.loadIntentOrNull(): Intent? =
10 | try {
11 | Class.forName(this).run { intentTo(this@loadIntentOrNull) }
12 | } catch (e: ClassNotFoundException) {
13 | null
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/navigation/Navigation.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.navigation
2 |
3 | import android.content.Intent
4 |
5 | object Navigation {
6 |
7 | private const val ONBOARDING = "com.mobiaxe.onboarding.presentation.OnboardingActivity"
8 | private const val DASHBOARD = "com.mobiaxe.wasd.dashboard.DashboardActivity"
9 |
10 | fun navigateOnboardingOrDashboard(isNavigateDashboard: Boolean): Intent? =
11 | if (isNavigateDashboard) {
12 | DASHBOARD.loadIntentOrNull()
13 | } else {
14 | ONBOARDING.loadIntentOrNull()
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/splash/data/datasource/SplashCachedDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.splash.data.datasource
2 |
3 | interface SplashCachedDataSource {
4 |
5 | fun isOnboardingShown(isShowOnboarding: Boolean)
6 |
7 | fun isOnboardingPassed(): Boolean
8 |
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/splash/data/repository/SplashDataRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.splash.data.repository
2 |
3 | import com.mobiaxe.wasd.splash.data.datasource.SplashCachedDataSource
4 | import com.mobiaxe.wasd.splash.domain.repository.SplashRepository
5 |
6 | class SplashDataRepository(private val splashCachedDataSource: SplashCachedDataSource): SplashRepository {
7 |
8 | override fun isOnboardingShow(isShown: Boolean) {
9 | splashCachedDataSource.isOnboardingShown(isShown)
10 | }
11 |
12 | override fun isOnboardingPass(): Boolean =
13 | splashCachedDataSource.isOnboardingPassed()
14 |
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/splash/domain/datasource/SplashCachedDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.splash.domain.datasource
2 |
3 | import com.mobiaxe.core.cache.PreferencesHelper
4 | import com.mobiaxe.wasd.splash.data.datasource.SplashCachedDataSource
5 |
6 | class SplashCachedDataSourceImpl constructor(private val preferencesHelper: PreferencesHelper): SplashCachedDataSource {
7 |
8 | override fun isOnboardingShown(isShowOnboarding: Boolean) {
9 | preferencesHelper.onboardingShow = isShowOnboarding
10 | }
11 |
12 | override fun isOnboardingPassed(): Boolean =
13 | preferencesHelper.onboardingShow
14 |
15 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/splash/domain/repository/SplashRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.splash.domain.repository
2 |
3 | interface SplashRepository {
4 |
5 | fun isOnboardingShow(isShown: Boolean)
6 |
7 | fun isOnboardingPass(): Boolean
8 |
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/splash/domain/usecase/OnboardingPassed.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.splash.domain.usecase
2 |
3 | import com.mobiaxe.core.domain.SynchronouosUseCase
4 | import com.mobiaxe.wasd.splash.domain.repository.SplashRepository
5 |
6 | class OnboardingPassed(
7 | private val splashRepository: SplashRepository
8 | ): SynchronouosUseCase {
9 |
10 | override fun execute(params: Boolean): Boolean {
11 | return if (splashRepository.isOnboardingPass()) {
12 | true
13 | } else {
14 | splashRepository.isOnboardingShow(params)
15 | params
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/splash/presentation/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.splash.presentation
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.mobiaxe.core.extension.observe
6 | import com.mobiaxe.core.extension.start
7 | import com.mobiaxe.wasd.navigation.Navigation
8 | import org.koin.androidx.viewmodel.ext.android.getViewModel
9 |
10 | class SplashActivity: AppCompatActivity() {
11 |
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 |
15 | with(getViewModel()) {
16 | isOnboardingPassed()
17 | observe(showOnboardingLiveData) {
18 | start(Navigation.navigateOnboardingOrDashboard(it))
19 | finish()
20 | }
21 | }
22 |
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/splash/presentation/SplashViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.splash.presentation
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import com.mobiaxe.core.presentation.BaseViewModel
6 | import com.mobiaxe.wasd.splash.domain.usecase.OnboardingPassed
7 |
8 | class SplashViewModel(
9 | private val onboardingPassed: OnboardingPassed
10 | ): BaseViewModel() {
11 | private val _showOnboardingLiveData = MutableLiveData()
12 | val showOnboardingLiveData: LiveData = _showOnboardingLiveData
13 |
14 | fun isOnboardingPassed() {
15 | _showOnboardingLiveData.value = onboardingPassed.execute(false)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mobiaxe/wasd/util/BindingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.wasd.util
2 |
3 | import androidx.databinding.BindingAdapter
4 | import androidx.recyclerview.widget.RecyclerView
5 | import com.google.android.material.card.MaterialCardView
6 | import com.mobiaxe.core.presentation.DataBindingAdapter
7 |
8 | @BindingAdapter("app:isSelected")
9 | fun MaterialCardView.setSelected(isSelected: Boolean) {
10 | isChecked = isSelected
11 | isHovered = isSelected
12 | isPressed = isSelected
13 | }
14 |
15 | @BindingAdapter("app:recycledViewPool")
16 | fun RecyclerView.setRecyclerViewPoolBinding(pool: RecyclerView.RecycledViewPool) {
17 | setRecycledViewPool(pool)
18 | }
19 |
20 | @BindingAdapter("app:submitList")
21 | fun setRecyclerViewProperties(recyclerView: RecyclerView, items: List?) {
22 | if (recyclerView.adapter is DataBindingAdapter<*>) {
23 | items?.let {
24 | (recyclerView.adapter as DataBindingAdapter).submitList(it)
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/splash_screen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/drawable/splash_screen.jpg
--------------------------------------------------------------------------------
/app/src/main/res/font/moon_bold_v2.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/font/moon_bold_v2.otf
--------------------------------------------------------------------------------
/app/src/main/res/font/moon_light_v2.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/font/moon_light_v2.otf
--------------------------------------------------------------------------------
/app/src/main/res/font/panton_black.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/font/panton_black.otf
--------------------------------------------------------------------------------
/app/src/main/res/font/panton_bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/font/panton_bold.otf
--------------------------------------------------------------------------------
/app/src/main/res/font/panton_light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/font/panton_light.otf
--------------------------------------------------------------------------------
/app/src/main/res/font/panton_regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/font/panton_regular.otf
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_dashboard.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
22 |
23 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_app_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v28/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #FFFFFF
8 | #4DFFFFFF
9 | #222d45
10 | #99222d45
11 | #171414
12 |
13 | #07152A
14 | #0A2143
15 | #92B4C2
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 32sp
5 | 24sp
6 | 20sp
7 | 18sp
8 | 16sp
9 | 14sp
10 | 12sp
11 | 10sp
12 |
13 | 16dp
14 | 16dp
15 |
16 | 132dp
17 | 192dp
18 | 224dp
19 |
20 | 16dp
21 | 12dp
22 | 8dp
23 | 12dp
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | WASD
3 | Onboarding
4 | Home
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
14 |
15 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/text_appearances.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
11 |
12 |
15 |
16 |
19 |
20 |
21 |
22 |
25 |
26 |
29 |
30 |
33 |
34 |
35 |
36 |
39 |
40 |
43 |
44 |
47 |
48 |
49 |
50 |
53 |
54 |
57 |
58 |
61 |
62 |
63 |
64 |
67 |
68 |
71 |
72 |
75 |
76 |
77 |
78 |
81 |
82 |
85 |
86 |
89 |
90 |
91 |
92 |
93 |
94 |
97 |
98 |
101 |
102 |
105 |
106 |
109 |
110 |
113 |
114 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extension.applyDefaults
2 |
3 | plugins {
4 | id(GradlePluginId.GRADLE_VERSION_PLUGIN) version GradlePluginVersion.GRADLE_VERSION_PLUGIN
5 | }
6 |
7 | buildscript {
8 | repositories {
9 | google()
10 | jcenter()
11 | }
12 |
13 | dependencies {
14 | classpath(GradleClasspath.GRADLE_PLUGIN)
15 | classpath(GradleClasspath.KOTLIN_GRADLE_PLUGIN)
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories.applyDefaults()
21 | }
22 |
23 | tasks {
24 | register("clean", Delete::class) {
25 | delete = setOf(rootProject.buildDir)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | /build
3 | /.gradle
4 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | repositories {
6 | jcenter()
7 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/AndroidConfig.kt:
--------------------------------------------------------------------------------
1 | object AndroidConfig {
2 | const val COMPILE_SDK_VERSION = 29
3 | const val MIN_SDK_VERSION = 21
4 | const val TARGET_SDK_VERSION = 29
5 | const val BUILD_TOOLS_VERSION = "29.0.2"
6 |
7 | const val TEST_INSTRUMENTATION_RUNNER = "androidx.test.runner.AndroidJUnitRunner"
8 |
9 | const val APPLICATION_ID = "com.mobiaxe.wasd"
10 |
11 | const val VERSION_CODE = 1
12 | const val VERSION_NAME = "1.0"
13 | }
14 |
15 | object BuildType {
16 | const val RELEASE = "release"
17 | const val DEBUG = "debug"
18 | }
19 |
20 | object SigningConfig {
21 | object DEBUG {
22 | const val KEY_ALIAS = "androiddebugkey"
23 | const val KEY_PASSWORD = "android"
24 | const val STORE_PASSWORD = "android"
25 | const val STORE_FILE = "keystores/debug.keystore"
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/CoreVersion.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * These versions are covered across libraries and plugins
3 | */
4 | object CoreVersion {
5 | const val KOTLIN = "1.3.70"
6 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/GradleDependency.kt:
--------------------------------------------------------------------------------
1 | object GradlePluginVersion {
2 | const val ANDROID_GRADLE = "3.5.3"
3 | const val GRADLE_VERSION_PLUGIN = "0.28.0"
4 | }
5 |
6 | object GradlePluginId {
7 | const val ANDROID_APPLICATION = "com.android.application"
8 | const val ANDROID_DYNAMIC_FEATURE = "com.android.dynamic-feature"
9 | const val ANDROID_LIBRARY = "com.android.library"
10 | const val KOTLIN_ANDROID = "org.jetbrains.kotlin.android"
11 | const val KOTLIN_ANDROID_EXTENSIONS = "org.jetbrains.kotlin.android.extensions"
12 | const val GRADLE_VERSION_PLUGIN = "com.github.ben-manes.versions"
13 | const val KOTLIN_KAPT = "kotlin-kapt"
14 | }
15 |
16 | object GradleClasspath {
17 | const val GRADLE_PLUGIN = "com.android.tools.build:gradle:${GradlePluginVersion.ANDROID_GRADLE}"
18 | const val KOTLIN_GRADLE_PLUGIN = "org.jetbrains.kotlin:kotlin-gradle-plugin:${CoreVersion.KOTLIN}"
19 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/LibraryDependency.kt:
--------------------------------------------------------------------------------
1 | private object Version {
2 | const val MATERIAL = "1.2.0-alpha05"
3 | const val LIFECYCLE = "2.2.0"
4 | const val CONSTRAINT = "2.0.0-beta4"
5 | const val KOIN = "2.1.4"
6 | const val GLIDE = "4.11.0"
7 | const val VIEWPAGER2 = "1.0.0"
8 | const val NAVIGATION = "2.3.0-alpha03"
9 | const val ROOM = "2.2.4"
10 | const val TIMBER = "4.7.1"
11 | const val STETHO = "1.5.1"
12 | const val RETROFIT = "2.7.1"
13 | const val PAGING = "2.1.1"
14 | const val COROUTINE = "1.3.4"
15 | const val LEAK_CANARY = "2.2"
16 | const val OKHTTP_LOGGING = "4.4.0"
17 | }
18 |
19 | object Library {
20 | const val KOTLIN = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${CoreVersion.KOTLIN}"
21 | const val VIEWMODEL = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.LIFECYCLE}"
22 | const val MATERIAL = "com.google.android.material:material:${Version.MATERIAL}"
23 | const val CONSTRAINT = "androidx.constraintlayout:constraintlayout:${Version.CONSTRAINT}"
24 | const val KOIN = "org.koin:koin-androidx-viewmodel:${Version.KOIN}"
25 | const val GLIDE = "com.github.bumptech.glide:glide:${Version.GLIDE}"
26 | const val VIEWPAGER2 = "androidx.viewpager2:viewpager2:${Version.VIEWPAGER2}"
27 | const val NAVIGATION_FRAGMENT_KTX = "androidx.navigation:navigation-fragment-ktx:${Version.NAVIGATION}"
28 | const val NAVIGATION_UI_KTX = "androidx.navigation:navigation-ui-ktx:${Version.NAVIGATION}"
29 | const val NAVIGATION_DYNAMIC_FEATURE = "androidx.navigation:navigation-dynamic-features-fragment:${Version.NAVIGATION}"
30 | const val ROOM = "androidx.room:room-runtime:${Version.ROOM}"
31 | const val ROOM_KTX = "androidx.room:room-ktx:${Version.ROOM}"
32 | const val TIMBER = "com.jakewharton.timber:timber:${Version.TIMBER}"
33 | const val STETHO = "com.facebook.stetho:stetho:${Version.STETHO}"
34 | const val STETHO_OKHTTP = "com.facebook.stetho:stetho-okhttp3:${Version.STETHO}"
35 | const val RETROFIT = "com.squareup.retrofit2:retrofit:${Version.RETROFIT}"
36 | const val RETROFIT_CONVERTER = "com.squareup.retrofit2:converter-gson:${Version.RETROFIT}"
37 | const val OKHTTP_LOGGING = "com.squareup.okhttp3:logging-interceptor:${Version.OKHTTP_LOGGING}"
38 | const val PAGING = "androidx.paging:paging-runtime-ktx:${Version.PAGING}"
39 | const val COROUTINE = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.COROUTINE}"
40 | const val LEAK_CANARY = "com.squareup.leakcanary:leakcanary-android:${Version.LEAK_CANARY}"
41 |
42 | const val GLIDE_COMPILER = "com.github.bumptech.glide:compiler:${Version.GLIDE}"
43 | const val ROOM_COMPILER = "androidx.room:room-compiler:${Version.ROOM}"
44 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/ModuleDependency.kt:
--------------------------------------------------------------------------------
1 | object ModuleDependency {
2 | const val APP = ":app"
3 | const val CORE = ":core"
4 | const val FEATURE_ONBOARDING = ":onboarding"
5 | const val FEATURE_HOME = ":home"
6 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/extension/ProjectExt.kt:
--------------------------------------------------------------------------------
1 | package extension
2 |
3 | import org.gradle.api.Project
4 | import utils.getLocalProperty
5 |
6 | /**
7 | * Obtain property declared on `$projectRoot/keystore.properties` file.
8 | *
9 | * @param propertyName the name of declared property
10 | */
11 | fun Project.getLocalProperty(propertyName: String): String {
12 | return getLocalProperty(propertyName, this)
13 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/extension/RepositoryHandler.kt:
--------------------------------------------------------------------------------
1 | package extension
2 |
3 | import org.gradle.api.artifacts.dsl.RepositoryHandler
4 |
5 | fun RepositoryHandler.applyDefaults() {
6 | google()
7 | jcenter()
8 | }
9 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/utils/BuildUtils.kt:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import org.gradle.api.Project
4 | import java.util.Properties
5 |
6 | private const val LOCAL_PROPERTIES_FILE_NAME = "keystore.properties"
7 |
8 | /**
9 | * Util to obtain property declared on `$projectRoot/local.properties` file.
10 | *
11 | * @param propertyName the name of declared property
12 | * @param project the project reference
13 | *
14 | * @return the value of property name, otherwise throw [Exception]
15 | */
16 | fun getLocalProperty(propertyName: String, project: Project): String {
17 | val localProperties = Properties().apply {
18 | val localPropertiesFile = project.rootProject.file(LOCAL_PROPERTIES_FILE_NAME)
19 | if (localPropertiesFile.exists()) {
20 | load(localPropertiesFile.inputStream())
21 | }
22 | }
23 |
24 | return localProperties.getProperty(propertyName)?.let {
25 | it
26 | } ?: run {
27 | throw NoSuchFieldException("Not defined property: $propertyName")
28 | }
29 | }
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(GradlePluginId.ANDROID_LIBRARY)
3 | id(GradlePluginId.KOTLIN_ANDROID)
4 | id(GradlePluginId.KOTLIN_ANDROID_EXTENSIONS)
5 | id(GradlePluginId.KOTLIN_KAPT)
6 | }
7 |
8 | android {
9 | compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION)
10 |
11 | defaultConfig {
12 | minSdkVersion(AndroidConfig.MIN_SDK_VERSION)
13 | targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION)
14 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
15 |
16 | buildConfigFieldFromGradleProperty("apiBaseUrl")
17 | buildConfigFieldFromGradleProperty("apiToken")
18 | }
19 |
20 | dataBinding {
21 | isEnabled = true
22 | }
23 |
24 | kotlinOptions {
25 | jvmTarget = JavaVersion.VERSION_1_8.toString()
26 | }
27 | }
28 |
29 | dependencies {
30 | api(Library.KOTLIN)
31 | api(Library.MATERIAL)
32 | api(Library.VIEWPAGER2)
33 | api(Library.CONSTRAINT)
34 | api(Library.KOIN)
35 | api(Library.GLIDE)
36 | api(Library.PAGING)
37 | api(Library.COROUTINE)
38 |
39 | implementation(Library.RETROFIT)
40 | implementation(Library.RETROFIT_CONVERTER)
41 | implementation(Library.OKHTTP_LOGGING)
42 | implementation(Library.VIEWMODEL)
43 | implementation(Library.ROOM)
44 | implementation(Library.ROOM_KTX)
45 | implementation(Library.TIMBER)
46 | implementation(Library.STETHO_OKHTTP)
47 |
48 | debugImplementation(Library.LEAK_CANARY)
49 |
50 | kapt(Library.ROOM_COMPILER)
51 | }
52 |
53 | fun com.android.build.gradle.internal.dsl.BaseFlavor.buildConfigFieldFromGradleProperty(gradlePropertyName: String) {
54 | val propertyValue = project.properties[gradlePropertyName] as? String
55 | checkNotNull(propertyValue) { "Gradle property $gradlePropertyName is null" }
56 |
57 | val androidResourceName = "GRADLE_${gradlePropertyName.toSnakeCase()}".toUpperCase()
58 | buildConfigField("String", androidResourceName, propertyValue)
59 | }
60 |
61 | fun String.toSnakeCase() = this.split(Regex("(?=[A-Z])")).joinToString("_") { it.toLowerCase() }
62 |
63 |
--------------------------------------------------------------------------------
/core/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/core/consumer-rules.pro
--------------------------------------------------------------------------------
/core/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
22 |
--------------------------------------------------------------------------------
/core/src/androidTest/java/com/mobiaxe/core/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.mobiaxe.core.test", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/PreferencesHelper.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | /**
7 | * General Preferences Helper class, used for storing preference values using the Preference API
8 | */
9 | open class PreferencesHelper(context: Context) {
10 |
11 | companion object {
12 | private const val PREF_WASD_PACKAGE_NAME = "com.mobiaxe.wasd"
13 |
14 | private const val PREF_KEY_LAST_CACHE = "last_cache"
15 |
16 | private const val PREF_KEY_ONBOARDING_SHOW = "onboarding_show"
17 | }
18 |
19 | private val wasdPref: SharedPreferences
20 |
21 | init {
22 | wasdPref = context.getSharedPreferences(PREF_WASD_PACKAGE_NAME, Context.MODE_PRIVATE)
23 | }
24 |
25 | /**
26 | * Store and retrieve the last time data was cached
27 | */
28 | var lastCacheTime: Long
29 | get() = wasdPref.getLong(PREF_KEY_LAST_CACHE, 0)
30 | set(lastCache) = wasdPref.edit().putLong(PREF_KEY_LAST_CACHE, lastCache).apply()
31 |
32 | var onboardingShow: Boolean
33 | get() = wasdPref.getBoolean(PREF_KEY_ONBOARDING_SHOW, false)
34 | set(isOnboardingShown) = wasdPref.edit().putBoolean(PREF_KEY_ONBOARDING_SHOW, isOnboardingShown).apply()
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/WASDCacheImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache
2 |
3 | import com.mobiaxe.core.cache.db.WASDDatabase
4 | import com.mobiaxe.core.data.Gamer
5 |
6 | class WASDCacheImpl(private val wasdDatabase: WASDDatabase): WASDCachedDataStore {
7 |
8 | override suspend fun savePlatform(platformId: String?) =
9 | wasdDatabase.cachedWASDDao().insertPlatformId(platformId?.let { Gamer(platform = it) })
10 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/WASDCachedDataStore.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache
2 |
3 | interface WASDCachedDataStore {
4 |
5 | suspend fun savePlatform(platformId: String?)
6 |
7 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/db/CachedWASDDao.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache.db
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import com.mobiaxe.core.data.Gamer
7 |
8 | @Dao
9 | abstract class CachedWASDDao {
10 |
11 | @Insert(onConflict = OnConflictStrategy.REPLACE)
12 | abstract suspend fun insertPlatformId(platform: Gamer?)
13 |
14 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/db/CoverConverter.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache.db
2 |
3 | import androidx.room.TypeConverter
4 | import com.google.gson.Gson
5 | import com.mobiaxe.core.data.Cover
6 |
7 | class CoverConverter {
8 |
9 | @TypeConverter
10 | fun coverToJson(value: Cover?): String {
11 | return Gson().toJson(value)
12 | }
13 |
14 | @TypeConverter
15 | fun jsonToCover(value: String): Cover? {
16 | return Gson().fromJson(value, Cover::class.java) ?: Cover(0, "", "", 0)
17 | }
18 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/db/ListConverter.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache.db
2 |
3 | import androidx.room.TypeConverter
4 | import com.google.gson.Gson
5 |
6 | class ListConverter {
7 |
8 | @TypeConverter
9 | fun listToJson(value: List?): String {
10 | return Gson().toJson(value)
11 | }
12 |
13 | @TypeConverter
14 | fun jsonToList(value: String): List? {
15 | val objects = Gson().fromJson(value, Array::class.java)
16 | return if (objects == null) {
17 | emptyList()
18 | } else {
19 | objects as Array
20 | objects.toList()
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/db/WASDConstants.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache.db
2 |
3 | object WASDConstants {
4 |
5 | const val TABLE_GAMER = "gamer"
6 | const val TABLE_POPULAR = "popular"
7 |
8 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/cache/db/WASDDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.cache.db
2 |
3 | import android.content.Context
4 | import androidx.room.Database
5 | import androidx.room.Room
6 | import androidx.room.RoomDatabase
7 | import com.mobiaxe.core.data.Gamer
8 |
9 | @Database(entities = [Gamer::class], version = 1)
10 | abstract class WASDDatabase: RoomDatabase() {
11 |
12 | abstract fun cachedWASDDao(): CachedWASDDao
13 |
14 | companion object {
15 |
16 | @Volatile
17 | private var INSTANCE: WASDDatabase? = null
18 |
19 | fun getInstance(context: Context): WASDDatabase =
20 | INSTANCE ?: synchronized(this) {
21 | INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
22 | }
23 |
24 | private fun buildDatabase(context: Context) =
25 | Room.databaseBuilder(context.applicationContext,
26 | WASDDatabase::class.java, "WASD.db")
27 | .build()
28 | }
29 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/data/Resource.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.data
2 |
3 | data class Resource(val status: Status, val data: T?, val message: String?) {
4 | companion object {
5 | fun success(data: T? = null): Resource {
6 | return Resource(Status.SUCCESS, data, null)
7 | }
8 |
9 | fun error(msg: String): Resource {
10 | return Resource(Status.ERROR, null, msg)
11 | }
12 |
13 | fun loading(data: T? = null): Resource {
14 | return Resource(Status.LOADING, data, null)
15 | }
16 |
17 | fun empty(msg: String): Resource {
18 | return Resource(Status.EMPTY, null, msg)
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/data/Status.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.data
2 |
3 | import android.view.View
4 |
5 | enum class Status(val visibility: Int) {
6 | SUCCESS(View.GONE),
7 | ERROR(View.GONE),
8 | LOADING(View.VISIBLE),
9 | EMPTY(View.GONE)
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/data/WASDModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.data
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 | import androidx.room.TypeConverters
7 | import com.google.gson.annotations.SerializedName
8 | import com.mobiaxe.core.cache.db.CoverConverter
9 | import com.mobiaxe.core.cache.db.ListConverter
10 | import com.mobiaxe.core.cache.db.WASDConstants
11 | import com.mobiaxe.core.presentation.ListItemViewModel
12 | import java.util.*
13 |
14 | @Entity(tableName = WASDConstants.TABLE_GAMER)
15 | data class Gamer(
16 | @PrimaryKey
17 | @ColumnInfo(name = "id")
18 | val id: String = UUID.randomUUID().toString(),
19 | @ColumnInfo(name = "platform")
20 | val platform: String
21 | )
22 |
23 | @Entity(tableName = WASDConstants.TABLE_POPULAR)
24 | data class Game(
25 | @PrimaryKey @field:SerializedName("id") val id: Long,
26 | @TypeConverters(CoverConverter::class) @field:SerializedName("cover") val cover: Cover?,
27 | @field:SerializedName("name") val name: String,
28 | @field:SerializedName("popularity") val popularity: Double,
29 | @TypeConverters(ListConverter::class) val platforms: MutableList = mutableListOf()
30 | ): ListItemViewModel()
31 |
32 | data class Cover(
33 | @SerializedName("id") val id: Long,
34 | @SerializedName("image_id") val imageId: String,
35 | @SerializedName("url") val url: String,
36 | @SerializedName("game") val game: Long)
37 |
38 | data class ReleaseDate(
39 | @SerializedName("id") val id: Long,
40 | @SerializedName("game") val game: Game
41 | ): ListItemViewModel()
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/domain/SimpleUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.domain
2 |
3 | abstract class SimpleUseCase where Type : Any {
4 | abstract fun run(params: Params): Type
5 | operator fun invoke(params: Params): Type = run(params)
6 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/domain/SuspendUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.domain
2 |
3 | import kotlinx.coroutines.*
4 | import timber.log.Timber
5 |
6 | abstract class SuspendUseCase where Type : Any {
7 |
8 | abstract suspend fun run(params: Params): Type
9 | suspend operator fun invoke(params: Params): Type = run(params)
10 |
11 | private val viewModelJob = SupervisorJob()
12 | private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
13 |
14 | private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
15 | handleErrors(throwable)
16 | }
17 |
18 | private fun handleErrors(error: Throwable?) {
19 | Timber.e(error, "Error")
20 | }
21 |
22 | fun execute(params: Params, onResult: (Type) -> Unit = {}) {
23 | viewModelScope.launch (exceptionHandler) {
24 | val result = withContext(Dispatchers.IO) {
25 | run(params)
26 | }
27 | onResult(result)
28 | }
29 | }
30 |
31 | fun cancel() {
32 | viewModelScope.coroutineContext.cancelChildren()
33 | }
34 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/domain/SynchronouosUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.domain
2 |
3 | interface SynchronouosUseCase {
4 | fun execute(params: Params): Results
5 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/extension/ContextExt.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.extension
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.widget.Toast
6 |
7 | fun Context.toast(message: String) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
8 |
9 | fun Context.start(intent: Intent?) = intent?.let { startActivity(it) }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/extension/LifecycleExt.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.extension
2 |
3 | import androidx.lifecycle.LifecycleOwner
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.Observer
6 |
7 | fun LifecycleOwner.observe(liveData: LiveData?, observer: (T) -> Unit) {
8 | liveData?.observe(this, Observer(observer))
9 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/extension/UtilExt.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.extension
2 |
3 | import android.content.res.Resources
4 |
5 | val Int.dp: Int
6 | get() = (this / Resources.getSystem().displayMetrics.density).toInt()
7 | val Int.px: Int
8 | get() = (this * Resources.getSystem().displayMetrics.density).toInt()
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/koin/Modules.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.koin
2 |
3 | import androidx.room.Room
4 | import com.mobiaxe.core.BuildConfig
5 | import com.mobiaxe.core.cache.PreferencesHelper
6 | import com.mobiaxe.core.cache.WASDCacheImpl
7 | import com.mobiaxe.core.cache.WASDCachedDataStore
8 | import com.mobiaxe.core.cache.db.WASDDatabase
9 | import com.mobiaxe.core.remote.WASDRemoteDataStore
10 | import com.mobiaxe.core.remote.WASDRemoteImpl
11 | import com.mobiaxe.core.remote.service.WASDServiceFactory
12 | import org.koin.android.ext.koin.androidContext
13 | import org.koin.dsl.module
14 |
15 | val coreModule = module {
16 | single { PreferencesHelper(androidContext()) }
17 | single {
18 | Room.databaseBuilder(androidContext(),
19 | WASDDatabase::class.java, "WASD.db")
20 | .build()
21 | }
22 |
23 | factory { WASDServiceFactory.makeWASDService(BuildConfig.DEBUG, BuildConfig.GRADLE_API_BASE_URL, BuildConfig.GRADLE_API_TOKEN) }
24 | factory { get().cachedWASDDao() }
25 | factory { WASDCacheImpl(get()) }
26 | factory { WASDRemoteImpl(get()) }
27 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/presentation/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.presentation
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.databinding.DataBindingUtil
6 | import androidx.databinding.ViewDataBinding
7 | import androidx.lifecycle.ViewModel
8 |
9 | abstract class BaseActivity: AppCompatActivity() {
10 |
11 | private lateinit var viewModel: V
12 | private lateinit var dataBinding: B
13 |
14 | /**
15 | * @return layout resource id
16 | */
17 | abstract fun getLayoutId(): Int
18 |
19 | /**
20 | * Override for set view model
21 | *
22 | * @return view model instance
23 | */
24 | abstract fun getViewModel(): Lazy
25 |
26 | /**
27 | * Override for bind view
28 | *
29 | * @param dataBinding instance
30 | */
31 | abstract fun bindViewModel(dataBinding: B)
32 |
33 | abstract fun loadKoinModules()
34 |
35 | abstract fun unloadKoinModules()
36 |
37 | fun getBinding(): B = dataBinding
38 | fun vm() = getViewModel().value
39 |
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 | dataBinding = DataBindingUtil.setContentView(this, getLayoutId())
43 | loadKoinModules()
44 | viewModel = getViewModel().value
45 | bindViewModel(dataBinding)
46 | }
47 |
48 | override fun onDestroy() {
49 | super.onDestroy()
50 | unloadKoinModules()
51 | }
52 |
53 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/presentation/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.presentation
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.databinding.ViewDataBinding
9 | import androidx.fragment.app.Fragment
10 | import androidx.lifecycle.ViewModel
11 |
12 | abstract class BaseFragment: Fragment() {
13 |
14 | private lateinit var viewModel: V
15 | private lateinit var dataBinding: B
16 | private lateinit var rootView: View
17 |
18 | /**
19 | * @return layout resource id
20 | */
21 | abstract fun getLayoutId(): Int
22 |
23 | /**
24 | * Override for set view model
25 | *
26 | * @return view model instance
27 | */
28 | abstract fun getViewModel(): Lazy
29 |
30 | /**
31 | * Override for bind view
32 | *
33 | * @param dataBinding instance
34 | */
35 | abstract fun bindViewModel(dataBinding: B)
36 |
37 | abstract fun loadKoinModules()
38 |
39 | abstract fun unloadKoinModules()
40 |
41 | override fun onCreate(savedInstanceState: Bundle?) {
42 | super.onCreate(savedInstanceState)
43 | loadKoinModules()
44 | viewModel = getViewModel().value
45 | }
46 |
47 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
48 | dataBinding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false)
49 | rootView = dataBinding.root
50 | return rootView
51 | }
52 |
53 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
54 | super.onViewCreated(view, savedInstanceState)
55 | bindViewModel(dataBinding)
56 | }
57 |
58 | override fun onDestroyView() {
59 | super.onDestroyView()
60 | unloadKoinModules()
61 | }
62 |
63 |
64 | fun getBinding() = dataBinding
65 |
66 | fun vModel() = getViewModel().value
67 |
68 |
69 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/presentation/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.presentation
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.mobiaxe.core.domain.SuspendUseCase
5 |
6 | abstract class BaseViewModel(vararg suspendUseCase: SuspendUseCase<*, *>): ViewModel() {
7 |
8 | protected var useCaseList: MutableList> = mutableListOf()
9 |
10 | init {
11 | useCaseList.addAll(suspendUseCase)
12 | }
13 |
14 | override fun onCleared() {
15 | super.onCleared()
16 | useCaseList.forEach {
17 | it.cancel()
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/presentation/DataBindingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.presentation
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.databinding.DataBindingUtil
7 | import androidx.databinding.ViewDataBinding
8 | import androidx.recyclerview.widget.DiffUtil
9 | import androidx.recyclerview.widget.ListAdapter
10 | import androidx.recyclerview.widget.RecyclerView
11 | import com.mobiaxe.core.BR
12 |
13 | abstract class DataBindingAdapter(diffCallback: DiffUtil.ItemCallback) :
14 | ListAdapter>(diffCallback) {
15 |
16 | private var onListItemViewClickListener: OnListItemViewClickListener? = null
17 |
18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBindingViewHolder {
19 | val layoutInflater = LayoutInflater.from(parent.context)
20 | val binding = DataBindingUtil.inflate(layoutInflater, viewType, parent, false)
21 | return DataBindingViewHolder(binding)
22 | }
23 |
24 | override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) = holder.bind(getItem(position))
25 |
26 | class DataBindingViewHolder(private val binding: ViewDataBinding) :
27 | RecyclerView.ViewHolder(binding.root) {
28 |
29 | fun bind(item: T) {
30 | binding.setVariable(BR.item, item)
31 | binding.executePendingBindings()
32 | }
33 | }
34 |
35 | interface OnListItemViewClickListener{
36 | fun onClick(view: View, position: Int)
37 | }
38 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/presentation/ListItemViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.presentation
2 |
3 | abstract class ListItemViewModel{
4 | var adapterPosition: Int = -1
5 | var onListItemViewClickListener: DataBindingAdapter.OnListItemViewClickListener? = null
6 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/remote/IGDBRequest.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.remote
2 |
3 | import okhttp3.MediaType.Companion.toMediaTypeOrNull
4 | import okhttp3.RequestBody
5 | import okhttp3.RequestBody.Companion.toRequestBody
6 |
7 | class IGDBRequest(
8 | val fields: String?,
9 | val sort: String?,
10 | val limit: Int?,
11 | val offset: Int?,
12 | val where: List?,
13 | val search: String?
14 | ) {
15 |
16 | companion object {
17 | private const val FIELDS = "f"
18 | private const val SORT = "s"
19 | private const val SEARCH = "search"
20 | private const val LIMIT = "l"
21 | private const val OFFSET = "o"
22 | private const val WHERE = "w"
23 | private const val COMMA = ";"
24 | private const val ASCENDING = "asc"
25 | private const val DESCENDING = "desc"
26 | private const val MEDIA_TYPE = "text/plain"
27 | }
28 |
29 | data class Builder(
30 | var fields: String? = null,
31 | var search: String? = null,
32 | var sort: String? = null,
33 | var limit: Int? = null,
34 | var offset: Int? = null,
35 | var where: List? = null,
36 | var isAscending: Boolean = false,
37 | var by: String? = null
38 | ) {
39 |
40 | fun search(query: String) = apply { this.search = query }
41 |
42 | fun fields(fields: String) = apply { this.fields = fields }
43 |
44 | fun sort(sort: String, isAscending: Boolean = false) = apply {
45 | this.sort = sort
46 | this.isAscending = isAscending
47 | }
48 |
49 | fun where(by: String) = apply { this.by = by }
50 |
51 | fun limit(limit: Int) = apply { this.limit = limit }
52 |
53 | fun offset(offset: Int) = apply { this.offset = offset }
54 |
55 | fun build(): RequestBody {
56 |
57 | val request = StringBuilder()
58 |
59 | if (search != null) {
60 | request.append("$SEARCH \"$search\"$COMMA")
61 | }
62 |
63 | if (fields != null) {
64 | request.append("$FIELDS $fields$COMMA")
65 | }
66 |
67 | if (sort != null) {
68 | if (isAscending) {
69 | request.append("$SORT $sort $ASCENDING$COMMA")
70 | } else {
71 | request.append("$SORT $sort $DESCENDING$COMMA")
72 | }
73 | }
74 |
75 | if (by != null) {
76 | request.append("$WHERE $by$COMMA")
77 | }
78 |
79 | if (limit != null) {
80 | request.append("$LIMIT $limit$COMMA")
81 | }
82 |
83 | if (offset != null) {
84 | request.append("$OFFSET $offset$COMMA")
85 | }
86 |
87 | return request.toString().toRequestBody(MEDIA_TYPE.toMediaTypeOrNull())
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/remote/WASDRemoteDataStore.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.remote
2 |
3 | import com.mobiaxe.core.data.Game
4 |
5 | interface WASDRemoteDataStore {
6 | suspend fun getPopularGames(offset: Int, platforms: String): List
7 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/remote/WASDRemoteImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.remote
2 |
3 | import com.mobiaxe.core.data.Game
4 | import com.mobiaxe.core.remote.service.WASDService
5 |
6 | class WASDRemoteImpl(private val wasdService: WASDService): WASDRemoteDataStore {
7 |
8 | companion object {
9 | private const val POPULAR_GAMES_FIELDS = "name,popularity,rating,cover.image_id,platforms"
10 | private const val POPULAR_GAMES_SORT = "popularity"
11 | private const val COVER_FIELDS = "image_id,url,game"
12 | private const val GAME = "game"
13 | private const val DATE = "date"
14 | private const val LIMIT = 50
15 | }
16 |
17 | private val whereGames: (String) -> String = {param -> "platforms = $param & category = 0"}
18 |
19 | override suspend fun getPopularGames(offset: Int, platforms: String): List {
20 | val request = IGDBRequest.Builder().fields(POPULAR_GAMES_FIELDS).sort(POPULAR_GAMES_SORT).where(whereGames(platforms)).limit(LIMIT).offset(offset).build()
21 | return wasdService.getGames(request)
22 | }
23 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/remote/service/WASDService.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.remote.service
2 |
3 | import com.mobiaxe.core.data.Game
4 | import okhttp3.RequestBody
5 | import retrofit2.http.Body
6 | import retrofit2.http.POST
7 |
8 | interface WASDService {
9 |
10 | @POST("games")
11 | suspend fun getGames(@Body request: RequestBody): List
12 |
13 |
14 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/mobiaxe/core/remote/service/WASDServiceFactory.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.core.remote.service
2 |
3 | import com.facebook.stetho.okhttp3.StethoInterceptor
4 | import com.google.gson.FieldNamingPolicy
5 | import com.google.gson.Gson
6 | import com.google.gson.GsonBuilder
7 | import okhttp3.Interceptor
8 | import okhttp3.OkHttpClient
9 | import okhttp3.logging.HttpLoggingInterceptor
10 | import retrofit2.Retrofit
11 | import retrofit2.converter.gson.GsonConverterFactory
12 | import java.util.concurrent.TimeUnit
13 |
14 | object WASDServiceFactory {
15 | fun makeWASDService(isDebug: Boolean, baseUrl: String, apiKey: String): WASDService {
16 | val retrofit = Retrofit.Builder()
17 | .baseUrl(baseUrl)
18 | .client(makeOkHttpClient(
19 | makeLoggingInterceptor(isDebug), apiKey))
20 | .addConverterFactory(GsonConverterFactory.create(makeGson()))
21 | .build()
22 | return retrofit.create(WASDService::class.java)
23 | }
24 |
25 | private fun makeOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor, apiKey: String): OkHttpClient {
26 | return OkHttpClient.Builder()
27 | .addInterceptor(httpLoggingInterceptor)
28 | .addInterceptor(makeHeaderInterceptor(apiKey))
29 | .addNetworkInterceptor(StethoInterceptor())
30 | .connectTimeout(120, TimeUnit.SECONDS)
31 | .readTimeout(120, TimeUnit.SECONDS)
32 | .build()
33 | }
34 |
35 | private fun makeGson(): Gson {
36 | return GsonBuilder()
37 | .setLenient()
38 | .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
39 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
40 | .create()
41 | }
42 |
43 | private fun makeLoggingInterceptor(isDebug: Boolean): HttpLoggingInterceptor {
44 | val logging = HttpLoggingInterceptor()
45 | logging.level = if (isDebug)
46 | HttpLoggingInterceptor.Level.BODY
47 | else
48 | HttpLoggingInterceptor.Level.NONE
49 | return logging
50 | }
51 |
52 | private fun makeHeaderInterceptor(apiKey: String): Interceptor =
53 | Interceptor { chain ->
54 | val request = chain.request().newBuilder()
55 | request.addHeader("user-key", apiKey)
56 | chain.proceed(request.build())
57 | }
58 | }
--------------------------------------------------------------------------------
/core/src/main/res/layout/list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
12 |
--------------------------------------------------------------------------------
/core/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | core
3 |
4 |
--------------------------------------------------------------------------------
/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 |
10 | # -------Gradle--------
11 | org.gradle.daemon=true
12 | org.gradle.parallel=true
13 | org.gradle.caching=true
14 | org.gradle.jvmargs=-Xmx4g
15 |
16 |
17 | # When configured, Gradle will run in incubating parallel mode.
18 | # This option should only be used with decoupled projects. More details, visit
19 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
20 | # org.gradle.parallel=true
21 | # AndroidX package structure to make it clearer which packages are bundled with the
22 | # Android operating system, and which are packaged with your app's APK
23 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
24 |
25 | # -------AndroidX-------
26 | android.useAndroidX=true
27 | android.enableJetifier=true
28 | android.enableBuildCache=true
29 |
30 | # -------Incremental annotation processing-------
31 | kapt.incremental.apt=true
32 | kapt.use.worker.api=true
33 | kapt.include.compile.classpath=false
34 |
35 |
36 | # -------New code srinker-------
37 | android.enableR8=true
38 | android.enableR8.libraries=true
39 |
40 | # Kotlin code style for this project: "official" or "obsolete":
41 | kotlin.code.style=official
42 |
43 | # -------Build parameters--------
44 | # Values may be overridden in CI using gradlew "-Pname=value" param
45 | apiBaseUrl="https://api-v3.igdb.com/"
46 | # Typically we shouldn't store token in public repository, however this is just a sample project, so
47 | # we can favour convenience (app can be compiled and launched after checkout) over security (each person who
48 | # checkouts the project must generate own api key and change app configuration before running it).
49 | # In real-live setup this key could be provided\overriden by CI.
50 | apiToken="INSERT YOUR API KEY"
51 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jan 28 01:46:46 EET 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/home/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/home/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(GradlePluginId.ANDROID_DYNAMIC_FEATURE)
3 | id(GradlePluginId.KOTLIN_ANDROID)
4 | id(GradlePluginId.KOTLIN_ANDROID_EXTENSIONS)
5 | id(GradlePluginId.KOTLIN_KAPT)
6 | }
7 |
8 | android {
9 | compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION)
10 |
11 | defaultConfig {
12 | applicationId = AndroidConfig.APPLICATION_ID
13 | minSdkVersion(AndroidConfig.MIN_SDK_VERSION)
14 | targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION)
15 | buildToolsVersion(AndroidConfig.BUILD_TOOLS_VERSION)
16 |
17 | versionCode = AndroidConfig.VERSION_CODE
18 | versionName = AndroidConfig.VERSION_NAME
19 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
20 | }
21 |
22 | dataBinding {
23 | isEnabled = true
24 | }
25 |
26 | }
27 |
28 | dependencies {
29 | implementation(project(ModuleDependency.APP))
30 | }
31 |
--------------------------------------------------------------------------------
/home/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/data/SectionData.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.data
2 |
3 | import androidx.recyclerview.widget.RecyclerView
4 | import com.mobiaxe.core.presentation.ListItemViewModel
5 | import com.mobiaxe.home.presentation.GameListAdapter
6 | import com.mobiaxe.home.presentation.RecentlyReleasedListAdapter
7 |
8 | data class SectionData(
9 | val headerTitle: Int,
10 | val gameListAdapter: GameListAdapter?,
11 | val recentlyReleasedListAdapter: RecentlyReleasedListAdapter?,
12 | val pool: RecyclerView.RecycledViewPool
13 | ): ListItemViewModel()
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/data/datasource/GamesPagingDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.data.datasource
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.paging.PageKeyedDataSource
6 | import com.mobiaxe.core.data.Game
7 | import com.mobiaxe.core.data.Status
8 | import com.mobiaxe.home.domain.usecase.GetPopularGames
9 |
10 | class GamesPagingDataSource(private val getPopularGames: GetPopularGames) : PageKeyedDataSource() {
11 |
12 | private val _networkState = MutableLiveData()
13 | val networkState: LiveData = _networkState
14 |
15 | override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
16 | _networkState.postValue(Status.LOADING)
17 | getPopularGames.execute(0) {
18 | callback.onResult(it, null, 0)
19 | _networkState.postValue(Status.SUCCESS)
20 | }
21 | }
22 |
23 | override fun loadAfter(params: LoadParams, callback: LoadCallback) {
24 | _networkState.postValue(Status.LOADING)
25 | getPopularGames.execute(params.key + 20) {
26 | callback.onResult(it, params.key + 20)
27 | _networkState.postValue(Status.SUCCESS)
28 | }
29 | }
30 |
31 | override fun loadBefore(params: LoadParams, callback: LoadCallback) {
32 |
33 | }
34 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/data/datasource/HomeRemoteDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.data.datasource
2 |
3 | import com.mobiaxe.core.data.Game
4 |
5 | interface HomeRemoteDataSource {
6 | suspend fun popularGamesFetched(offset: Int): List
7 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/data/repository/HomeDataRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.data.repository
2 |
3 | import com.mobiaxe.core.data.Game
4 | import com.mobiaxe.home.data.datasource.HomeRemoteDataSource
5 | import com.mobiaxe.home.domain.repository.HomeRepository
6 |
7 | class HomeDataRepository(private val homeRemoteDataSource: HomeRemoteDataSource): HomeRepository {
8 | override suspend fun getPopularGames(offset: Int): List = homeRemoteDataSource.popularGamesFetched(offset)
9 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/domain/datasource/GamesDataSourceFactory.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.domain.datasource
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import androidx.paging.DataSource
5 | import com.mobiaxe.core.data.Game
6 | import com.mobiaxe.home.data.datasource.GamesPagingDataSource
7 | import com.mobiaxe.home.domain.usecase.GetPopularGames
8 |
9 | class GamesDataSourceFactory(private val getPopularGames: GetPopularGames): DataSource.Factory() {
10 |
11 | val gameDataSourceLiveData = MutableLiveData()
12 |
13 | override fun create(): DataSource {
14 | val dataSource = GamesPagingDataSource(
15 | getPopularGames
16 | )
17 | gameDataSourceLiveData.postValue(dataSource)
18 | return dataSource
19 | }
20 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/domain/datasource/HomeRemoteDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.domain.datasource
2 |
3 | import com.mobiaxe.core.data.Game
4 | import com.mobiaxe.core.remote.WASDRemoteDataStore
5 | import com.mobiaxe.home.data.datasource.HomeRemoteDataSource
6 |
7 | class HomeRemoteDataSourceImpl(private val wasdRemoteDataStore: WASDRemoteDataStore): HomeRemoteDataSource {
8 | override suspend fun popularGamesFetched(offset: Int): List = wasdRemoteDataStore.getPopularGames(offset, "(48, 6)")
9 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/domain/repository/HomeRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.domain.repository
2 |
3 | import com.mobiaxe.core.data.Game
4 |
5 | interface HomeRepository {
6 | suspend fun getPopularGames(offset: Int): List
7 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/domain/usecase/GetPopularGames.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.domain.usecase
2 |
3 | import com.mobiaxe.core.data.Game
4 | import com.mobiaxe.core.domain.SuspendUseCase
5 | import com.mobiaxe.home.domain.repository.HomeRepository
6 |
7 | class GetPopularGames(
8 | private val homeRepository: HomeRepository
9 | ): SuspendUseCase, Int>() {
10 | override suspend fun run(params: Int): List =
11 | homeRepository.getPopularGames(params)
12 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/koin/Modules.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.koin
2 |
3 | import com.mobiaxe.home.data.datasource.HomeRemoteDataSource
4 | import com.mobiaxe.home.data.repository.HomeDataRepository
5 | import com.mobiaxe.home.domain.datasource.HomeRemoteDataSourceImpl
6 | import com.mobiaxe.home.domain.repository.HomeRepository
7 | import com.mobiaxe.home.domain.usecase.GetPopularGames
8 | import com.mobiaxe.home.presentation.HomeFragment
9 | import com.mobiaxe.home.presentation.HomeViewModel
10 | import com.mobiaxe.home.presentation.SectionListDataAdapter
11 | import org.koin.androidx.viewmodel.dsl.viewModel
12 | import org.koin.core.context.loadKoinModules
13 | import org.koin.core.context.unloadKoinModules
14 | import org.koin.dsl.module
15 |
16 | fun loadModules() = loadKoinModules(
17 | listOf(
18 | viewModelModule,
19 | repositoryModule,
20 | useCaseModule,
21 | homeModule
22 | )
23 | )
24 |
25 | fun unloadModules() = unloadKoinModules(
26 | listOf(
27 | viewModelModule,
28 | repositoryModule,
29 | useCaseModule,
30 | homeModule
31 | )
32 | )
33 |
34 | val homeModule = module(override = true) {
35 | factory { SectionListDataAdapter() }
36 | }
37 |
38 | val viewModelModule = module {
39 | viewModel { HomeViewModel(get()) }
40 | }
41 |
42 | val repositoryModule = module {
43 | factory { HomeDataRepository(get()) }
44 | factory { HomeRemoteDataSourceImpl(get()) }
45 | }
46 |
47 | val useCaseModule = module {
48 | factory { GetPopularGames(get()) }
49 | }
50 |
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/presentation/GameListAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.presentation
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.paging.PagedListAdapter
6 | import androidx.recyclerview.widget.DiffUtil
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.mobiaxe.core.data.Game
9 | import com.mobiaxe.core.data.Status
10 | import com.mobiaxe.home.databinding.ItemGameBinding
11 | import com.mobiaxe.home.databinding.ItemNetworkStateBinding
12 | import com.mobiaxe.home.BR
13 |
14 | class GameListAdapter : PagedListAdapter(DiffCallback()) {
15 |
16 | companion object {
17 | private const val DATA_VIEW_TYPE = 1
18 | private const val PROGRESS_VIEW_TYPE = 2
19 | }
20 |
21 | private var networkState: Status? = null
22 |
23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
24 | val layoutInflater = LayoutInflater.from(parent.context)
25 | return if (viewType == PROGRESS_VIEW_TYPE) {
26 | NetworkStateItemViewHolder(ItemNetworkStateBinding.inflate(layoutInflater, parent, false))
27 | } else {
28 | GameBindingViewHolder(ItemGameBinding.inflate(layoutInflater, parent, false))
29 | }
30 | }
31 |
32 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
33 | if (holder is GameBindingViewHolder) {
34 | getItem(position)?.let {
35 | holder.bind(it)
36 | }
37 | } else {
38 | (holder as NetworkStateItemViewHolder).bind(networkState)
39 | }
40 | }
41 |
42 | override fun getItemViewType(position: Int): Int {
43 | return if (hasExtraRow() && position == itemCount - 1) {
44 | PROGRESS_VIEW_TYPE
45 | } else {
46 | DATA_VIEW_TYPE
47 | }
48 | }
49 |
50 | override fun getItemCount(): Int {
51 | return super.getItemCount() + if (hasExtraRow()) 1 else 0
52 | }
53 |
54 | private fun hasExtraRow() = networkState != null && networkState != Status.SUCCESS
55 |
56 |
57 | fun setNetworkState(newNetworkState: Status?) {
58 | val previousState = this.networkState
59 | val hadExtraRow = hasExtraRow()
60 | this.networkState = newNetworkState
61 | val hasExtraRow = hasExtraRow()
62 | if (hadExtraRow != hasExtraRow) {
63 | if (hadExtraRow) {
64 | notifyItemRemoved(super.getItemCount())
65 | } else {
66 | notifyItemInserted(super.getItemCount())
67 | }
68 | } else if (hasExtraRow && previousState != newNetworkState) {
69 | notifyItemChanged(itemCount - 1)
70 | }
71 | }
72 |
73 | class DiffCallback : DiffUtil.ItemCallback() {
74 | override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem.id == newItem.id
75 |
76 | override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean = oldItem == newItem
77 | }
78 |
79 | class GameBindingViewHolder(private val binding: ItemGameBinding) :
80 | RecyclerView.ViewHolder(binding.root) {
81 |
82 | fun bind(item: Game) {
83 | binding.setVariable(BR.item, item)
84 | binding.executePendingBindings()
85 | }
86 | }
87 |
88 | class NetworkStateItemViewHolder(private val binding: ItemNetworkStateBinding) :
89 | RecyclerView.ViewHolder(binding.root) {
90 |
91 | fun bind(item: Status?) {
92 | binding.setVariable(BR.item, item)
93 | binding.executePendingBindings()
94 | }
95 | }
96 |
97 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/presentation/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.presentation
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.mobiaxe.core.extension.observe
6 | import com.mobiaxe.core.presentation.BaseFragment
7 | import com.mobiaxe.home.R
8 | import com.mobiaxe.home.databinding.FragmentHomeBinding
9 | import com.mobiaxe.home.koin.loadModules
10 | import com.mobiaxe.home.koin.unloadModules
11 | import org.koin.android.ext.android.inject
12 | import org.koin.androidx.viewmodel.ext.android.viewModel
13 |
14 | class HomeFragment: BaseFragment() {
15 |
16 | private val sectionListDataAdapter: SectionListDataAdapter by inject()
17 |
18 | override fun getLayoutId(): Int = R.layout.fragment_home
19 |
20 | override fun getViewModel(): Lazy = viewModel()
21 |
22 | override fun bindViewModel(dataBinding: FragmentHomeBinding) {
23 |
24 | }
25 |
26 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
27 | super.onViewCreated(view, savedInstanceState)
28 | initListeners()
29 | initObservers()
30 | vModel().getDashboard()
31 | }
32 |
33 | private fun initListeners() {
34 | getBinding().recyclerHome.adapter = sectionListDataAdapter
35 | }
36 |
37 | private fun initObservers() {
38 | vModel().apply {
39 | observe(sectionListLiveData) { sectionDataList ->
40 | sectionListDataAdapter.submitList(sectionDataList)
41 | observe(popularGamesPagedList) {
42 | sectionDataList[0].gameListAdapter?.submitList(it)
43 | }
44 | observe(networkState) {
45 | sectionDataList[0].gameListAdapter?.setNetworkState(it)
46 | }
47 | }
48 | }
49 | }
50 |
51 | override fun loadKoinModules() {
52 | loadModules()
53 | }
54 |
55 | override fun unloadKoinModules() {
56 | unloadModules()
57 | }
58 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/presentation/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.presentation
2 |
3 | import androidx.lifecycle.LiveData
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.Transformations
6 | import androidx.paging.LivePagedListBuilder
7 | import androidx.paging.PagedList
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.mobiaxe.core.data.Game
10 | import com.mobiaxe.core.data.Status
11 | import com.mobiaxe.core.presentation.BaseViewModel
12 | import com.mobiaxe.home.R
13 | import com.mobiaxe.home.data.SectionData
14 | import com.mobiaxe.home.domain.datasource.GamesDataSourceFactory
15 | import com.mobiaxe.home.domain.usecase.GetPopularGames
16 |
17 | class HomeViewModel(
18 | private val getPopularGames: GetPopularGames
19 | ): BaseViewModel(getPopularGames) {
20 |
21 | private val _sectionListLiveData: MutableLiveData> = MutableLiveData()
22 | val sectionListLiveData: LiveData> = _sectionListLiveData
23 |
24 | lateinit var popularGamesPagedList: LiveData>
25 |
26 | var networkState: LiveData? = null
27 |
28 | fun getDashboard() {
29 | val popularGamesDataFactory = GamesDataSourceFactory(getPopularGames)
30 |
31 | val config = PagedList.Config.Builder()
32 | .setPageSize(50)
33 | .setPrefetchDistance(20)
34 | .setEnablePlaceholders(false)
35 | .build()
36 |
37 | popularGamesPagedList = LivePagedListBuilder(popularGamesDataFactory, 50).build()
38 |
39 | getSection(mutableListOf(
40 | SectionData(R.string.title_popular_games, GameListAdapter(), null, RecyclerView.RecycledViewPool())))
41 |
42 | networkState = Transformations.switchMap(popularGamesDataFactory.gameDataSourceLiveData) {
43 | it.networkState
44 | }
45 | }
46 |
47 | private fun getSection(sectionDataList: MutableList) {
48 | _sectionListLiveData.value = sectionDataList
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/presentation/RecentlyReleasedListAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.presentation
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.paging.PagedListAdapter
6 | import androidx.recyclerview.widget.DiffUtil
7 | import androidx.recyclerview.widget.RecyclerView
8 | import com.mobiaxe.core.data.ReleaseDate
9 | import com.mobiaxe.home.databinding.ItemRecentlyReleasedBinding
10 | import com.mobiaxe.home.BR
11 |
12 | class RecentlyReleasedListAdapter : PagedListAdapter(DiffCallback()) {
13 |
14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
15 | val layoutInflater = LayoutInflater.from(parent.context)
16 | return GameBindingViewHolder(ItemRecentlyReleasedBinding.inflate(layoutInflater, parent, false))
17 | }
18 |
19 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
20 | getItem(position)?.let {
21 | (holder as GameBindingViewHolder).bind(it)
22 | }
23 |
24 | }
25 |
26 | class DiffCallback : DiffUtil.ItemCallback() {
27 | override fun areItemsTheSame(oldItem: ReleaseDate, newItem: ReleaseDate): Boolean = false
28 |
29 | override fun areContentsTheSame(oldItem: ReleaseDate, newItem: ReleaseDate): Boolean = false
30 | }
31 |
32 | class GameBindingViewHolder(private val binding: ItemRecentlyReleasedBinding) :
33 | RecyclerView.ViewHolder(binding.root) {
34 |
35 | fun bind(item: ReleaseDate) {
36 | binding.setVariable(BR.item, item)
37 | binding.executePendingBindings()
38 | }
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/presentation/SectionListDataAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.presentation
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import com.mobiaxe.core.presentation.DataBindingAdapter
5 | import com.mobiaxe.home.R
6 | import com.mobiaxe.home.data.SectionData
7 |
8 | class SectionListDataAdapter: DataBindingAdapter(DiffCallback()) {
9 |
10 | class DiffCallback: DiffUtil.ItemCallback() {
11 | override fun areItemsTheSame(oldItem: SectionData, newItem: SectionData): Boolean = false
12 |
13 | override fun areContentsTheSame(oldItem: SectionData, newItem: SectionData): Boolean = false
14 | }
15 |
16 | override fun getItemViewType(position: Int) = R.layout.item_section
17 | }
--------------------------------------------------------------------------------
/home/src/main/java/com/mobiaxe/home/util/BindingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.home.util
2 |
3 | import android.widget.ImageView
4 | import androidx.databinding.BindingAdapter
5 | import com.bumptech.glide.Glide
6 | import com.bumptech.glide.request.RequestOptions
7 | import com.bumptech.glide.request.target.Target
8 |
9 | @BindingAdapter("app:coverBig")
10 | fun setCoverBig(imageView: ImageView, hash: String?) {
11 | val context = imageView.context
12 | Glide.with(context)
13 | .load("https://images.igdb.com/igdb/image/upload/t_cover_big/$hash.jpg")
14 | .apply(RequestOptions().override(Target.SIZE_ORIGINAL))
15 | .into(imageView)
16 | }
--------------------------------------------------------------------------------
/home/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/home/src/main/res/layout/item_game.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
17 |
18 |
25 |
26 |
32 |
33 |
34 |
43 |
44 |
--------------------------------------------------------------------------------
/home/src/main/res/layout/item_network_state.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/home/src/main/res/layout/item_recently_released.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
17 |
18 |
25 |
26 |
32 |
33 |
34 |
43 |
44 |
--------------------------------------------------------------------------------
/home/src/main/res/layout/item_section.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 |
15 |
25 |
26 |
36 |
37 |
--------------------------------------------------------------------------------
/home/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Popular Games
4 |
--------------------------------------------------------------------------------
/keystore.properties:
--------------------------------------------------------------------------------
1 | ## This file is automatically generated by Android Studio.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file should *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 | #
7 | # Location of the SDK. This is only used by Gradle.
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | storePassword=storePassword
11 | keyPassword=keyPassword
12 | keyAlias=keyAlias
13 | storeFile=keystores/your_keystore_file
--------------------------------------------------------------------------------
/misc/home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/misc/home.jpg
--------------------------------------------------------------------------------
/misc/onboarding_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/misc/onboarding_1.jpg
--------------------------------------------------------------------------------
/misc/onboarding_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/misc/onboarding_2.jpg
--------------------------------------------------------------------------------
/misc/onboarding_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/misc/onboarding_3.jpg
--------------------------------------------------------------------------------
/misc/select_platform.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/misc/select_platform.jpg
--------------------------------------------------------------------------------
/onboarding/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/onboarding/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(GradlePluginId.ANDROID_DYNAMIC_FEATURE)
3 | id(GradlePluginId.KOTLIN_ANDROID)
4 | id(GradlePluginId.KOTLIN_ANDROID_EXTENSIONS)
5 | id(GradlePluginId.KOTLIN_KAPT)
6 | }
7 |
8 | android {
9 | compileSdkVersion(AndroidConfig.COMPILE_SDK_VERSION)
10 |
11 | defaultConfig {
12 | applicationId = AndroidConfig.APPLICATION_ID
13 | minSdkVersion(AndroidConfig.MIN_SDK_VERSION)
14 | targetSdkVersion(AndroidConfig.TARGET_SDK_VERSION)
15 | buildToolsVersion(AndroidConfig.BUILD_TOOLS_VERSION)
16 |
17 | versionCode = AndroidConfig.VERSION_CODE
18 | versionName = AndroidConfig.VERSION_NAME
19 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
20 | }
21 |
22 | dataBinding {
23 | isEnabled = true
24 | }
25 |
26 | }
27 |
28 | dependencies {
29 | implementation(project(ModuleDependency.APP))
30 | }
31 |
--------------------------------------------------------------------------------
/onboarding/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/data/OnboardingData.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.data
2 |
3 | import android.os.Parcelable
4 | import kotlinx.android.parcel.Parcelize
5 |
6 | @Parcelize
7 | data class OnboardingData(
8 | val title: String,
9 | val description: String,
10 | val background: Int,
11 | val position: Int
12 | ) : Parcelable
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/data/datasource/OnboardingCachedDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.data.datasource
2 |
3 | interface OnboardingCachedDataSource {
4 |
5 | suspend fun platformSaved(platformId: String?)
6 |
7 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/data/repository/OnboardingDataRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.data.repository
2 |
3 | import com.mobiaxe.onboarding.data.datasource.OnboardingCachedDataSource
4 | import com.mobiaxe.onboarding.domain.repository.OnboardingRepository
5 |
6 | class OnboardingDataRepository(private val onboardingCachedDataSource: OnboardingCachedDataSource): OnboardingRepository {
7 |
8 | override suspend fun savePlatform(platformId: String?) =
9 | onboardingCachedDataSource.platformSaved(platformId)
10 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/domain/datasource/OnboardingCachedDataSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.domain.datasource
2 |
3 | import com.mobiaxe.core.cache.WASDCachedDataStore
4 | import com.mobiaxe.onboarding.data.datasource.OnboardingCachedDataSource
5 |
6 | class OnboardingCachedDataSourceImpl(private val wasdCachedDataStore: WASDCachedDataStore): OnboardingCachedDataSource {
7 |
8 | override suspend fun platformSaved(platformId: String?) =
9 | wasdCachedDataStore.savePlatform(platformId)
10 |
11 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/domain/repository/OnboardingRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.domain.repository
2 |
3 |
4 | interface OnboardingRepository {
5 |
6 | suspend fun savePlatform(platformId: String?)
7 |
8 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/domain/usecase/SavePlatform.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.domain.usecase
2 |
3 | import com.mobiaxe.core.domain.SuspendUseCase
4 | import com.mobiaxe.onboarding.domain.repository.OnboardingRepository
5 |
6 | class SavePlatform(
7 | private val onboardingRepository: OnboardingRepository): SuspendUseCase() {
8 |
9 | override suspend fun run(params: String) = onboardingRepository.savePlatform(params)
10 |
11 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/koin/Modules.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.koin
2 |
3 | import com.mobiaxe.onboarding.data.datasource.OnboardingCachedDataSource
4 | import com.mobiaxe.onboarding.data.repository.OnboardingDataRepository
5 | import com.mobiaxe.onboarding.domain.datasource.OnboardingCachedDataSourceImpl
6 | import com.mobiaxe.onboarding.domain.repository.OnboardingRepository
7 | import com.mobiaxe.onboarding.domain.usecase.SavePlatform
8 | import com.mobiaxe.onboarding.presentation.OnboardingActivity
9 | import com.mobiaxe.onboarding.presentation.OnboardingAdapter
10 | import com.mobiaxe.onboarding.presentation.OnboardingViewModel
11 | import com.mobiaxe.onboarding.presentation.ParallaxPageTransformer
12 | import org.koin.android.ext.koin.androidApplication
13 | import org.koin.androidx.viewmodel.dsl.viewModel
14 | import org.koin.core.context.loadKoinModules
15 | import org.koin.core.context.unloadKoinModules
16 | import org.koin.dsl.module
17 |
18 | fun loadModules() = loadKoinModules(
19 | listOf(
20 | viewModelModule,
21 | onboardingModule,
22 | repositoryModule,
23 | useCaseModule
24 | )
25 | )
26 |
27 | fun unloadModules() = unloadKoinModules(
28 | listOf(
29 | viewModelModule,
30 | onboardingModule,
31 | repositoryModule,
32 | useCaseModule
33 | )
34 | )
35 |
36 | val viewModelModule = module(override = true) {
37 | viewModel { OnboardingViewModel(androidApplication(), get(), get()) }
38 | }
39 |
40 | val onboardingModule = module(override = true) {
41 | factory { ParallaxPageTransformer() }
42 | factory { (activity: OnboardingActivity) -> OnboardingAdapter(activity) }
43 | factory { (activity: OnboardingActivity) -> activity.ViewPager2PageChangeCallback() }
44 | }
45 |
46 | val repositoryModule = module {
47 | factory { OnboardingDataRepository(get()) }
48 | factory { OnboardingCachedDataSourceImpl(get()) }
49 | }
50 |
51 | val useCaseModule = module {
52 | factory { SavePlatform(get()) }
53 | }
54 |
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/presentation/OnboardingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.presentation
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.viewpager2.widget.ViewPager2
6 | import com.google.android.material.tabs.TabLayoutMediator
7 | import com.mobiaxe.core.presentation.BaseActivity
8 | import com.mobiaxe.onboarding.R
9 | import com.mobiaxe.onboarding.databinding.ActivityOnboardingBinding
10 | import com.mobiaxe.onboarding.koin.loadModules
11 | import com.mobiaxe.onboarding.koin.unloadModules
12 | import org.koin.android.ext.android.inject
13 | import org.koin.androidx.viewmodel.ext.android.viewModel
14 | import org.koin.core.parameter.parametersOf
15 |
16 |
17 | class OnboardingActivity : BaseActivity() {
18 |
19 | companion object {
20 | private const val IMAGE_PARALLAX = 2f
21 | private const val TITLE_PARALLAX_ENTER = -1.95f
22 | private const val TITLE_PARALLAX_EXIT = 0.5f
23 | }
24 |
25 | private val onboardingAdapter: OnboardingAdapter by inject { parametersOf(this) }
26 |
27 | private val mPageTransformer: ParallaxPageTransformer by inject()
28 |
29 | private val viewPage2Callback: ViewPager2PageChangeCallback by inject { parametersOf(this) }
30 |
31 | private val tabIconArray = arrayOf(
32 | R.drawable.tab_page_one_selector,
33 | R.drawable.tab_page_two_selector,
34 | R.drawable.tab_page_three_selector,
35 | R.drawable.tab_page_four_selector
36 | )
37 |
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | addFragments()
41 | setUpParallaxEffect()
42 | setUpTabIndicator()
43 | initListener()
44 | }
45 |
46 | override fun getLayoutId(): Int = R.layout.activity_onboarding
47 |
48 | override fun getViewModel(): Lazy = viewModel()
49 |
50 | override fun bindViewModel(dataBinding: ActivityOnboardingBinding) {
51 |
52 | }
53 |
54 | override fun onResume() {
55 | super.onResume()
56 | runBasicImmersive()
57 | }
58 |
59 | private fun runBasicImmersive() {
60 | val uiOptions: Int = window.decorView.systemUiVisibility
61 | var newUiOptions = uiOptions
62 | newUiOptions = newUiOptions xor View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
63 | newUiOptions = newUiOptions xor View.SYSTEM_UI_FLAG_FULLSCREEN
64 | newUiOptions = newUiOptions xor View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
65 | window.decorView.systemUiVisibility = newUiOptions
66 | }
67 |
68 | private fun addFragments() {
69 | getViewModel().value.getOnboardingData().map {
70 | onboardingAdapter.addFragment(OnboardingFragment.newInstance(it))
71 | }
72 | onboardingAdapter.addFragment(SelectPlatformFragment.newInstance())
73 | }
74 |
75 | private fun setUpParallaxEffect() {
76 | getBinding().apply {
77 | pagerOnboarding.adapter = onboardingAdapter
78 | pagerOnboarding.registerOnPageChangeCallback(viewPage2Callback)
79 |
80 | val transformationImage = ParallaxPageTransformer().ParallaxTransformInformation(
81 | R.id.img_onboarding,
82 | IMAGE_PARALLAX,
83 | IMAGE_PARALLAX
84 | )
85 | mPageTransformer.addViewToParallax(transformationImage)
86 |
87 | val transformationTitle = ParallaxPageTransformer().ParallaxTransformInformation(
88 | R.id.title_onboarding,
89 | TITLE_PARALLAX_ENTER,
90 | TITLE_PARALLAX_EXIT
91 | )
92 | mPageTransformer.addViewToParallax(transformationTitle)
93 |
94 | val transformationDescription = ParallaxPageTransformer().ParallaxTransformInformation(
95 | R.id.description_onboarding,
96 | TITLE_PARALLAX_ENTER,
97 | TITLE_PARALLAX_EXIT
98 | )
99 | mPageTransformer.addViewToParallax(transformationDescription)
100 |
101 | pagerOnboarding.setPageTransformer(mPageTransformer)
102 |
103 | TabLayoutMediator(pagerIndicator, pagerOnboarding) { _, _ ->
104 | }.attach()
105 | }
106 | }
107 |
108 | private fun setUpTabIndicator() {
109 | getBinding().pagerIndicator.apply {
110 | onboardingAdapter.getFragmentList().forEachIndexed { i, _ ->
111 | this.getTabAt(i)?.setIcon(tabIconArray[i])
112 | }
113 | }
114 | }
115 |
116 | private fun initListener() {
117 | getBinding().apply {
118 | pagerRightBtn.setOnClickListener {
119 | if (!vm().lastPageObservable.get())
120 | pagerOnboarding.setCurrentItem(pagerOnboarding.currentItem + 1, true)
121 | }
122 |
123 | pagerLeftBtn.setOnClickListener {
124 | if (!vm().lastPageObservable.get())
125 | pagerOnboarding.setCurrentItem(pagerOnboarding.currentItem - 1, true)
126 | }
127 | }
128 | }
129 |
130 | override fun loadKoinModules() {
131 | loadModules()
132 | }
133 |
134 | override fun unloadKoinModules() {
135 | unloadModules()
136 | }
137 |
138 | override fun onDestroy() {
139 | super.onDestroy()
140 | getBinding().pagerOnboarding.unregisterOnPageChangeCallback(viewPage2Callback)
141 | }
142 |
143 | inner class ViewPager2PageChangeCallback : ViewPager2.OnPageChangeCallback() {
144 | override fun onPageScrolled(
145 | position: Int,
146 | positionOffset: Float,
147 | positionOffsetPixels: Int
148 | ) {
149 | if (onboardingAdapter.getFragmentList().size > 1) {
150 | val newProgress =
151 | (position + positionOffset) / (onboardingAdapter.getFragmentList().size - 1)
152 | getBinding().onboardingRoot.progress = newProgress
153 | }
154 | }
155 |
156 | override fun onPageSelected(position: Int) {
157 | vm().lastPageObservable.set(onboardingAdapter.getFragmentList().size.minus(1) == position)
158 | }
159 | }
160 |
161 | override fun onBackPressed() {
162 | finishAfterTransition()
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/presentation/OnboardingAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.presentation
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.fragment.app.FragmentActivity
5 | import androidx.viewpager2.adapter.FragmentStateAdapter
6 |
7 | class OnboardingAdapter(context: FragmentActivity) : FragmentStateAdapter(context) {
8 |
9 | private var fragmentList = mutableListOf()
10 |
11 |
12 | fun getFragmentList() = fragmentList
13 |
14 | fun addFragment(fragment: Fragment) {
15 | fragmentList.add(fragment)
16 | }
17 |
18 | override fun getItemCount(): Int = fragmentList.size
19 |
20 | override fun createFragment(position: Int): Fragment = fragmentList[position]
21 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/presentation/OnboardingFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.presentation
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import com.mobiaxe.core.presentation.BaseFragment
8 | import com.mobiaxe.onboarding.R
9 | import com.mobiaxe.onboarding.data.OnboardingData
10 | import com.mobiaxe.onboarding.databinding.FragmentOnboardingBinding
11 | import org.koin.androidx.viewmodel.ext.android.viewModel
12 |
13 | class OnboardingFragment : BaseFragment() {
14 |
15 | private lateinit var onboardingData: OnboardingData
16 |
17 | companion object {
18 |
19 | private const val DATA = "data"
20 |
21 | fun newInstance(data: OnboardingData): OnboardingFragment {
22 | val fragment = OnboardingFragment()
23 | val b = Bundle()
24 | b.putParcelable(DATA, data)
25 | fragment.arguments = b
26 | return fragment
27 | }
28 | }
29 |
30 | override fun getLayoutId(): Int = R.layout.fragment_onboarding
31 |
32 | override fun getViewModel(): Lazy = viewModel()
33 |
34 | override fun bindViewModel(dataBinding: FragmentOnboardingBinding) {
35 | dataBinding.onboardingData = onboardingData
36 | dataBinding.executePendingBindings()
37 | }
38 |
39 | override fun onCreateView(
40 | inflater: LayoutInflater,
41 | container: ViewGroup?,
42 | savedInstanceState: Bundle?
43 | ): View? {
44 | val rootView = super.onCreateView(inflater, container, savedInstanceState)
45 | onboardingData = arguments?.get(DATA) as OnboardingData
46 | rootView?.tag = onboardingData.position
47 | return rootView
48 | }
49 |
50 | override fun loadKoinModules() {
51 | }
52 |
53 | override fun unloadKoinModules() {
54 | }
55 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/presentation/OnboardingViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.presentation
2 |
3 | import android.app.Application
4 | import androidx.databinding.ObservableBoolean
5 | import androidx.lifecycle.LiveData
6 | import androidx.lifecycle.MutableLiveData
7 | import com.mobiaxe.core.presentation.BaseViewModel
8 | import com.mobiaxe.onboarding.R
9 | import com.mobiaxe.onboarding.data.OnboardingData
10 | import com.mobiaxe.onboarding.domain.usecase.SavePlatform
11 | import com.mobiaxe.wasd.splash.domain.usecase.OnboardingPassed
12 |
13 | class OnboardingViewModel(
14 | private val appContext: Application,
15 | private val onboardingPassed: OnboardingPassed,
16 | private val savePlatform: SavePlatform
17 | ): BaseViewModel(savePlatform) {
18 |
19 | private val _passOnboardingLiveData = MutableLiveData()
20 | val passOnboardingLiveData: LiveData = _passOnboardingLiveData
21 |
22 | val lastPageObservable = ObservableBoolean(false)
23 | val checkWindowsObsv = ObservableBoolean(false)
24 | val checkPlaystationObsv = ObservableBoolean(false)
25 | val checkXboxObsv = ObservableBoolean(false)
26 | val checkNintendoObsv = ObservableBoolean(false)
27 |
28 | private val selectedPlatforms = mutableSetOf()
29 |
30 | companion object {
31 | private const val SWITCH = 130
32 | private const val PC = 6
33 | private const val XBOX = 49
34 | private const val PS4 = 48
35 | }
36 |
37 | fun isOnboardingPassed() {
38 | _passOnboardingLiveData.value = onboardingPassed.execute(true)
39 | }
40 |
41 | fun getOnboardingData(): List {
42 | val titleList = appContext.resources.getStringArray(R.array.title_onboarding)
43 | val descriptionList = appContext.resources.getStringArray(R.array.description_onboarding)
44 | val drawableList = mutableListOf(
45 | R.drawable.witcher_onboarding_one,
46 | R.drawable.godofwar_onboarding_two,
47 | R.drawable.rdr_onboarding_three)
48 |
49 | return (titleList.indices).map { i ->
50 | OnboardingData(titleList[i], descriptionList[i], drawableList[i], i)
51 | }
52 | }
53 |
54 | fun selectWindows() {
55 | if (!checkWindowsObsv.get()) {
56 | checkWindowsObsv.set(true)
57 | selectedPlatforms.add(PC)
58 | } else {
59 | checkWindowsObsv.set(false)
60 | selectedPlatforms.remove(PC)
61 | }
62 |
63 | }
64 |
65 | fun selectPlaystation() {
66 | if (!checkPlaystationObsv.get()) {
67 | checkPlaystationObsv.set(true)
68 | selectedPlatforms.add(PS4)
69 | } else {
70 | checkPlaystationObsv.set(false)
71 | selectedPlatforms.remove(PS4)
72 | }
73 | }
74 |
75 | fun selectXBox() {
76 | if (!checkXboxObsv.get()) {
77 | checkXboxObsv.set(true)
78 | selectedPlatforms.add(XBOX)
79 | } else {
80 | checkXboxObsv.set(false)
81 | selectedPlatforms.remove(XBOX)
82 | }
83 | }
84 |
85 | fun selectNintendo() {
86 | if (!checkNintendoObsv.get()) {
87 | checkNintendoObsv.set(true)
88 | selectedPlatforms.add(SWITCH)
89 | } else {
90 | checkNintendoObsv.set(false)
91 | selectedPlatforms.remove(SWITCH)
92 | }
93 | }
94 |
95 | fun savePlatform() {
96 | savePlatform.execute(selectedPlatforms.joinToString(prefix = "(", postfix = ")")) {
97 | isOnboardingPassed()
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/presentation/ParallaxPageTransformer.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.presentation
2 |
3 | import android.view.View
4 | import androidx.viewpager2.widget.ViewPager2
5 |
6 | class ParallaxPageTransformer : ViewPager2.PageTransformer {
7 |
8 | private val mViewsToParallax = mutableListOf()
9 |
10 | companion object {
11 | const val PARALLAX_EFFECT_DEFAULT = -101.1986f
12 | }
13 |
14 | fun addViewToParallax(viewInfo: ParallaxTransformInformation): ParallaxPageTransformer {
15 | mViewsToParallax.add(viewInfo)
16 | return this
17 | }
18 |
19 | override fun transformPage(page: View, position: Float) {
20 | val pageWidth: Int = page.width
21 |
22 | if (position < -1) {
23 | page.alpha = 1f
24 | } else if (position <= 1 && mViewsToParallax.isNotEmpty()) {
25 | for (parallaxTransformInformation in mViewsToParallax) {
26 | applyParallaxEffect(
27 | page, position, pageWidth, parallaxTransformInformation,
28 | position > 0
29 | )
30 | }
31 | } else {
32 | page.alpha = 1f
33 | }
34 | }
35 |
36 | private fun applyParallaxEffect(
37 | page: View,
38 | position: Float,
39 | pageWidth: Int,
40 | information: ParallaxTransformInformation,
41 | isEnter: Boolean
42 | ) {
43 | if (information.isValid() && page.findViewById(information.resource) != null) {
44 | if (isEnter && information.isEnterDefault().not()) {
45 | page.findViewById(information.resource).translationX = -position * (pageWidth / information.parallaxEnterEffect)
46 | } else if (isEnter.not() && information.isExitDefault().not()) {
47 | page.findViewById(information.resource).translationX = -position * (pageWidth / information.parallaxExitEffect)
48 | }
49 | }
50 | }
51 |
52 | inner class ParallaxTransformInformation(
53 | val resource: Int,
54 | val parallaxEnterEffect: Float,
55 | val parallaxExitEffect: Float
56 | ) {
57 |
58 | fun isValid() = parallaxEnterEffect != 0f && parallaxExitEffect != 0f && resource != -1
59 |
60 | fun isEnterDefault() = parallaxEnterEffect == PARALLAX_EFFECT_DEFAULT
61 |
62 | fun isExitDefault() = parallaxExitEffect == PARALLAX_EFFECT_DEFAULT
63 | }
64 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/presentation/SelectPlatformFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.presentation
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.mobiaxe.core.extension.observe
6 | import com.mobiaxe.core.extension.start
7 | import com.mobiaxe.core.presentation.BaseFragment
8 | import com.mobiaxe.onboarding.R
9 | import com.mobiaxe.onboarding.databinding.FragmentPlatformBinding
10 | import com.mobiaxe.wasd.navigation.Navigation
11 | import org.koin.androidx.viewmodel.ext.android.viewModel
12 |
13 | class SelectPlatformFragment : BaseFragment() {
14 |
15 | companion object {
16 |
17 | fun newInstance(): SelectPlatformFragment {
18 | val fragment = SelectPlatformFragment()
19 | val b = Bundle()
20 | fragment.arguments = b
21 | return fragment
22 | }
23 | }
24 |
25 | override fun getLayoutId(): Int = R.layout.fragment_platform
26 |
27 | override fun getViewModel(): Lazy = viewModel()
28 |
29 | override fun bindViewModel(dataBinding: FragmentPlatformBinding) {
30 | dataBinding.viewModel = vModel()
31 | }
32 |
33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
34 | super.onViewCreated(view, savedInstanceState)
35 | initListeners()
36 | initObservers()
37 | }
38 |
39 | private fun initObservers() {
40 | with(vModel()) {
41 | observe(passOnboardingLiveData) {
42 | requireActivity().start(Navigation.navigateOnboardingOrDashboard(it))
43 | requireActivity().finish()
44 | }
45 | }
46 | }
47 |
48 |
49 | private fun initListeners() {
50 | getBinding().apply {
51 | btnWindows.setOnClickListener {
52 | vModel().selectWindows()
53 | }
54 |
55 | btnNintendo.setOnClickListener {
56 | vModel().selectNintendo()
57 | }
58 |
59 | btnPlaystation.setOnClickListener {
60 | vModel().selectPlaystation()
61 | }
62 |
63 | btnXbox.setOnClickListener {
64 | vModel().selectXBox()
65 | }
66 |
67 | btnContinue.setOnClickListener {
68 | vModel().savePlatform()
69 | }
70 | }
71 | }
72 |
73 | override fun loadKoinModules() {
74 | }
75 |
76 | override fun unloadKoinModules() {
77 | }
78 | }
--------------------------------------------------------------------------------
/onboarding/src/main/java/com/mobiaxe/onboarding/util/BindingUtils.kt:
--------------------------------------------------------------------------------
1 | package com.mobiaxe.onboarding.util
2 |
3 | import android.graphics.Rect
4 | import android.view.TouchDelegate
5 | import android.view.View
6 | import android.widget.ImageView
7 | import androidx.databinding.BindingAdapter
8 | import com.bumptech.glide.Glide
9 | import com.bumptech.glide.request.RequestOptions
10 | import com.google.android.material.card.MaterialCardView
11 | import com.bumptech.glide.request.target.Target
12 | import com.mobiaxe.core.extension.px
13 |
14 |
15 | @BindingAdapter("isSelected")
16 | fun MaterialCardView.setSelected(isSelected: Boolean) {
17 | isChecked = isSelected
18 | isHovered = isSelected
19 | isPressed = isSelected
20 | }
21 |
22 | @BindingAdapter("imageDrawable")
23 | fun setImageDrawable(imageView: ImageView, res: Int?) {
24 | val context = imageView.context
25 | Glide.with(context).load(res).apply(RequestOptions().override(Target.SIZE_ORIGINAL)).into(imageView)
26 | }
27 |
28 | @BindingAdapter("increaseClickingArea")
29 | fun increaseClickingArea(view: View, extraSpace: Int) {
30 | val parent = view.parent as View
31 | parent.post {
32 | val touchableArea = Rect()
33 | view.getHitRect(touchableArea)
34 | touchableArea.top -= extraSpace.px
35 | touchableArea.bottom += extraSpace.px
36 | touchableArea.left -= extraSpace.px
37 | touchableArea.right += extraSpace.px
38 | parent.touchDelegate = TouchDelegate(touchableArea, view)
39 | }
40 | }
--------------------------------------------------------------------------------
/onboarding/src/main/res/color/onboarding_continue_btn_color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable-xxxhdpi/godofwar_onboarding_two.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/onboarding/src/main/res/drawable-xxxhdpi/godofwar_onboarding_two.jpg
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable-xxxhdpi/platform_background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/onboarding/src/main/res/drawable-xxxhdpi/platform_background.jpg
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable-xxxhdpi/rdr_onboarding_three.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/onboarding/src/main/res/drawable-xxxhdpi/rdr_onboarding_three.jpg
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable-xxxhdpi/witcher_onboarding_one.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abalta/Kotlin-DynamicFeature-Clean/98217b9dfd24438cb81b5b4208f7fd9679bf43f1/onboarding/src/main/res/drawable-xxxhdpi/witcher_onboarding_one.jpg
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_nintendo.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
39 |
42 |
45 |
46 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_nintendo_colored.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
39 |
42 |
43 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_nintendo_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_onboarding_left.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_onboarding_right.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_pc_colored.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
39 |
42 |
45 |
48 |
51 |
54 |
57 |
60 |
63 |
66 |
67 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_pc_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_playstation.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_ps4_colored.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_ps4_white.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_tick.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_windows.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_xbox.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_xbox_colored.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
39 |
42 |
45 |
48 |
51 |
54 |
57 |
58 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/ic_xbox_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
37 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/tab_page_four_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/tab_page_one_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/tab_page_three_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/drawable/tab_page_two_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/layout/activity_onboarding.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
22 |
23 |
33 |
34 |
44 |
45 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/layout/fragment_onboarding.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
14 |
15 |
25 |
26 |
38 |
39 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | - Discover
7 | - Play
8 | - Gamer
9 |
10 |
11 |
12 |
13 |
14 | - Check out popular games.\nFollow upcoming.
15 | - Make your favorite list.\nComment and like.
16 | - Casual, Pro, Hardcore.\nWhat kind of gamer are you?
17 |
18 |
19 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Choose your platform
4 | Are the keyboard and mouse indispensable?\nOr are you a hardcore console player?
5 | PC
6 | Play Station
7 | Xbox
8 | Nintendo
9 | Continue
10 |
11 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/onboarding/src/main/res/xml/onboarding_scene.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
14 |
15 |
19 |
20 |
24 |
25 |
29 |
30 |
34 |
35 |
36 |
37 |
38 |
47 |
48 |
56 |
57 |
58 |
59 |
68 |
69 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":app")
2 | include(":core")
3 | include(":onboarding")
4 | include(":home")
5 |
6 | rootProject.name = "WASD"
7 | rootProject.buildFileName = "build.gradle.kts"
8 |
9 |
--------------------------------------------------------------------------------