├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── salihkinali │ │ └── gameguide │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── salihkinali │ │ │ ├── GameGuideApp.kt │ │ │ └── gameguide │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── salihkinali │ └── gameguide │ └── ExampleUnitTest.kt ├── build.gradle ├── core ├── common │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── salihkinali │ │ │ └── common │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── salihkinali │ │ │ ├── ResponseHandle.kt │ │ │ └── common │ │ │ ├── NetworkResponse.kt │ │ │ ├── coroutine │ │ │ ├── DispachersModule.kt │ │ │ └── IoDispacher.kt │ │ │ └── mapper │ │ │ ├── GameListMapper.kt │ │ │ └── GameMapper.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── salihkinali │ │ └── common │ │ └── ExampleUnitTest.kt ├── data │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── salihkinali │ │ │ └── data │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── salihkinali │ │ │ │ └── data │ │ │ │ ├── api │ │ │ │ └── GameApi.kt │ │ │ │ ├── di │ │ │ │ ├── DataModule.kt │ │ │ │ ├── network │ │ │ │ │ └── NetworkModule.kt │ │ │ │ └── repository │ │ │ │ │ └── GameRepositoryModule.kt │ │ │ │ ├── dto │ │ │ │ ├── detail │ │ │ │ │ ├── AddedByStatus.kt │ │ │ │ │ ├── Developer.kt │ │ │ │ │ ├── EsrbRating.kt │ │ │ │ │ ├── Genre.kt │ │ │ │ │ ├── MetacriticPlatform.kt │ │ │ │ │ ├── ParentPlatform.kt │ │ │ │ │ ├── Platform.kt │ │ │ │ │ ├── PlatformX.kt │ │ │ │ │ ├── PlatformXX.kt │ │ │ │ │ ├── PlatformXXX.kt │ │ │ │ │ ├── Publisher.kt │ │ │ │ │ ├── Rating.kt │ │ │ │ │ ├── Reactions.kt │ │ │ │ │ ├── Requirements.kt │ │ │ │ │ ├── SingleGameResponse.kt │ │ │ │ │ ├── Store.kt │ │ │ │ │ ├── StoreX.kt │ │ │ │ │ ├── Tag.kt │ │ │ │ │ └── screenshots │ │ │ │ │ │ ├── GameResult.kt │ │ │ │ │ │ └── SingleGameScreenShotResponse.kt │ │ │ │ └── game │ │ │ │ │ ├── AddedByStatus.kt │ │ │ │ │ ├── EsrbRating.kt │ │ │ │ │ ├── Filters.kt │ │ │ │ │ ├── Genre.kt │ │ │ │ │ ├── ParentPlatform.kt │ │ │ │ │ ├── Platform.kt │ │ │ │ │ ├── PlatformX.kt │ │ │ │ │ ├── PlatformXX.kt │ │ │ │ │ ├── Rating.kt │ │ │ │ │ ├── RequirementsEn.kt │ │ │ │ │ ├── RequirementsRu.kt │ │ │ │ │ ├── Result.kt │ │ │ │ │ ├── ShortScreenshot.kt │ │ │ │ │ ├── Store.kt │ │ │ │ │ ├── StoreX.kt │ │ │ │ │ ├── Tag.kt │ │ │ │ │ ├── TotalGameResponse.kt │ │ │ │ │ ├── Year.kt │ │ │ │ │ └── YearX.kt │ │ │ │ ├── mapper │ │ │ │ ├── GameScreenShotMapper.kt │ │ │ │ ├── SingleGameMapper.kt │ │ │ │ └── TotalEntityMapper.kt │ │ │ │ ├── repository │ │ │ │ └── GameRepositoryImpl.kt │ │ │ │ └── source │ │ │ │ ├── RemoteDataSource.kt │ │ │ │ └── RemoteDataSourceImpl.kt │ │ └── resources │ │ │ ├── SingleGameInfoResponse.json │ │ │ └── TotalGameInfoResponse.json │ │ └── test │ │ └── java │ │ └── com │ │ └── salihkinali │ │ └── data │ │ ├── ExampleUnitTest.kt │ │ ├── TestConstants.kt │ │ ├── api │ │ └── GameApiTest.kt │ │ ├── mapper │ │ ├── GameScreenShotMapperTest.kt │ │ ├── SingleGameMapperTest.kt │ │ └── TotalEntityMapperTest.kt │ │ ├── repository │ │ ├── FakeGameRepository.kt │ │ └── GameRepositoryTest.kt │ │ └── source │ │ └── RemoteDataSourceTest.kt ├── domain │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── salihkinali │ │ │ └── domain │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── salihkinali │ │ │ └── domain │ │ │ ├── di │ │ │ └── UseCaseModule.kt │ │ │ ├── entity │ │ │ ├── GameScreenShotEntity.kt │ │ │ ├── SingleGameEntity.kt │ │ │ └── TotalGameEntity.kt │ │ │ ├── repository │ │ │ └── GameRepository.kt │ │ │ └── usecase │ │ │ ├── GetGameScreenShotUseCase.kt │ │ │ ├── GetGameScreenUseCaseImpl.kt │ │ │ ├── GetSingleGameUseCase.kt │ │ │ ├── GetSingleGameUseCaseImpl.kt │ │ │ ├── GetTotalGameUseCase.kt │ │ │ └── GetTotalGameUseCaseImpl.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── salihkinali │ │ └── domain │ │ ├── ExampleUnitTest.kt │ │ ├── TestConstants.kt │ │ ├── screenshots │ │ ├── FakeGetGameScreenShotUseCase.kt │ │ └── GetGameScreenShotUseCaseTest.kt │ │ ├── single │ │ ├── FakeSingleGameUseCase.kt │ │ └── GetSingleGameUseCaseTest.kt │ │ └── total │ │ ├── FakeGetTotalGameUseCase.kt │ │ └── GetTotalGameUseCaseTest.kt └── ui │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── salihkinali │ │ └── ui │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── salihkinali │ │ │ └── ui │ │ │ ├── base │ │ │ ├── BaseRecyclerAdapter.kt │ │ │ └── BaseViewHolder.kt │ │ │ ├── common │ │ │ └── UiResponseState.kt │ │ │ └── extension │ │ │ ├── ViewBindingExtension.kt │ │ │ └── ViewExtension.kt │ └── res │ │ └── values │ │ ├── colors.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── salihkinali │ └── ui │ └── ExampleUnitTest.kt ├── feature ├── detail │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── salihkinali │ │ │ └── feature │ │ │ └── detail │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── salihkinali │ │ │ │ └── feature │ │ │ │ └── detail │ │ │ │ ├── DetailFragment.kt │ │ │ │ ├── DetailScreenShotAdapter.kt │ │ │ │ ├── DetailViewModel.kt │ │ │ │ ├── GameScreenShotUiData.kt │ │ │ │ ├── GameScreenShotUiMapperImpl.kt │ │ │ │ ├── GameScreenShotViewHolder.kt │ │ │ │ ├── SingleGameUiData.kt │ │ │ │ ├── SingleGameUiDataImpl.kt │ │ │ │ └── di │ │ │ │ └── DetailMapperModule.kt │ │ └── res │ │ │ └── layout │ │ │ ├── app_screenshots_item.xml │ │ │ └── fragment_detail.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── salihkinali │ │ └── feature │ │ └── detail │ │ └── ExampleUnitTest.kt └── home │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── salihkinali │ │ └── feature │ │ └── home │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── salihkinali │ │ │ └── feature │ │ │ └── home │ │ │ ├── GameViewHolder.kt │ │ │ ├── HomeAdapter.kt │ │ │ ├── HomeFragment.kt │ │ │ ├── HomeViewModel.kt │ │ │ ├── TotalGameUiData.kt │ │ │ ├── TotalGameUiMapperImpl.kt │ │ │ └── di │ │ │ └── MapperModule.kt │ └── res │ │ └── layout │ │ ├── adapter_list_item.xml │ │ └── fragment_home.xml │ └── test │ └── java │ └── com │ └── salihkinali │ └── feature │ └── home │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── navigation ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── salihkinali │ │ └── navigation │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── sharedRes │ │ └── navigation │ │ └── nav_graph.xml │ └── test │ └── java │ └── com │ └── salihkinali │ └── navigation │ └── ExampleUnitTest.kt ├── previews ├── android-test.png ├── clean_arch.png ├── clean_architecture.png ├── layers.png ├── screenshot_four.png ├── screenshot_one.png ├── screenshot_three.png ├── screenshot_two.png └── usecase.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Game Guide -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

GameGuide

2 | This application is a game promotion application. It has been designed in accordance with the understanding of clean architecture. 3 | 4 | ## App ScreenShots 5 | 6 |

7 | 8 | 9 | 10 | 11 |

12 | 13 | ## App Architecture 14 | 15 |

16 | 17 | What is a USE CASE & Why do we need them ? 18 | 19 | In MVVM, we use to put all our business logic into our ViewModels but the problem with that is, the 20 | bigger your project gets, 21 | the more number of ViewModels you have and all your business logic is divided into various 22 | viewModels. 23 | That’s where clean architecture comes into the picture, we use USE CASES instead of ViewModels for 24 | all our Business logic. 25 | 26 | 27 | 28 | 29 | Use case is an action we can do within a feature. For example, We have feature called “Profile” 30 | which can have actions such as 31 | 32 | - Getting user profile data 33 | - Updating user profile photo 34 | - Editing user profile 35 | All the above are USE CASES of a feature. 36 | 37 |

38 | 39 | ## Unit & Ui Testing 40 | Unit tests added into data and domain module. 41 | 42 | Testing your app is an integral part of the app development process. By running tests against your 43 | app consistently, you can verify your app's correctness, functional behavior, and usability before 44 | you release it publicly. 45 | 46 | Testing also offers the following advantages: 47 | 48 | - Rapid feedback on failures. 49 | - Early failure detection in the development cycle. 50 | - Safer code refactoring, allowing you to optimize code without worrying about regressions. 51 | - Stable development velocity, helping you minimize technical debt. 52 | 53 | 54 | 55 | 56 | ## Tech stack & Open-source Libraries 57 | 58 | - Minimum SDK level 21 59 | - 100% [Kotlin](https://kotlinlang.org/) based 60 | - [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) 61 | - [Android Architecture Components](https://developer.android.com/topic/libraries/architecture) - 62 | Collection of libraries that help you design robust, testable, and maintainable apps. 63 | - A single-activity architecture, 64 | - using 65 | the [Navigation component](https://developer.android.com/guide/navigation/navigation-getting-started) 66 | to manage fragment operations. 67 | - [Kotlin Flow](https://https://kotlinlang.org/docs/flow.html) Flows are built on top of coroutines 68 | and can provide multiple values. A flow is conceptually a stream of data that can be computed 69 | asynchronously. The emitted values must be of the same type 70 | - [Lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle) - perform an 71 | action when lifecycle state changes 72 | - [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel) - Stores 73 | UI-related data that isn't destroyed on UI changes. 74 | - [Repository](https://developer.android.com/topic/architecture/data-layer) - Located in data layer 75 | that contains application data and business logic. 76 | - [ViewBinding](https://developer.android.com/topic/libraries/view-binding) - Generates a binding 77 | class for each XML layout file present in that module and allows you to more easily write code 78 | that interacts with views. 79 | - [Usecases](https://developer.android.com/topic/architecture/domain-layer) - The domain layer is 80 | responsible for encapsulating complex business logic, or simple business logic that is reused by 81 | multiple ViewModels. This layer is optional because not all apps will have these requirements. 82 | - [Android Hilt](https://developer.android.com/training/dependency-injection/hilt-android) - 83 | Dependency Injection Library 84 | - [Retrofit](https://square.github.io/retrofit/) A type-safe HTTP client for Android and Java 85 | - [Modularization](https://developer.android.com/topic/modularization) Modularization is a means of 86 | structuring your codebase in a way that improves maintainability and helps avoid these problems. 87 | - [Gson](https://github.com/google/gson) Gson is a Java library that can be used to convert Java 88 | Objects into their JSON representation. It can also be used to convert a JSON string to an 89 | equivalent Java object 90 | - [Glide](https://github.com/bumptech/glide) An image loading library for Android backed by Kotlin 91 | Coroutines. Glide supports fetching, decoding, and displaying video stills, images, and animated 92 | GIFs. Glide includes a flexible API that allows developers to plug in to almost any network stack. 93 | - Material Design 94 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'com.google.dagger.hilt.android' 6 | id 'androidx.navigation.safeargs' 7 | 8 | } 9 | 10 | android { 11 | namespace 'com.salihkinali.gameguide' 12 | compileSdk 33 13 | 14 | buildFeatures { 15 | viewBinding true 16 | } 17 | 18 | defaultConfig { 19 | applicationId "com.salihkinali.gameguide" 20 | minSdk 21 21 | targetSdk 33 22 | versionCode 1 23 | versionName "1.0" 24 | 25 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 26 | 27 | Properties properties = new Properties() 28 | properties.load(project.rootProject.file("local.properties").newDataInputStream()) 29 | 30 | 31 | } 32 | 33 | buildTypes { 34 | release { 35 | minifyEnabled false 36 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 37 | } 38 | } 39 | compileOptions { 40 | sourceCompatibility JavaVersion.VERSION_1_8 41 | targetCompatibility JavaVersion.VERSION_1_8 42 | } 43 | kotlinOptions { 44 | jvmTarget = '1.8' 45 | } 46 | 47 | sourceSets { 48 | getByName("debug").res.srcDirs("$rootDir/navigation/src/main/sharedRes") 49 | } 50 | } 51 | 52 | dependencies { 53 | 54 | implementation project(path:':core:ui') 55 | implementation project(path:':core:domain') 56 | implementation project(path:':core:common') 57 | implementation project(path:':core:data') 58 | implementation project(path:':feature:home') 59 | implementation project(path:':feature:detail') 60 | 61 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 62 | implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' 63 | implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' 64 | def lifecycle_version = "2.5.1" 65 | def arch_version = "2.1.0" 66 | def retrofit_version = "2.9.0" 67 | def retrofit_interceptor_version = "4.9.3" 68 | def okhttp_version = "3.6.0" 69 | def fragment_version = "1.5.4" 70 | def nav_version = "2.5.3" 71 | 72 | implementation 'androidx.core:core-ktx:1.7.0' 73 | implementation 'androidx.appcompat:appcompat:1.5.1' 74 | implementation 'com.google.android.material:material:1.7.0' 75 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 76 | testImplementation 'junit:junit:4.13.2' 77 | androidTestImplementation 'androidx.test.ext:junit:1.1.4' 78 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' 79 | 80 | // ViewModel 81 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 82 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" 83 | implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" 84 | kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" 85 | 86 | //Navigation 87 | implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" 88 | implementation "androidx.navigation:navigation-ui-ktx:$nav_version" 89 | 90 | //Dagger-Hilt 91 | implementation "com.google.dagger:hilt-android:2.44" 92 | kapt "com.google.dagger:hilt-compiler:2.44" 93 | 94 | // Retrofit & Okhttp 95 | implementation "com.squareup.retrofit2:retrofit:$retrofit_version" 96 | implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" 97 | implementation "com.squareup.okhttp3:okhttp:$okhttp_version" 98 | implementation "com.squareup.okhttp3:logging-interceptor:$retrofit_interceptor_version" 99 | 100 | //Fragments 101 | implementation "androidx.fragment:fragment-ktx:$fragment_version" 102 | 103 | //Glide 104 | implementation 'com.github.bumptech.glide:glide:4.14.2' 105 | annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' 106 | 107 | 108 | } 109 | kapt { 110 | correctErrorTypes true 111 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/salihkinali/gameguide/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.gameguide 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.salihkinali.gameguide", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/salihkinali/GameGuideApp.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class GameGuideApp : Application() -------------------------------------------------------------------------------- /app/src/main/java/com/salihkinali/gameguide/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.gameguide 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import dagger.hilt.android.AndroidEntryPoint 6 | 7 | @AndroidEntryPoint 8 | class MainActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContentView(R.layout.activity_main) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /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/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | -------------------------------------------------------------------------------- /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.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/com/salihkinali/gameguide/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.gameguide 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '7.3.1' apply false 4 | id 'com.android.library' version '7.3.1' apply false 5 | id 'org.jetbrains.kotlin.android' version '1.7.20' apply false 6 | id 'com.google.dagger.hilt.android' version '2.44' apply false 7 | id 'androidx.navigation.safeargs.kotlin' version '2.5.3' apply false 8 | id 'org.jetbrains.kotlin.jvm' version '1.7.20' apply false 9 | 10 | 11 | } -------------------------------------------------------------------------------- /core/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/common/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'com.google.dagger.hilt.android' 6 | } 7 | 8 | android { 9 | namespace 'com.salihkinali.common' 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | minSdk 21 14 | targetSdk 33 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | } 34 | 35 | dependencies { 36 | 37 | implementation 'androidx.core:core-ktx:1.7.0' 38 | implementation 'androidx.appcompat:appcompat:1.6.0' 39 | implementation 'com.google.android.material:material:1.8.0' 40 | testImplementation 'junit:junit:4.13.2' 41 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 43 | 44 | // Hilt 45 | implementation "com.google.dagger:hilt-android:2.44" 46 | kapt "com.google.dagger:hilt-compiler:2.44" 47 | } -------------------------------------------------------------------------------- /core/common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/core/common/consumer-rules.pro -------------------------------------------------------------------------------- /core/common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/common/src/androidTest/java/com/salihkinali/common/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.common 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.salihkinali.common.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/common/src/main/java/com/salihkinali/ResponseHandle.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | 5 | fun NetworkResponse.mapResponse(mapper:I.()->O): NetworkResponse { 6 | return when(this){ 7 | is NetworkResponse.Error -> NetworkResponse.Error(this.exception) 8 | NetworkResponse.Loading -> NetworkResponse.Loading 9 | is NetworkResponse.Success -> NetworkResponse.Success(mapper.invoke(this.result)) 10 | } 11 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/salihkinali/common/NetworkResponse.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.common 2 | 3 | sealed class NetworkResponse { 4 | object Loading : NetworkResponse() 5 | data class Success(val result: T) : NetworkResponse() 6 | data class Error(val exception: Exception) : NetworkResponse() 7 | } 8 | -------------------------------------------------------------------------------- /core/common/src/main/java/com/salihkinali/common/coroutine/DispachersModule.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.common.coroutine 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.android.components.ViewModelComponent 7 | import dagger.hilt.android.scopes.ViewModelScoped 8 | import kotlinx.coroutines.CoroutineDispatcher 9 | import kotlinx.coroutines.Dispatchers 10 | 11 | @Module 12 | @InstallIn(ViewModelComponent::class) 13 | object DispachersModule { 14 | 15 | @IoDispatcher 16 | @Provides 17 | @ViewModelScoped 18 | fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO 19 | } -------------------------------------------------------------------------------- /core/common/src/main/java/com/salihkinali/common/coroutine/IoDispacher.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.common.coroutine 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Retention(AnnotationRetention.RUNTIME) 6 | @Qualifier 7 | annotation class IoDispatcher -------------------------------------------------------------------------------- /core/common/src/main/java/com/salihkinali/common/mapper/GameListMapper.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.common.mapper 2 | 3 | interface GameListMapper : GameMapper?, List> 4 | 5 | -------------------------------------------------------------------------------- /core/common/src/main/java/com/salihkinali/common/mapper/GameMapper.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.common.mapper 2 | 3 | interface GameMapper { 4 | fun map(input:INPUT):OUTPUT 5 | } 6 | 7 | -------------------------------------------------------------------------------- /core/common/src/test/java/com/salihkinali/common/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.common 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/data/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'com.google.dagger.hilt.android' 6 | } 7 | 8 | android { 9 | namespace 'com.salihkinali.data' 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | minSdk 21 14 | targetSdk 33 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | 19 | Properties properties = new Properties() 20 | properties.load(project.rootProject.file("local.properties").newDataInputStream()) 21 | 22 | buildConfigField "String", "API_KEY", "\"${properties.getProperty("API_KEY")}\"" 23 | buildConfigField "String", "BASE_URL", "\"${properties.getProperty("BASE_URL")}\"" 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | } 40 | 41 | dependencies { 42 | 43 | def retrofit_version = "2.9.0" 44 | def retrofit_interceptor_version = "4.9.3" 45 | def okhttp_version = "3.6.0" 46 | 47 | 48 | implementation project(path: ':core:common') 49 | implementation project(path: ':core:domain') 50 | 51 | implementation 'androidx.core:core-ktx:1.7.0' 52 | implementation 'androidx.appcompat:appcompat:1.6.0' 53 | implementation 'com.google.android.material:material:1.8.0' 54 | testImplementation 'junit:junit:4.13.2' 55 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 57 | 58 | // Retrofit & Okhttp 59 | implementation "com.squareup.retrofit2:retrofit:$retrofit_version" 60 | implementation "com.squareup.retrofit2:converter-gson:$retrofit_version" 61 | implementation "com.squareup.okhttp3:okhttp:$okhttp_version" 62 | implementation "com.squareup.okhttp3:logging-interceptor:$retrofit_interceptor_version" 63 | 64 | // Hilt 65 | implementation "com.google.dagger:hilt-android:2.44" 66 | kapt "com.google.dagger:hilt-compiler:2.44" 67 | 68 | // Test 69 | testImplementation 'junit:junit:4.13.2' 70 | testImplementation "com.squareup.okhttp3:mockwebserver:4.10.0" 71 | testImplementation "com.google.truth:truth:1.1.3" 72 | testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" 73 | testImplementation "org.mockito:mockito-inline:3.0.0" 74 | testImplementation 'app.cash.turbine:turbine:0.12.1' 75 | 76 | implementation "androidx.arch.core:core-testing:2.1.0" 77 | 78 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2" 79 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 80 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 81 | } -------------------------------------------------------------------------------- /core/data/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/core/data/consumer-rules.pro -------------------------------------------------------------------------------- /core/data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/data/src/androidTest/java/com/salihkinali/data/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data 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.salihkinali.data.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/api/GameApi.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.api 2 | 3 | import com.salihkinali.data.BuildConfig 4 | import com.salihkinali.data.dto.detail.SingleGameResponse 5 | import com.salihkinali.data.dto.detail.screenshots.SingleGameScreenShotResponse 6 | import com.salihkinali.data.dto.game.TotalGameResponse 7 | import retrofit2.http.GET 8 | import retrofit2.http.Path 9 | import retrofit2.http.Query 10 | 11 | interface GameApi { 12 | 13 | @GET("api/games") 14 | suspend fun getTotalGameInfo(@Query("key") key: String = BuildConfig.API_KEY): TotalGameResponse 15 | 16 | @GET("api/games/{id}") 17 | suspend fun getSingleGameInfo( 18 | @Path("id") id: Int, 19 | @Query("key") key: String = BuildConfig.API_KEY 20 | ): SingleGameResponse 21 | 22 | @GET("/api/games/{game_pk}/screenshots") 23 | suspend fun getGameScreenShots( 24 | @Path("game_pk") gamePk: Int, 25 | @Query("key") key: String = BuildConfig.API_KEY 26 | ): SingleGameScreenShotResponse 27 | 28 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/di/DataModule.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.di 2 | 3 | import com.salihkinali.common.mapper.GameListMapper 4 | import com.salihkinali.common.mapper.GameMapper 5 | import com.salihkinali.data.dto.detail.SingleGameResponse 6 | import com.salihkinali.data.dto.detail.screenshots.GameResult 7 | import com.salihkinali.data.mapper.GameScreenShotMapper 8 | import com.salihkinali.data.mapper.SingleGameMapper 9 | import com.salihkinali.data.mapper.TotalEntityMapper 10 | import com.salihkinali.domain.entity.GameScreenShotEntity 11 | import com.salihkinali.domain.entity.SingleGameEntity 12 | import com.salihkinali.domain.entity.TotalGameEntity 13 | import dagger.Binds 14 | import dagger.Module 15 | import dagger.hilt.InstallIn 16 | import dagger.hilt.android.components.ViewModelComponent 17 | import dagger.hilt.android.scopes.ViewModelScoped 18 | import com.salihkinali.data.dto.game.Result 19 | 20 | @Module 21 | @InstallIn(ViewModelComponent::class) 22 | abstract class GameMapperModule { 23 | 24 | 25 | 26 | @Binds 27 | @ViewModelScoped 28 | abstract fun bindSingleMapper(singleMapperImpl: SingleGameMapper): GameMapper 29 | 30 | 31 | @Binds 32 | @ViewModelScoped 33 | abstract fun bindGameMapper(totalEntityMapperImpl: TotalEntityMapper): GameListMapper 34 | 35 | 36 | 37 | @Binds 38 | @ViewModelScoped 39 | abstract fun bindGameScMapper(gameScMapperImpl: GameScreenShotMapper): GameListMapper 40 | 41 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/di/network/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.di.network 2 | 3 | import com.salihkinali.data.BuildConfig 4 | import com.salihkinali.data.api.GameApi 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.components.ViewModelComponent 9 | import dagger.hilt.android.scopes.ViewModelScoped 10 | import okhttp3.Interceptor 11 | import okhttp3.OkHttpClient 12 | import okhttp3.logging.HttpLoggingInterceptor 13 | import retrofit2.Retrofit 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | 16 | @Module 17 | @InstallIn(ViewModelComponent::class) 18 | object NetworkModule { 19 | 20 | @Provides 21 | @ViewModelScoped 22 | fun getInterceptor(): Interceptor { 23 | return HttpLoggingInterceptor().apply { 24 | setLevel(HttpLoggingInterceptor.Level.BODY) 25 | } 26 | } 27 | 28 | @Provides 29 | @ViewModelScoped 30 | fun getHttpClient(interceptor: Interceptor): OkHttpClient { 31 | return OkHttpClient.Builder() 32 | .addInterceptor(interceptor) 33 | .build() 34 | } 35 | 36 | @Provides 37 | @ViewModelScoped 38 | fun provideGameApi(client: OkHttpClient): GameApi { 39 | return Retrofit.Builder() 40 | .baseUrl(BuildConfig.BASE_URL) 41 | .addConverterFactory(GsonConverterFactory.create()) 42 | .client(client) 43 | .build() 44 | .create(GameApi::class.java) 45 | } 46 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/di/repository/GameRepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.di.repository 2 | 3 | import com.salihkinali.data.repository.GameRepositoryImpl 4 | import com.salihkinali.data.source.RemoteDataSource 5 | import com.salihkinali.data.source.RemoteDataSourceImpl 6 | import com.salihkinali.domain.repository.GameRepository 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.components.ViewModelComponent 11 | import dagger.hilt.android.scopes.ViewModelScoped 12 | 13 | @Module 14 | @InstallIn(ViewModelComponent::class) 15 | abstract class GameRepositoryModule { 16 | 17 | @Binds 18 | @ViewModelScoped 19 | abstract fun bindRemoteDataSource(remoteDataSourceImpl: RemoteDataSourceImpl): RemoteDataSource 20 | 21 | @Binds 22 | @ViewModelScoped 23 | abstract fun bindGameRepository(gameRepositoryImpl: GameRepositoryImpl): GameRepository 24 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/AddedByStatus.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class AddedByStatus( 7 | @SerializedName("beaten") 8 | val beaten: Int?, 9 | @SerializedName("dropped") 10 | val dropped: Int?, 11 | @SerializedName("owned") 12 | val owned: Int?, 13 | @SerializedName("playing") 14 | val playing: Int?, 15 | @SerializedName("toplay") 16 | val toplay: Int?, 17 | @SerializedName("yet") 18 | val yet: Int? 19 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Developer.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Developer( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image_background") 12 | val imageBackground: String?, 13 | @SerializedName("name") 14 | val name: String?, 15 | @SerializedName("slug") 16 | val slug: String? 17 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/EsrbRating.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class EsrbRating( 7 | @SerializedName("id") 8 | val id: Int?, 9 | @SerializedName("name") 10 | val name: String?, 11 | @SerializedName("slug") 12 | val slug: String? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Genre.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Genre( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image_background") 12 | val imageBackground: String?, 13 | @SerializedName("name") 14 | val name: String?, 15 | @SerializedName("slug") 16 | val slug: String? 17 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/MetacriticPlatform.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class MetacriticPlatform( 7 | @SerializedName("metascore") 8 | val metascore: Int?, 9 | @SerializedName("platform") 10 | val platform: Platform?, 11 | @SerializedName("url") 12 | val url: String? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/ParentPlatform.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class ParentPlatform( 7 | @SerializedName("platform") 8 | val platform: PlatformX? 9 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Platform.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Platform( 7 | @SerializedName("name") 8 | val name: String?, 9 | @SerializedName("platform") 10 | val platform: Int?, 11 | @SerializedName("slug") 12 | val slug: String? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/PlatformX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class PlatformX( 7 | @SerializedName("id") 8 | val id: Int?, 9 | @SerializedName("name") 10 | val name: String?, 11 | @SerializedName("slug") 12 | val slug: String? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/PlatformXX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class PlatformXX( 7 | @SerializedName("platform") 8 | val platform: PlatformXXX?, 9 | @SerializedName("released_at") 10 | val releasedAt: String?, 11 | @SerializedName("requirements") 12 | val requirements: Requirements? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/PlatformXXX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class PlatformXXX( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image") 12 | val image: Any?, 13 | @SerializedName("image_background") 14 | val imageBackground: String?, 15 | @SerializedName("name") 16 | val name: String?, 17 | @SerializedName("slug") 18 | val slug: String?, 19 | @SerializedName("year_end") 20 | val yearEnd: Any?, 21 | @SerializedName("year_start") 22 | val yearStart: Int? 23 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Publisher.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Publisher( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image_background") 12 | val imageBackground: String?, 13 | @SerializedName("name") 14 | val name: String?, 15 | @SerializedName("slug") 16 | val slug: String? 17 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Rating.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Rating( 7 | @SerializedName("count") 8 | val count: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("percent") 12 | val percent: Double?, 13 | @SerializedName("title") 14 | val title: String? 15 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Reactions.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Reactions( 7 | @SerializedName("1") 8 | val x1: Int?, 9 | @SerializedName("10") 10 | val x10: Int?, 11 | @SerializedName("11") 12 | val x11: Int?, 13 | @SerializedName("12") 14 | val x12: Int?, 15 | @SerializedName("14") 16 | val x14: Int?, 17 | @SerializedName("15") 18 | val x15: Int?, 19 | @SerializedName("16") 20 | val x16: Int?, 21 | @SerializedName("18") 22 | val x18: Int?, 23 | @SerializedName("2") 24 | val x2: Int?, 25 | @SerializedName("20") 26 | val x20: Int?, 27 | @SerializedName("21") 28 | val x21: Int?, 29 | @SerializedName("3") 30 | val x3: Int?, 31 | @SerializedName("4") 32 | val x4: Int?, 33 | @SerializedName("5") 34 | val x5: Int?, 35 | @SerializedName("6") 36 | val x6: Int?, 37 | @SerializedName("7") 38 | val x7: Int?, 39 | @SerializedName("8") 40 | val x8: Int?, 41 | @SerializedName("9") 42 | val x9: Int? 43 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Requirements.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Requirements( 7 | @SerializedName("minimum") 8 | val minimum: String?, 9 | @SerializedName("recommended") 10 | val recommended: String? 11 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/SingleGameResponse.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class SingleGameResponse( 7 | @SerializedName("achievements_count") 8 | val achievementsCount: Int?, 9 | @SerializedName("added") 10 | val added: Int?, 11 | @SerializedName("added_by_status") 12 | val addedByStatus: AddedByStatus?, 13 | @SerializedName("additions_count") 14 | val additionsCount: Int?, 15 | @SerializedName("alternative_names") 16 | val alternativeNames: List?, 17 | @SerializedName("background_image") 18 | val backgroundImage: String?, 19 | @SerializedName("background_image_additional") 20 | val backgroundImageAdditional: String?, 21 | @SerializedName("clip") 22 | val clip: Any?, 23 | @SerializedName("creators_count") 24 | val creatorsCount: Int?, 25 | @SerializedName("description") 26 | val description: String?, 27 | @SerializedName("description_raw") 28 | val descriptionRaw: String?, 29 | @SerializedName("developers") 30 | val developers: List?, 31 | @SerializedName("dominant_color") 32 | val dominantColor: String?, 33 | @SerializedName("esrb_rating") 34 | val esrbRating: EsrbRating?, 35 | @SerializedName("game_series_count") 36 | val gameSeriesCount: Int?, 37 | @SerializedName("genres") 38 | val genres: List?, 39 | @SerializedName("id") 40 | val id: Int?, 41 | @SerializedName("metacritic") 42 | val metacritic: Int?, 43 | @SerializedName("metacritic_platforms") 44 | val metacriticPlatforms: List?, 45 | @SerializedName("metacritic_url") 46 | val metacriticUrl: String?, 47 | @SerializedName("movies_count") 48 | val moviesCount: Int?, 49 | @SerializedName("name") 50 | val name: String?, 51 | @SerializedName("name_original") 52 | val nameOriginal: String?, 53 | @SerializedName("parent_achievements_count") 54 | val parentAchievementsCount: Int?, 55 | @SerializedName("parent_platforms") 56 | val parentPlatforms: List?, 57 | @SerializedName("parents_count") 58 | val parentsCount: Int?, 59 | @SerializedName("platforms") 60 | val platforms: List?, 61 | @SerializedName("playtime") 62 | val playtime: Int?, 63 | @SerializedName("publishers") 64 | val publishers: List?, 65 | @SerializedName("rating") 66 | val rating: Double?, 67 | @SerializedName("rating_top") 68 | val ratingTop: Int?, 69 | @SerializedName("ratings") 70 | val ratings: List?, 71 | @SerializedName("ratings_count") 72 | val ratingsCount: Int?, 73 | @SerializedName("reactions") 74 | val reactions: Reactions?, 75 | @SerializedName("reddit_count") 76 | val redditCount: Int?, 77 | @SerializedName("reddit_description") 78 | val redditDescription: String?, 79 | @SerializedName("reddit_logo") 80 | val redditLogo: String?, 81 | @SerializedName("reddit_name") 82 | val redditName: String?, 83 | @SerializedName("reddit_url") 84 | val redditUrl: String?, 85 | @SerializedName("released") 86 | val released: String?, 87 | @SerializedName("reviews_count") 88 | val reviewsCount: Int?, 89 | @SerializedName("reviews_text_count") 90 | val reviewsTextCount: Int?, 91 | @SerializedName("saturated_color") 92 | val saturatedColor: String?, 93 | @SerializedName("screenshots_count") 94 | val screenshotsCount: Int?, 95 | @SerializedName("slug") 96 | val slug: String?, 97 | @SerializedName("stores") 98 | val stores: List?, 99 | @SerializedName("suggestions_count") 100 | val suggestionsCount: Int?, 101 | @SerializedName("tags") 102 | val tags: List?, 103 | @SerializedName("tba") 104 | val tba: Boolean?, 105 | @SerializedName("twitch_count") 106 | val twitchCount: Int?, 107 | @SerializedName("updated") 108 | val updated: String?, 109 | @SerializedName("user_game") 110 | val userGame: Any?, 111 | @SerializedName("website") 112 | val website: String?, 113 | @SerializedName("youtube_count") 114 | val youtubeCount: Int? 115 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Store.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Store( 7 | @SerializedName("id") 8 | val id: Int?, 9 | @SerializedName("store") 10 | val store: StoreX?, 11 | @SerializedName("url") 12 | val url: String? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/StoreX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class StoreX( 7 | @SerializedName("domain") 8 | val domain: String?, 9 | @SerializedName("games_count") 10 | val gamesCount: Int?, 11 | @SerializedName("id") 12 | val id: Int?, 13 | @SerializedName("image_background") 14 | val imageBackground: String?, 15 | @SerializedName("name") 16 | val name: String?, 17 | @SerializedName("slug") 18 | val slug: String? 19 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/Tag.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Tag( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image_background") 12 | val imageBackground: String?, 13 | @SerializedName("language") 14 | val language: String?, 15 | @SerializedName("name") 16 | val name: String?, 17 | @SerializedName("slug") 18 | val slug: String? 19 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/screenshots/GameResult.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail.screenshots 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class GameResult( 7 | @SerializedName("height") 8 | val height: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image") 12 | val image: String?, 13 | @SerializedName("is_deleted") 14 | val isDeleted: Boolean?, 15 | @SerializedName("width") 16 | val width: Int? 17 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/detail/screenshots/SingleGameScreenShotResponse.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.detail.screenshots 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class SingleGameScreenShotResponse( 7 | @SerializedName("count") 8 | val count: Int?, 9 | @SerializedName("next") 10 | val next: Any?, 11 | @SerializedName("previous") 12 | val previous: Any?, 13 | @SerializedName("results") 14 | val results: List? 15 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/AddedByStatus.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class AddedByStatus( 7 | @SerializedName("beaten") 8 | val beaten: Int?, 9 | @SerializedName("dropped") 10 | val dropped: Int?, 11 | @SerializedName("owned") 12 | val owned: Int?, 13 | @SerializedName("playing") 14 | val playing: Int?, 15 | @SerializedName("toplay") 16 | val toplay: Int?, 17 | @SerializedName("yet") 18 | val yet: Int? 19 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/EsrbRating.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class EsrbRating( 7 | @SerializedName("id") 8 | val id: Int?, 9 | @SerializedName("name") 10 | val name: String?, 11 | @SerializedName("slug") 12 | val slug: String? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Filters.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Filters( 7 | @SerializedName("years") 8 | val years: List? 9 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Genre.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Genre( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image_background") 12 | val imageBackground: String?, 13 | @SerializedName("name") 14 | val name: String?, 15 | @SerializedName("slug") 16 | val slug: String? 17 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/ParentPlatform.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class ParentPlatform( 7 | @SerializedName("platform") 8 | val platform: Platform? 9 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Platform.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Platform( 7 | @SerializedName("id") 8 | val id: Int?, 9 | @SerializedName("name") 10 | val name: String?, 11 | @SerializedName("slug") 12 | val slug: String? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/PlatformX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class PlatformX( 7 | @SerializedName("platform") 8 | val platform: PlatformXX?, 9 | @SerializedName("released_at") 10 | val releasedAt: String?, 11 | @SerializedName("requirements_en") 12 | val requirementsEn: RequirementsEn?, 13 | @SerializedName("requirements_ru") 14 | val requirementsRu: RequirementsRu? 15 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/PlatformXX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class PlatformXX( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image") 12 | val image: Any?, 13 | @SerializedName("image_background") 14 | val imageBackground: String?, 15 | @SerializedName("name") 16 | val name: String?, 17 | @SerializedName("slug") 18 | val slug: String?, 19 | @SerializedName("year_end") 20 | val yearEnd: Any?, 21 | @SerializedName("year_start") 22 | val yearStart: Int? 23 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Rating.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Rating( 7 | @SerializedName("count") 8 | val count: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("percent") 12 | val percent: Double?, 13 | @SerializedName("title") 14 | val title: String? 15 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/RequirementsEn.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class RequirementsEn( 7 | @SerializedName("minimum") 8 | val minimum: String?, 9 | @SerializedName("recommended") 10 | val recommended: String? 11 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/RequirementsRu.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class RequirementsRu( 7 | @SerializedName("minimum") 8 | val minimum: String?, 9 | @SerializedName("recommended") 10 | val recommended: String? 11 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Result.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Result( 7 | @SerializedName("added") 8 | val added: Int?, 9 | @SerializedName("added_by_status") 10 | val addedByStatus: AddedByStatus?, 11 | @SerializedName("background_image") 12 | val backgroundImage: String?, 13 | @SerializedName("clip") 14 | val clip: Any?, 15 | @SerializedName("dominant_color") 16 | val dominantColor: String?, 17 | @SerializedName("esrb_rating") 18 | val esrbRating: EsrbRating?, 19 | @SerializedName("genres") 20 | val genres: List?, 21 | @SerializedName("id") 22 | val id: Int?, 23 | @SerializedName("metacritic") 24 | val metacritic: Int?, 25 | @SerializedName("name") 26 | val name: String?, 27 | @SerializedName("parent_platforms") 28 | val parentPlatforms: List?, 29 | @SerializedName("platforms") 30 | val platforms: List?, 31 | @SerializedName("playtime") 32 | val playtime: Int?, 33 | @SerializedName("rating") 34 | val rating: Double?, 35 | @SerializedName("rating_top") 36 | val ratingTop: Int?, 37 | @SerializedName("ratings") 38 | val ratings: List?, 39 | @SerializedName("ratings_count") 40 | val ratingsCount: Int?, 41 | @SerializedName("released") 42 | val released: String?, 43 | @SerializedName("reviews_count") 44 | val reviewsCount: Int?, 45 | @SerializedName("reviews_text_count") 46 | val reviewsTextCount: Int?, 47 | @SerializedName("saturated_color") 48 | val saturatedColor: String?, 49 | @SerializedName("short_screenshots") 50 | val shortScreenshots: List?, 51 | @SerializedName("slug") 52 | val slug: String?, 53 | @SerializedName("stores") 54 | val stores: List?, 55 | @SerializedName("suggestions_count") 56 | val suggestionsCount: Int?, 57 | @SerializedName("tags") 58 | val tags: List?, 59 | @SerializedName("tba") 60 | val tba: Boolean?, 61 | @SerializedName("updated") 62 | val updated: String?, 63 | @SerializedName("user_game") 64 | val userGame: Any? 65 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/ShortScreenshot.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class ShortScreenshot( 7 | @SerializedName("id") 8 | val id: Int?, 9 | @SerializedName("image") 10 | val image: String? 11 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Store.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Store( 7 | @SerializedName("id") 8 | val id: Int?, 9 | @SerializedName("store") 10 | val store: StoreX? 11 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/StoreX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class StoreX( 7 | @SerializedName("domain") 8 | val domain: String?, 9 | @SerializedName("games_count") 10 | val gamesCount: Int?, 11 | @SerializedName("id") 12 | val id: Int?, 13 | @SerializedName("image_background") 14 | val imageBackground: String?, 15 | @SerializedName("name") 16 | val name: String?, 17 | @SerializedName("slug") 18 | val slug: String? 19 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Tag.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Tag( 7 | @SerializedName("games_count") 8 | val gamesCount: Int?, 9 | @SerializedName("id") 10 | val id: Int?, 11 | @SerializedName("image_background") 12 | val imageBackground: String?, 13 | @SerializedName("language") 14 | val language: String?, 15 | @SerializedName("name") 16 | val name: String?, 17 | @SerializedName("slug") 18 | val slug: String? 19 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/TotalGameResponse.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class TotalGameResponse( 7 | @SerializedName("count") 8 | val count: Int?, 9 | @SerializedName("description") 10 | val description: String?, 11 | @SerializedName("filters") 12 | val filters: Filters?, 13 | @SerializedName("next") 14 | val next: String?, 15 | @SerializedName("nofollow") 16 | val nofollow: Boolean?, 17 | @SerializedName("nofollow_collections") 18 | val nofollowCollections: List?, 19 | @SerializedName("noindex") 20 | val noindex: Boolean?, 21 | @SerializedName("previous") 22 | val previous: Any?, 23 | @SerializedName("results") 24 | val results: List?, 25 | @SerializedName("seo_description") 26 | val seoDescription: String?, 27 | @SerializedName("seo_h1") 28 | val seoH1: String?, 29 | @SerializedName("seo_keywords") 30 | val seoKeywords: String?, 31 | @SerializedName("seo_title") 32 | val seoTitle: String? 33 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/Year.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class Year( 7 | @SerializedName("count") 8 | val count: Int?, 9 | @SerializedName("decade") 10 | val decade: Int?, 11 | @SerializedName("filter") 12 | val filter: String?, 13 | @SerializedName("from") 14 | val from: Int?, 15 | @SerializedName("nofollow") 16 | val nofollow: Boolean?, 17 | @SerializedName("to") 18 | val to: Int?, 19 | @SerializedName("years") 20 | val years: List? 21 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/dto/game/YearX.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.dto.game 2 | 3 | 4 | import com.google.gson.annotations.SerializedName 5 | 6 | data class YearX( 7 | @SerializedName("count") 8 | val count: Int?, 9 | @SerializedName("nofollow") 10 | val nofollow: Boolean?, 11 | @SerializedName("year") 12 | val year: Int? 13 | ) -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/mapper/GameScreenShotMapper.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.mapper 2 | 3 | import com.salihkinali.common.mapper.GameListMapper 4 | import com.salihkinali.data.dto.detail.screenshots.GameResult 5 | import com.salihkinali.domain.entity.GameScreenShotEntity 6 | import javax.inject.Inject 7 | 8 | class GameScreenShotMapper @Inject constructor() : GameListMapper { 9 | override fun map(input: List?): List { 10 | return input?.map { 11 | GameScreenShotEntity( 12 | gameScreenShot = it.image.orEmpty() 13 | ) 14 | } ?: emptyList() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/mapper/SingleGameMapper.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.mapper 2 | 3 | import com.salihkinali.common.mapper.GameMapper 4 | import com.salihkinali.data.dto.detail.SingleGameResponse 5 | import com.salihkinali.domain.entity.SingleGameEntity 6 | import javax.inject.Inject 7 | 8 | class SingleGameMapper @Inject constructor() : 9 | GameMapper { 10 | override fun map(input: SingleGameResponse): SingleGameEntity { 11 | return SingleGameEntity( 12 | id = input.id ?: 0, 13 | name = input.name.orEmpty(), 14 | released = input.released.orEmpty(), 15 | backgroundImage = input.backgroundImage.orEmpty(), 16 | description = input.descriptionRaw.orEmpty(), 17 | website = input.website.orEmpty(), 18 | ratingTop = input.ratingTop ?: 0 19 | ) 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/mapper/TotalEntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.mapper 2 | 3 | import com.salihkinali.common.mapper.GameListMapper 4 | import com.salihkinali.domain.entity.TotalGameEntity 5 | import javax.inject.Inject 6 | import com.salihkinali.data.dto.game.Result 7 | 8 | class TotalEntityMapper @Inject constructor() : GameListMapper { 9 | override fun map(input: List?): List { 10 | return input?.map { 11 | TotalGameEntity( 12 | id = it.id ?: 0, 13 | name = it.name.orEmpty(), 14 | rating = it.rating ?: 0.00, 15 | released = it.released.orEmpty(), 16 | backgroundImage = it.backgroundImage.orEmpty() 17 | ) 18 | } ?: emptyList() 19 | } 20 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/repository/GameRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.repository 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.common.coroutine.IoDispatcher 5 | import com.salihkinali.common.mapper.GameListMapper 6 | import com.salihkinali.common.mapper.GameMapper 7 | import com.salihkinali.data.dto.detail.SingleGameResponse 8 | import com.salihkinali.data.dto.detail.screenshots.GameResult 9 | import com.salihkinali.data.dto.game.Result 10 | import com.salihkinali.data.source.RemoteDataSource 11 | import com.salihkinali.domain.entity.GameScreenShotEntity 12 | import com.salihkinali.domain.entity.SingleGameEntity 13 | import com.salihkinali.domain.entity.TotalGameEntity 14 | import com.salihkinali.domain.repository.GameRepository 15 | import com.salihkinali.mapResponse 16 | import kotlinx.coroutines.CoroutineDispatcher 17 | import kotlinx.coroutines.Dispatchers 18 | import kotlinx.coroutines.flow.Flow 19 | import kotlinx.coroutines.flow.map 20 | import javax.inject.Inject 21 | 22 | class GameRepositoryImpl @Inject constructor( 23 | private val remoteDataSource: RemoteDataSource, 24 | private val gameListMapper: GameListMapper, 25 | private val gameScMapper: GameListMapper, 26 | private val gameMapper: GameMapper, 27 | @IoDispatcher private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO 28 | ) : GameRepository { 29 | override fun getTotalGameInfo(): Flow>> = 30 | remoteDataSource.getTotalGameInfo().map {data-> 31 | data.mapResponse { gameListMapper.map(this) } 32 | } 33 | 34 | override fun getSingleGameInfo(id: Int): Flow> = 35 | remoteDataSource.getSingleCharacter(id).map {data-> 36 | data.mapResponse { gameMapper.map(this) } 37 | } 38 | override fun getGameScreenShots(id: Int): Flow>> = 39 | remoteDataSource.getGameScreenShots(id).map {data-> 40 | data.mapResponse { gameScMapper.map(this) } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/source/RemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.source 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.data.dto.detail.SingleGameResponse 5 | import com.salihkinali.data.dto.detail.screenshots.GameResult 6 | import com.salihkinali.data.dto.game.Result 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | interface RemoteDataSource { 10 | fun getTotalGameInfo(): Flow>> 11 | fun getSingleCharacter(id: Int): Flow> 12 | fun getGameScreenShots(id: Int): Flow>> 13 | } -------------------------------------------------------------------------------- /core/data/src/main/java/com/salihkinali/data/source/RemoteDataSourceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.source 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.data.api.GameApi 5 | import com.salihkinali.data.dto.detail.SingleGameResponse 6 | import com.salihkinali.data.dto.detail.screenshots.GameResult 7 | import com.salihkinali.data.dto.game.Result 8 | import kotlinx.coroutines.flow.Flow 9 | import kotlinx.coroutines.flow.flow 10 | import javax.inject.Inject 11 | 12 | class RemoteDataSourceImpl @Inject constructor(private val api: GameApi) : RemoteDataSource { 13 | override fun getTotalGameInfo(): Flow>> = flow { 14 | emit(NetworkResponse.Loading) 15 | try { 16 | val response = api.getTotalGameInfo() 17 | emit(NetworkResponse.Success(response.results!!)) 18 | } catch (e: java.lang.Exception) { 19 | emit(NetworkResponse.Error(e)) 20 | } 21 | } 22 | 23 | 24 | override fun getSingleCharacter(id: Int): Flow> = flow { 25 | emit(NetworkResponse.Loading) 26 | try { 27 | val response = api.getSingleGameInfo(id) 28 | emit(NetworkResponse.Success(response)) 29 | } catch (e: Exception) { 30 | emit(NetworkResponse.Error(e)) 31 | } 32 | } 33 | 34 | override fun getGameScreenShots(id: Int): Flow>> = flow { 35 | emit(NetworkResponse.Loading) 36 | try { 37 | val response = api.getGameScreenShots(id).results 38 | emit(NetworkResponse.Success(response!!)) 39 | } catch (e: Exception) { 40 | emit(NetworkResponse.Error(e)) 41 | } 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/TestConstants.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.salihkinali.data.dto.detail.SingleGameResponse 5 | import com.salihkinali.data.dto.detail.screenshots.GameResult 6 | import com.salihkinali.data.dto.detail.screenshots.SingleGameScreenShotResponse 7 | import com.salihkinali.data.dto.game.Result 8 | import com.salihkinali.data.dto.game.TotalGameResponse 9 | import com.salihkinali.domain.entity.GameScreenShotEntity 10 | import com.salihkinali.domain.entity.SingleGameEntity 11 | import com.salihkinali.domain.entity.TotalGameEntity 12 | 13 | const val GAME_API_FILE_NAME = "TotalGameInfoResponse.json" 14 | const val SINGLE_GAME_API_FILE_NAME = "SingleGameInfoResponse.json" 15 | const val GAME_ID = 3498 16 | const val GAME_NAME = "Grand Theft Auto V" 17 | 18 | @VisibleForTesting 19 | val totalGameEntity = TotalGameEntity( 20 | id = 3498, 21 | name = "Grand Theft Auto V", 22 | rating = 4.47, 23 | released = "2013-09-17", 24 | backgroundImage = "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg" 25 | ) 26 | 27 | @VisibleForTesting 28 | val totalGameEntities = listOf(totalGameEntity) 29 | 30 | 31 | @VisibleForTesting 32 | val singleGameResponse = SingleGameResponse( 33 | achievementsCount = null, 34 | added = null, 35 | addedByStatus = null, 36 | additionsCount = null, 37 | alternativeNames = null, 38 | backgroundImage = "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg", 39 | backgroundImageAdditional = null, 40 | clip = null, 41 | creatorsCount = null, 42 | description = "Rockstar Games went bigger, since their previous installment of the series. You get the complicated and realistic world-building from Liberty City of GTA4 in the setting of lively and diverse Los Santos", 43 | descriptionRaw = null, 44 | developers = null, 45 | dominantColor = null, 46 | esrbRating = null, 47 | gameSeriesCount = null, 48 | genres = null, 49 | id = 3498, 50 | metacritic = null, 51 | metacriticPlatforms = null, 52 | metacriticUrl = null, 53 | moviesCount = null, 54 | name = "Grand Theft Auto V", 55 | nameOriginal = null, 56 | parentAchievementsCount = null, 57 | parentPlatforms = null, 58 | parentsCount = null, 59 | platforms = null, 60 | playtime = null, 61 | publishers = null, 62 | rating = null, 63 | ratingTop = 5, 64 | ratings = null, 65 | ratingsCount = null, 66 | reactions = null, 67 | redditCount = null, 68 | redditDescription = null, 69 | redditLogo = null, 70 | redditName = null, 71 | redditUrl = "https://www.reddit.com/r/GrandTheftAutoV/", 72 | released = "2013-09-17", 73 | reviewsCount = null, 74 | reviewsTextCount = null, 75 | saturatedColor = null, 76 | screenshotsCount = 57, 77 | slug = "grand-theft-auto-v", 78 | stores = null, 79 | suggestionsCount = null, 80 | tags = null, 81 | tba = null, 82 | twitchCount = null, 83 | updated = null, 84 | userGame = null, 85 | website = "http://www.rockstargames.com/V/", 86 | youtubeCount = null 87 | ) 88 | 89 | @VisibleForTesting 90 | val singleGameEntity = SingleGameEntity( 91 | id = 3498, 92 | name = "Grand Theft Auto V", 93 | released = "2013-09-17", 94 | backgroundImage = "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg", 95 | description = "Rockstar Games went bigger, since their previous installment of the series. You get the complicated and realistic world-building from Liberty City of GTA4 in the setting of lively and diverse Los Santos", 96 | website = "http://www.rockstargames.com/V/", 97 | ratingTop = 5 98 | ) 99 | 100 | 101 | @VisibleForTesting 102 | val gameScreenShotEntity = GameScreenShotEntity( 103 | gameScreenShot = "https://media.rawg.io/media/screenshots/a7c/a7c43871a54bed6573a6a429451564ef.jpg" 104 | ) 105 | 106 | @VisibleForTesting 107 | val gameScreenShotEntities = listOf(gameScreenShotEntity) 108 | 109 | @VisibleForTesting 110 | val result = Result( 111 | added = null, 112 | addedByStatus = null, 113 | backgroundImage = "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg", 114 | clip = null, 115 | dominantColor = null, 116 | esrbRating = null, 117 | genres = null, 118 | id = 3498, 119 | metacritic = null, 120 | name = "Grand Theft Auto V", 121 | parentPlatforms = null, 122 | platforms = null, 123 | playtime = null, 124 | rating = 4.47, 125 | ratingTop = 5, 126 | ratings = null, 127 | ratingsCount = null, 128 | released = "2013-09-17", 129 | reviewsCount = null, 130 | reviewsTextCount = null, 131 | saturatedColor = "0f0f0f", 132 | shortScreenshots = null, 133 | slug = null, 134 | stores = null, 135 | suggestionsCount = null, 136 | tags = null, 137 | tba = false, 138 | updated = "2023-02-02T07:58:58", 139 | userGame = null 140 | ) 141 | 142 | @VisibleForTesting 143 | val gameResults = listOf(result) 144 | 145 | @VisibleForTesting 146 | val gameScreenShot = GameResult( 147 | height = null, 148 | id = null, 149 | image = "https://media.rawg.io/media/screenshots/a7c/a7c43871a54bed6573a6a429451564ef.jpg", 150 | isDeleted = false, 151 | width = null 152 | ) 153 | 154 | @VisibleForTesting 155 | val gameScreenShots = listOf(gameScreenShot) 156 | 157 | @VisibleForTesting 158 | val totalGameResponse = TotalGameResponse( 159 | count = null, 160 | description = null, 161 | filters = null, 162 | next = null, 163 | nofollow = false, 164 | nofollowCollections = null, 165 | noindex = false, 166 | previous = null, 167 | results = gameResults, 168 | seoDescription = null, 169 | seoH1 = null, 170 | seoKeywords = null, 171 | seoTitle = null 172 | ) 173 | 174 | @VisibleForTesting 175 | val gameScResponse = SingleGameScreenShotResponse( 176 | count = null, 177 | next = null, 178 | previous = null, 179 | results = gameScreenShots 180 | ) 181 | 182 | -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/api/GameApiTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.api 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import com.salihkinali.data.* 5 | import kotlinx.coroutines.runBlocking 6 | import okhttp3.mockwebserver.MockResponse 7 | import okhttp3.mockwebserver.MockWebServer 8 | import okio.buffer 9 | import okio.source 10 | import org.junit.After 11 | import org.junit.Before 12 | import org.junit.Test 13 | import retrofit2.Retrofit 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | 16 | class GameApiTest { 17 | 18 | 19 | private lateinit var gameApi: GameApi 20 | private val mockWebServer = MockWebServer() 21 | 22 | @Before 23 | fun setup() { 24 | mockWebServer.start(8080) 25 | gameApi = Retrofit.Builder().baseUrl(mockWebServer.url("")) 26 | .addConverterFactory(GsonConverterFactory.create()).build().create(GameApi::class.java) 27 | } 28 | 29 | 30 | /* when conditions is here */ 31 | 32 | @Test 33 | fun when_response_is_not_null() { 34 | runBlocking { 35 | readFileResponse(GAME_API_FILE_NAME) 36 | val response = gameApi.getTotalGameInfo() 37 | val request = mockWebServer.takeRequest() 38 | assertThat(response).isNotNull() 39 | } 40 | } 41 | 42 | @Test 43 | fun when_game_id_is_requestPath_same() { 44 | runBlocking { 45 | readFileResponse(GAME_API_FILE_NAME) 46 | val response = gameApi.getSingleGameInfo(GAME_ID) 47 | val request = mockWebServer.takeRequest() 48 | assertThat(request.path).isEqualTo("/api/games/$GAME_ID?key=${BuildConfig.API_KEY}") 49 | } 50 | } 51 | 52 | @Test 53 | fun when_game_response_is_requestPath_same() { 54 | runBlocking { 55 | readFileResponse(GAME_API_FILE_NAME) 56 | val response = gameApi.getTotalGameInfo() 57 | val request = mockWebServer.takeRequest() 58 | assertThat(request.path).isEqualTo("/api/games?key=${BuildConfig.API_KEY}") 59 | } 60 | } 61 | 62 | 63 | @Test 64 | fun when_response_is_firstGameId_expected() { 65 | runBlocking { 66 | readFileResponse(GAME_API_FILE_NAME) 67 | val response = gameApi.getTotalGameInfo() 68 | val firstGame = response.results!!.first() 69 | assertThat(firstGame.id).isEqualTo(GAME_ID) 70 | } 71 | } 72 | 73 | @Test 74 | fun when_game_name_has_same_response_expected() { 75 | runBlocking { 76 | readFileResponse(SINGLE_GAME_API_FILE_NAME) 77 | val response = gameApi.getSingleGameInfo(GAME_ID) 78 | val gameName = response.name 79 | assertThat(gameName).isEqualTo(GAME_NAME) 80 | } 81 | } 82 | 83 | 84 | // This is Read File 85 | private fun readFileResponse(fileName: String) { 86 | javaClass.classLoader?.let { 87 | 88 | val inputStream = it.getResourceAsStream(fileName) 89 | val source = inputStream.source().buffer() 90 | val mockResponse = MockResponse() 91 | mockResponse.setBody(source.readString(Charsets.UTF_8)) 92 | mockWebServer.enqueue(mockResponse) 93 | } 94 | } 95 | 96 | @After 97 | fun shotDown() { 98 | mockWebServer.shutdown() 99 | } 100 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/mapper/GameScreenShotMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.mapper 2 | 3 | import com.salihkinali.data.gameScreenShots 4 | import com.salihkinali.domain.entity.GameScreenShotEntity 5 | import org.junit.Assert 6 | import org.junit.Before 7 | import org.junit.Test 8 | 9 | class GameScreenShotMapperTest { 10 | 11 | private val gameScreenShotMapper = GameScreenShotMapper() 12 | private lateinit var gameScreenShotList: List 13 | 14 | @Before 15 | fun setup(){ 16 | gameScreenShotList = gameScreenShotMapper.map(gameScreenShots) 17 | } 18 | 19 | @Test 20 | fun totalGameScreenShotListSize_whenGameScreenShotMapped_isSameSize(){ 21 | Assert.assertEquals(gameScreenShotList.size, gameScreenShots.size) 22 | } 23 | 24 | @Test 25 | fun firstGameScreenShots_backGroundImage_whenGameScreenShotMapped_isSameSize(){ 26 | Assert.assertEquals(gameScreenShotList.first().gameScreenShot, gameScreenShots.first().image) 27 | } 28 | 29 | @Test 30 | fun lastGameScreenShots_backGroundImage_whenGameScreenShotMapped_isSameSize(){ 31 | Assert.assertEquals(gameScreenShotList.last().gameScreenShot, gameScreenShots.last().image) 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/mapper/SingleGameMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.mapper 2 | 3 | import com.salihkinali.data.singleGameResponse 4 | import com.salihkinali.domain.entity.SingleGameEntity 5 | import org.junit.Assert 6 | import org.junit.Before 7 | import org.junit.Test 8 | 9 | class SingleGameMapperTest { 10 | private val singleGameMapper = SingleGameMapper() 11 | private lateinit var singleGameEntity: SingleGameEntity 12 | 13 | @Before 14 | fun setup(){ 15 | singleGameEntity = singleGameMapper.map(singleGameResponse) 16 | } 17 | 18 | @Test 19 | fun name_whenGameMapped_isSameSize() { 20 | Assert.assertEquals(singleGameEntity.name, singleGameResponse.name) 21 | } 22 | 23 | @Test 24 | fun image_whenSingleGameMapped_isSameSize() { 25 | Assert.assertEquals(singleGameEntity.backgroundImage, singleGameResponse.backgroundImage) 26 | } 27 | 28 | @Test 29 | fun id_whenSingleGameMapped_isSameSize() { 30 | Assert.assertEquals(singleGameEntity.id, singleGameResponse.id) 31 | } 32 | 33 | @Test 34 | fun released_whenSingleGameMapped_isSameSize() { 35 | Assert.assertEquals(singleGameEntity.released, singleGameResponse.released) 36 | } 37 | 38 | @Test 39 | fun description_whenSingleGameMapped_isSameSize() { 40 | Assert.assertEquals(singleGameEntity.description, singleGameResponse.description) 41 | } 42 | 43 | @Test 44 | fun website_whenSingleGameMapped_isSameSize() { 45 | Assert.assertEquals(singleGameEntity.website, singleGameResponse.website) 46 | } 47 | 48 | @Test 49 | fun ratingTop_whenSingleGameMapped_isSameSize() { 50 | Assert.assertEquals(singleGameEntity.ratingTop, singleGameResponse.ratingTop) 51 | } 52 | 53 | 54 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/mapper/TotalEntityMapperTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.mapper 2 | 3 | import com.salihkinali.data.gameResults 4 | import com.salihkinali.domain.entity.TotalGameEntity 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Before 7 | import org.junit.Test 8 | 9 | class TotalEntityMapperTest { 10 | 11 | private val totalGameMapper = TotalEntityMapper() 12 | 13 | private lateinit var totalGameEntities: List 14 | 15 | @Before 16 | fun setup(){ 17 | totalGameEntities = totalGameMapper.map(gameResults) 18 | } 19 | 20 | @Test 21 | fun totalGameListSize_whenGameMapped_isSameSize() { 22 | assertEquals(totalGameEntities.size, gameResults.size) 23 | } 24 | 25 | @Test 26 | fun name_whenGameMappedWithName_isSame() { 27 | assertEquals(totalGameEntities.first().name, gameResults.first().name) 28 | } 29 | 30 | @Test 31 | fun rating_whenGameMappedWithRating_isSame(){ 32 | assertEquals(totalGameEntities.first().rating, gameResults.first().rating) 33 | } 34 | 35 | @Test 36 | fun id_whenGameMappedWithId_isSame(){ 37 | assertEquals(totalGameEntities.first().id, gameResults.first().id) 38 | } 39 | 40 | @Test 41 | fun released_whenGameMappedWithReleased_isSame(){ 42 | assertEquals(totalGameEntities.first().released, gameResults.first().released) 43 | } 44 | 45 | @Test 46 | fun lastGameName_whenGameeMapped_isSame() { 47 | assertEquals(totalGameEntities.last().name, totalGameEntities.last().name) 48 | } 49 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/repository/FakeGameRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.repository 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.data.gameScreenShotEntities 5 | import com.salihkinali.data.totalGameEntities 6 | import com.salihkinali.data.singleGameEntity 7 | import com.salihkinali.domain.entity.GameScreenShotEntity 8 | import com.salihkinali.domain.entity.SingleGameEntity 9 | import com.salihkinali.domain.entity.TotalGameEntity 10 | import com.salihkinali.domain.repository.GameRepository 11 | import kotlinx.coroutines.flow.Flow 12 | import kotlinx.coroutines.flow.flow 13 | import okio.IOException 14 | 15 | class FakeGameRepository : GameRepository { 16 | 17 | private var showErrorForTotalGameResponse = false 18 | private var showErrorForSingleGameResponse = false 19 | private var showErrorForGameScreenShotResponse = false 20 | 21 | fun updateShowErrorForTotalGameResponse(response: Boolean) { 22 | this.showErrorForTotalGameResponse = response 23 | } 24 | 25 | fun updateShowErrorForSingleGameResponse(response: Boolean) { 26 | this.showErrorForSingleGameResponse = response 27 | } 28 | 29 | fun updateShowErrorForGameScreenShotResponse(response: Boolean) { 30 | this.showErrorForGameScreenShotResponse = response 31 | } 32 | 33 | override fun getTotalGameInfo(): Flow>> = flow { 34 | emit(NetworkResponse.Loading) 35 | if (showErrorForTotalGameResponse) { 36 | emit(NetworkResponse.Error(IOException())) 37 | } else { 38 | emit(NetworkResponse.Success(totalGameEntities)) 39 | } 40 | } 41 | 42 | override fun getSingleGameInfo(id: Int): Flow> = flow { 43 | emit(NetworkResponse.Loading) 44 | if (showErrorForSingleGameResponse) { 45 | emit(NetworkResponse.Error(IOException())) 46 | } else { 47 | emit(NetworkResponse.Success(singleGameEntity)) 48 | } 49 | } 50 | 51 | override fun getGameScreenShots(id: Int): Flow>> = 52 | flow { 53 | emit(NetworkResponse.Loading) 54 | if (showErrorForGameScreenShotResponse) { 55 | emit(NetworkResponse.Error(IOException())) 56 | } else { 57 | emit(NetworkResponse.Success(gameScreenShotEntities)) 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/repository/GameRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.repository 2 | 3 | import app.cash.turbine.test 4 | import com.google.common.truth.Truth 5 | import com.salihkinali.common.NetworkResponse 6 | import com.salihkinali.data.GAME_ID 7 | import kotlinx.coroutines.runBlocking 8 | import org.junit.Test 9 | 10 | class GameRepositoryTest { 11 | private val fakeGameRepository = FakeGameRepository() 12 | 13 | /** 14 | Total Game Test 15 | */ 16 | @Test 17 | fun totalGameResponse_whenRemoteDataSource_ReturnSuccess_returnSuccess() { 18 | runBlocking { 19 | fakeGameRepository.getTotalGameInfo().test { 20 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 21 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 22 | awaitComplete() 23 | } 24 | } 25 | } 26 | 27 | @Test 28 | fun totalGameResponse_whenRemoteDataSource_ReturnError_returnError() { 29 | runBlocking { 30 | fakeGameRepository.updateShowErrorForTotalGameResponse(true) 31 | fakeGameRepository.getTotalGameInfo().test { 32 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 33 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 34 | awaitComplete() 35 | } 36 | } 37 | } 38 | 39 | /* Single Game Test */ 40 | 41 | @Test 42 | fun singleGameResponse_whenRemoteDataSource_ReturnSuccess_returnSuccess() { 43 | runBlocking { 44 | fakeGameRepository.getSingleGameInfo(GAME_ID).test { 45 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 46 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 47 | awaitComplete() 48 | } 49 | } 50 | } 51 | 52 | @Test 53 | fun singleGameResponse_whenRemoteDataSource_ReturnError_returnError() { 54 | runBlocking { 55 | fakeGameRepository.updateShowErrorForSingleGameResponse(true) 56 | fakeGameRepository.getSingleGameInfo(GAME_ID).test { 57 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 58 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 59 | awaitComplete() 60 | } 61 | } 62 | } 63 | 64 | /* Game ScreenShot Test */ 65 | 66 | @Test 67 | fun gameScreenShotResponse_whenRemoteDataSource_ReturnSuccess_returnSuccess() { 68 | runBlocking { 69 | fakeGameRepository.getGameScreenShots(GAME_ID).test { 70 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 71 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 72 | awaitComplete() 73 | } 74 | } 75 | } 76 | 77 | @Test 78 | fun gameScreenShotResponse_whenRemoteDataSource_ReturnError_returnError() { 79 | runBlocking { 80 | fakeGameRepository.updateShowErrorForGameScreenShotResponse(true) 81 | fakeGameRepository.getGameScreenShots(GAME_ID).test { 82 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 83 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 84 | awaitComplete() 85 | } 86 | } 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /core/data/src/test/java/com/salihkinali/data/source/RemoteDataSourceTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.data.source 2 | 3 | import app.cash.turbine.test 4 | import com.google.common.truth.Truth.assertThat 5 | import com.salihkinali.common.NetworkResponse 6 | import com.salihkinali.data.GAME_ID 7 | import com.salihkinali.data.api.GameApi 8 | import com.salihkinali.data.gameScResponse 9 | import com.salihkinali.data.singleGameResponse 10 | import com.salihkinali.data.totalGameResponse 11 | import kotlinx.coroutines.runBlocking 12 | import org.junit.Before 13 | import org.junit.Test 14 | import org.mockito.Mock 15 | import org.mockito.Mockito 16 | import org.mockito.MockitoAnnotations 17 | 18 | class RemoteDataSourceTest { 19 | 20 | @Mock 21 | private lateinit var gameApi: GameApi 22 | 23 | private lateinit var remoteDataSource: RemoteDataSourceImpl 24 | 25 | @Before 26 | fun setup() { 27 | MockitoAnnotations.openMocks(this) 28 | remoteDataSource = RemoteDataSourceImpl(gameApi) 29 | } 30 | 31 | 32 | /* Total Game Response Test */ 33 | 34 | @Test 35 | fun totalGameResponse_whenApiReturnSuccess_isResponseStateSuccess() { 36 | runBlocking { 37 | Mockito.`when`(gameApi.getTotalGameInfo()).thenReturn(totalGameResponse) 38 | remoteDataSource.getTotalGameInfo().test { 39 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 40 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 41 | awaitComplete() 42 | } 43 | } 44 | } 45 | 46 | @Test 47 | fun totalGameResponse_whenApiReturnNull_isResponseStateError() { 48 | runBlocking { 49 | Mockito.`when`(gameApi.getTotalGameInfo()).thenReturn(null) 50 | remoteDataSource.getTotalGameInfo().test { 51 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 52 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 53 | awaitComplete() 54 | } 55 | } 56 | } 57 | 58 | /* Single Game Response Test */ 59 | 60 | @Test 61 | fun singleGameResponse_whenApiReturnSuccess_isResponseStateSuccess() { 62 | runBlocking { 63 | Mockito.`when`(gameApi.getSingleGameInfo(GAME_ID)).thenReturn( 64 | singleGameResponse 65 | ) 66 | 67 | remoteDataSource.getSingleCharacter(GAME_ID).test { 68 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 69 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 70 | awaitComplete() 71 | } 72 | } 73 | } 74 | 75 | @Test 76 | fun singleGameResponse_whenApiReturnSuccess_isResponseStateError() { 77 | runBlocking { 78 | Mockito.`when`(gameApi.getSingleGameInfo(GAME_ID)).thenReturn(null) 79 | 80 | remoteDataSource.getSingleCharacter(GAME_ID).test { 81 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 82 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 83 | awaitComplete() 84 | } 85 | } 86 | } 87 | 88 | /* Game ScreenShot Test */ 89 | 90 | 91 | @Test 92 | fun gameScreenShotResponse_whenApiReturnSuccess_isResponseStateSuccess() { 93 | runBlocking { 94 | Mockito.`when`(gameApi.getGameScreenShots(GAME_ID)).thenReturn(gameScResponse) 95 | 96 | remoteDataSource.getGameScreenShots(GAME_ID).test { 97 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 98 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 99 | awaitComplete() 100 | } 101 | } 102 | } 103 | 104 | @Test 105 | fun gameScreenShotResponse_whenApiReturnSuccess_isResponseStateError() { 106 | runBlocking { 107 | Mockito.`when`(gameApi.getGameScreenShots(GAME_ID)).thenReturn(null) 108 | 109 | remoteDataSource.getGameScreenShots(GAME_ID).test { 110 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 111 | assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 112 | awaitComplete() 113 | } 114 | } 115 | } 116 | 117 | } -------------------------------------------------------------------------------- /core/domain/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/domain/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'com.google.dagger.hilt.android' 6 | } 7 | 8 | android { 9 | namespace 'com.salihkinali.domain' 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | minSdk 21 14 | targetSdk 33 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | } 34 | 35 | dependencies { 36 | 37 | implementation project(path: ':core:common') 38 | 39 | implementation 'androidx.core:core-ktx:1.7.0' 40 | implementation 'androidx.appcompat:appcompat:1.6.0' 41 | implementation 'com.google.android.material:material:1.8.0' 42 | testImplementation 'junit:junit:4.13.2' 43 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 44 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 45 | 46 | //Dagger-Hilt 47 | implementation "com.google.dagger:hilt-android:2.44" 48 | kapt "com.google.dagger:hilt-compiler:2.44" 49 | 50 | // Test 51 | testImplementation 'junit:junit:4.13.2' 52 | testImplementation "com.squareup.okhttp3:mockwebserver:4.10.0" 53 | testImplementation "com.google.truth:truth:1.1.3" 54 | testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0" 55 | testImplementation "org.mockito:mockito-inline:3.0.0" 56 | testImplementation 'app.cash.turbine:turbine:0.12.1' 57 | 58 | implementation "androidx.arch.core:core-testing:2.1.0" 59 | 60 | testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2" 61 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 62 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 63 | } -------------------------------------------------------------------------------- /core/domain/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/core/domain/consumer-rules.pro -------------------------------------------------------------------------------- /core/domain/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/domain/src/androidTest/java/com/salihkinali/domain/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.salihkinali.domain.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/di/UseCaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.di 2 | 3 | import com.salihkinali.domain.usecase.* 4 | import dagger.Binds 5 | import dagger.Module 6 | import dagger.hilt.InstallIn 7 | import dagger.hilt.android.components.ViewModelComponent 8 | import dagger.hilt.android.scopes.ViewModelScoped 9 | 10 | @Module 11 | @InstallIn(ViewModelComponent::class) 12 | abstract class UseCaseModule { 13 | 14 | @Binds 15 | @ViewModelScoped 16 | abstract fun bindTotalGameUseCase(gameUseCaseImpl: GetTotalGameUseCaseImpl): GetTotalGameUseCase 17 | 18 | @Binds 19 | @ViewModelScoped 20 | abstract fun bindSingleGameUseCase(singleGameUseCase: GetSingleGameUseCaseImpl): GetSingleGameUseCase 21 | 22 | @Binds 23 | @ViewModelScoped 24 | abstract fun bindGameScreenShotUseCase(gameScreenShotUseCaseImp: GetGameScreenUseCaseImpl): GetGameScreenShotUseCase 25 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/entity/GameScreenShotEntity.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.entity 2 | 3 | data class GameScreenShotEntity( 4 | val gameScreenShot: String 5 | ) 6 | -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/entity/SingleGameEntity.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.entity 2 | 3 | data class SingleGameEntity( 4 | val id: Int, 5 | val name: String, 6 | val released: String, 7 | val backgroundImage: String, 8 | val description: String, 9 | val website: String, 10 | val ratingTop: Int, 11 | ) 12 | -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/entity/TotalGameEntity.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.entity 2 | 3 | data class TotalGameEntity( 4 | val id: Int, 5 | val name: String, 6 | val rating: Double, 7 | val released: String, 8 | val backgroundImage: String, 9 | ) 10 | -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/repository/GameRepository.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.repository 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.GameScreenShotEntity 5 | import com.salihkinali.domain.entity.SingleGameEntity 6 | import com.salihkinali.domain.entity.TotalGameEntity 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | 10 | interface GameRepository { 11 | fun getTotalGameInfo(): Flow>> 12 | fun getSingleGameInfo(id: Int): Flow> 13 | fun getGameScreenShots(id: Int): Flow>> 14 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/usecase/GetGameScreenShotUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.usecase 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.GameScreenShotEntity 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface GetGameScreenShotUseCase { 8 | operator fun invoke(gameId: Int): Flow>> 9 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/usecase/GetGameScreenUseCaseImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.usecase 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.GameScreenShotEntity 5 | import com.salihkinali.domain.repository.GameRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetGameScreenUseCaseImpl @Inject constructor( 10 | private val repository: GameRepository 11 | ) : GetGameScreenShotUseCase { 12 | override fun invoke(gameId: Int): Flow>> = repository.getGameScreenShots(gameId) 13 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/usecase/GetSingleGameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.usecase 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.SingleGameEntity 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface GetSingleGameUseCase { 8 | 9 | operator fun invoke(id: Int): Flow> 10 | 11 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/usecase/GetSingleGameUseCaseImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.usecase 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.SingleGameEntity 5 | import com.salihkinali.domain.repository.GameRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetSingleGameUseCaseImpl @Inject constructor( 10 | private val repository: GameRepository, 11 | ) : GetSingleGameUseCase { 12 | override fun invoke(id:Int): Flow> = repository.getSingleGameInfo(id) 13 | } 14 | -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/usecase/GetTotalGameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.usecase 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.TotalGameEntity 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface GetTotalGameUseCase { 8 | operator fun invoke(): Flow>> 9 | } -------------------------------------------------------------------------------- /core/domain/src/main/java/com/salihkinali/domain/usecase/GetTotalGameUseCaseImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.usecase 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.TotalGameEntity 5 | import com.salihkinali.domain.repository.GameRepository 6 | import kotlinx.coroutines.flow.Flow 7 | import javax.inject.Inject 8 | 9 | class GetTotalGameUseCaseImpl @Inject constructor( 10 | private val repository: GameRepository, 11 | ) : GetTotalGameUseCase { 12 | override fun invoke(): Flow>> = repository.getTotalGameInfo() 13 | } -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/TestConstants.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain 2 | 3 | import androidx.annotation.VisibleForTesting 4 | import com.salihkinali.domain.entity.GameScreenShotEntity 5 | import com.salihkinali.domain.entity.SingleGameEntity 6 | import com.salihkinali.domain.entity.TotalGameEntity 7 | 8 | @VisibleForTesting 9 | val totalGameEntity = TotalGameEntity( 10 | id = 3498, 11 | name = "Grand Theft Auto V", 12 | rating = 4.47, 13 | released = "2013-09-17", 14 | backgroundImage = "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg" 15 | ) 16 | 17 | @VisibleForTesting 18 | val gameScreenShotEntity = GameScreenShotEntity( 19 | gameScreenShot = "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg" 20 | ) 21 | 22 | @VisibleForTesting 23 | val gameScreenShotEntities = listOf(gameScreenShotEntity) 24 | 25 | @VisibleForTesting 26 | val totalGameEntities = listOf(totalGameEntity) 27 | 28 | @VisibleForTesting 29 | val singleGameEntity = SingleGameEntity( 30 | id = 3498, 31 | name = "Grand Theft Auto V", 32 | released = "2013-09-17", 33 | backgroundImage = "https://media.rawg.io/media/games/456/456dea5e1c7e3cd07060c14e96612001.jpg", 34 | description = "Rockstar Games went bigger, since their previous installment of the series. You get the complicated and realistic world-building from Liberty City of GTA4 in the setting of lively and diverse Los Santos", 35 | website = "http://www.rockstargames.com/V/", 36 | ratingTop = 5 37 | ) 38 | 39 | const val GAME_ID: Int = 3498 40 | 41 | -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/screenshots/FakeGetGameScreenShotUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.screenshots 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.GameScreenShotEntity 5 | import com.salihkinali.domain.gameScreenShotEntities 6 | import com.salihkinali.domain.usecase.GetGameScreenShotUseCase 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import java.io.IOException 10 | 11 | class FakeGetGameScreenShotUseCase: GetGameScreenShotUseCase { 12 | 13 | private var showError = false 14 | 15 | fun updateShowError(showError: Boolean) { 16 | this.showError = showError 17 | } 18 | 19 | override fun invoke(gameId: Int): Flow>> = flow { 20 | emit(NetworkResponse.Loading) 21 | if (showError){ 22 | emit(NetworkResponse.Error(IOException())) 23 | }else{ 24 | emit(NetworkResponse.Success(gameScreenShotEntities)) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/screenshots/GetGameScreenShotUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.screenshots 2 | 3 | import app.cash.turbine.test 4 | import com.google.common.truth.Truth 5 | import com.salihkinali.common.NetworkResponse 6 | import com.salihkinali.domain.GAME_ID 7 | import kotlinx.coroutines.runBlocking 8 | import org.junit.Test 9 | 10 | class GetGameScreenShotUseCaseTest { 11 | 12 | private val fakeGetGameScreenShotUseCase = FakeGetGameScreenShotUseCase() 13 | 14 | /* GetGameScreenShotUseCase Test */ 15 | 16 | @Test 17 | fun networkState_whenStateLoading_returnLoading() { 18 | runBlocking { 19 | fakeGetGameScreenShotUseCase(GAME_ID).test { 20 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 21 | cancelAndIgnoreRemainingEvents() 22 | } 23 | } 24 | } 25 | 26 | @Test 27 | fun networkState_whenStateLoadingAndSuccess_returnLoadingAndSuccessSequentially() { 28 | runBlocking { 29 | fakeGetGameScreenShotUseCase(GAME_ID).test { 30 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 31 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 32 | awaitComplete() 33 | } 34 | } 35 | } 36 | 37 | @Test 38 | fun networkState_whenStateLoadingAndError_returnError() { 39 | runBlocking { 40 | fakeGetGameScreenShotUseCase.updateShowError(true) 41 | fakeGetGameScreenShotUseCase(GAME_ID).test { 42 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 43 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 44 | awaitComplete() 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/single/FakeSingleGameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.single 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.SingleGameEntity 5 | import com.salihkinali.domain.singleGameEntity 6 | import com.salihkinali.domain.usecase.GetSingleGameUseCase 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import java.io.IOException 10 | 11 | class FakeSingleGameUseCase : GetSingleGameUseCase { 12 | 13 | private var showError = false 14 | 15 | fun updateShowError(showError: Boolean) { 16 | this.showError = showError 17 | } 18 | 19 | override fun invoke(id: Int): Flow> = flow{ 20 | emit(NetworkResponse.Loading) 21 | if (showError){ 22 | emit(NetworkResponse.Error(IOException())) 23 | }else{ 24 | emit(NetworkResponse.Success(singleGameEntity)) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/single/GetSingleGameUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.single 2 | 3 | import app.cash.turbine.test 4 | import com.google.common.truth.Truth 5 | import com.salihkinali.common.NetworkResponse 6 | import com.salihkinali.domain.GAME_ID 7 | import kotlinx.coroutines.runBlocking 8 | import org.junit.Test 9 | 10 | class GetSingleGameUseCaseTest { 11 | private val fakeSingleGameUseCase = FakeSingleGameUseCase() 12 | 13 | @Test 14 | fun networkState_whenStateLoading_returnLoading() { 15 | runBlocking { 16 | fakeSingleGameUseCase(GAME_ID).test { 17 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 18 | cancelAndIgnoreRemainingEvents() 19 | } 20 | } 21 | } 22 | 23 | @Test 24 | fun networkState_whenStateLoadingAndSuccess_returnLoadingAndSuccessSequentially() { 25 | runBlocking { 26 | fakeSingleGameUseCase(GAME_ID).test { 27 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 28 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 29 | awaitComplete() 30 | } 31 | } 32 | } 33 | 34 | @Test 35 | fun networkState_whenStateLoadingAndError_returnError() { 36 | runBlocking { 37 | fakeSingleGameUseCase.updateShowError(true) 38 | fakeSingleGameUseCase(GAME_ID).test { 39 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 40 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 41 | awaitComplete() 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/total/FakeGetTotalGameUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.total 2 | 3 | import com.salihkinali.common.NetworkResponse 4 | import com.salihkinali.domain.entity.TotalGameEntity 5 | import com.salihkinali.domain.totalGameEntities 6 | import com.salihkinali.domain.usecase.GetTotalGameUseCase 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.flow.flow 9 | import java.io.IOException 10 | 11 | class FakeGetTotalGameUseCase: GetTotalGameUseCase { 12 | 13 | private var showError = false 14 | 15 | fun updateShowError(showError: Boolean) { 16 | this.showError = showError 17 | } 18 | override fun invoke(): Flow>> = flow { 19 | emit(NetworkResponse.Loading) 20 | if (showError){ 21 | emit(NetworkResponse.Error(IOException())) 22 | }else{ 23 | emit(NetworkResponse.Success(totalGameEntities)) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /core/domain/src/test/java/com/salihkinali/domain/total/GetTotalGameUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.domain.total 2 | 3 | import app.cash.turbine.test 4 | import com.google.common.truth.Truth 5 | import com.salihkinali.common.NetworkResponse 6 | import kotlinx.coroutines.runBlocking 7 | import org.junit.Test 8 | 9 | class GetTotalGameUseCaseTest { 10 | 11 | private val fakeGetTotalGameUseCase = FakeGetTotalGameUseCase() 12 | 13 | @Test 14 | fun networkState_whenStateLoading_returnLoading() { 15 | runBlocking { 16 | fakeGetTotalGameUseCase().test { 17 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 18 | cancelAndIgnoreRemainingEvents() 19 | } 20 | } 21 | } 22 | 23 | @Test 24 | fun networkState_whenStateLoadingAndSuccess_returnLoadingAndSuccessSequentially() { 25 | runBlocking { 26 | fakeGetTotalGameUseCase().test { 27 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 28 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Success::class.java) 29 | awaitComplete() 30 | } 31 | } 32 | } 33 | 34 | @Test 35 | fun networkState_whenStateLoadingAndError_returnError() { 36 | runBlocking { 37 | fakeGetTotalGameUseCase.updateShowError(true) 38 | fakeGetTotalGameUseCase().test { 39 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Loading::class.java) 40 | Truth.assertThat(awaitItem()).isInstanceOf(NetworkResponse.Error::class.java) 41 | awaitComplete() 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /core/ui/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/ui/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.salihkinali.ui' 8 | compileSdk 33 9 | 10 | defaultConfig { 11 | minSdk 21 12 | targetSdk 33 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | buildFeatures { 26 | viewBinding true 27 | } 28 | 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | kotlinOptions { 34 | jvmTarget = '1.8' 35 | } 36 | } 37 | 38 | dependencies { 39 | 40 | implementation 'androidx.core:core-ktx:1.7.0' 41 | implementation 'androidx.appcompat:appcompat:1.6.0' 42 | implementation 'com.google.android.material:material:1.8.0' 43 | testImplementation 'junit:junit:4.13.2' 44 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 45 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 46 | 47 | //Glide 48 | implementation 'com.github.bumptech.glide:glide:4.14.2' 49 | annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' 50 | } -------------------------------------------------------------------------------- /core/ui/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/core/ui/consumer-rules.pro -------------------------------------------------------------------------------- /core/ui/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /core/ui/src/androidTest/java/com/salihkinali/ui/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.ui 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.salihkinali.ui.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/ui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/ui/src/main/java/com/salihkinali/ui/base/BaseRecyclerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.ui.base 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | abstract class BaseRecyclerAdapter> : 8 | RecyclerView.Adapter() { 9 | 10 | private val items = mutableListOf() 11 | 12 | @SuppressLint("NotifyDataSetChanged") 13 | fun updateItems(newItems: List) { 14 | items.apply { 15 | clear() 16 | addAll(newItems) 17 | notifyDataSetChanged() 18 | } 19 | } 20 | 21 | fun getItem(position: Int) = items[position] 22 | 23 | abstract override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH 24 | 25 | override fun onBindViewHolder(holder: VH, position: Int) { 26 | holder.onBind(items[position]) 27 | } 28 | 29 | override fun getItemCount() = items.size 30 | } 31 | -------------------------------------------------------------------------------- /core/ui/src/main/java/com/salihkinali/ui/base/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.ui.base 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) { 7 | abstract fun onBind(data: T) 8 | } -------------------------------------------------------------------------------- /core/ui/src/main/java/com/salihkinali/ui/common/UiResponseState.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.ui.common 2 | 3 | import androidx.annotation.StringRes 4 | 5 | sealed class UiResponseState { 6 | object Loading : UiResponseState() 7 | data class Error(@StringRes val error: Int) : UiResponseState() 8 | data class Success(val data:T) : UiResponseState() 9 | } -------------------------------------------------------------------------------- /core/ui/src/main/java/com/salihkinali/ui/extension/ViewBindingExtension.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.ui.extension 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.viewbinding.ViewBinding 6 | 7 | inline fun ViewGroup.inflateBinding( 8 | crossinline bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> T 9 | ) = bindingInflater(LayoutInflater.from(this.context), this, false) 10 | -------------------------------------------------------------------------------- /core/ui/src/main/java/com/salihkinali/ui/extension/ViewExtension.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.ui.extension 2 | 3 | import android.view.View 4 | import android.widget.ImageView 5 | import com.bumptech.glide.Glide 6 | 7 | fun ImageView.downloadImage(url: String?) { 8 | Glide.with(this) 9 | .load(url) 10 | .into(this) 11 | } 12 | 13 | fun View.visible(visible: Boolean) { 14 | if (visible) { 15 | this.visibility = View.VISIBLE 16 | } else { 17 | this.visibility = View.GONE 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #0FA3B6 7 | #0C8392 8 | #FF018786 9 | #FF000000 10 | #FFFFFFFF 11 | #4D4D4D 12 | -------------------------------------------------------------------------------- /core/ui/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Game Guide 3 | This is error message. 4 | Game Screenshots 5 | -------------------------------------------------------------------------------- /core/ui/src/test/java/com/salihkinali/ui/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.ui 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /feature/detail/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/detail/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'com.google.dagger.hilt.android' 6 | } 7 | 8 | android { 9 | namespace 'com.salihkinali.feature.detail' 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | minSdk 21 14 | targetSdk 33 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | buildFeatures{ 28 | viewBinding true 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | kotlinOptions { 35 | jvmTarget = '1.8' 36 | } 37 | } 38 | 39 | dependencies { 40 | 41 | implementation project(path: ":core:common") 42 | implementation project(path: ":core:domain") 43 | implementation project(path: ":core:ui") 44 | implementation project(path: ":navigation") 45 | 46 | implementation 'androidx.core:core-ktx:1.7.0' 47 | implementation 'androidx.appcompat:appcompat:1.6.0' 48 | implementation 'com.google.android.material:material:1.8.0' 49 | testImplementation 'junit:junit:4.13.2' 50 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 52 | 53 | // ViewModel 54 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" 55 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1" 56 | implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1" 57 | kapt "androidx.lifecycle:lifecycle-compiler:2.5.1" 58 | 59 | // Lifecycle 60 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" 61 | implementation 'androidx.fragment:fragment-ktx:1.5.4' 62 | implementation "androidx.activity:activity-ktx:1.6.1" 63 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" 64 | 65 | // Hilt 66 | implementation "com.google.dagger:hilt-android:2.44" 67 | kapt "com.google.dagger:hilt-compiler:2.44" 68 | } -------------------------------------------------------------------------------- /feature/detail/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/feature/detail/consumer-rules.pro -------------------------------------------------------------------------------- /feature/detail/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /feature/detail/src/androidTest/java/com/salihkinali/feature/detail/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 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.salihkinali.feature.detail.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feature/detail/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/DetailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.fragment.app.viewModels 9 | import androidx.lifecycle.lifecycleScope 10 | import androidx.navigation.fragment.navArgs 11 | import com.salihkinali.feature.detail.databinding.FragmentDetailBinding 12 | import com.salihkinali.ui.common.UiResponseState 13 | import com.salihkinali.ui.extension.downloadImage 14 | import com.salihkinali.ui.extension.visible 15 | import dagger.hilt.android.AndroidEntryPoint 16 | import kotlinx.coroutines.flow.collectLatest 17 | 18 | @AndroidEntryPoint 19 | class DetailFragment : Fragment() { 20 | private var _binding: FragmentDetailBinding? = null 21 | private val binding get() = _binding!! 22 | 23 | private val viewModel: DetailViewModel by viewModels() 24 | 25 | private val args: DetailFragmentArgs by navArgs() 26 | 27 | private val adapter by lazy { DetailScreenShotAdapter() } 28 | 29 | override fun onCreateView( 30 | inflater: LayoutInflater, container: ViewGroup?, 31 | savedInstanceState: Bundle? 32 | ): View { 33 | _binding = FragmentDetailBinding.inflate(layoutInflater) 34 | return binding.root 35 | } 36 | 37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 38 | super.onViewCreated(view, savedInstanceState) 39 | 40 | viewModel.getDataFromSource(args.gameId) 41 | setGameScreenShot() 42 | showData() 43 | } 44 | 45 | private fun setGameScreenShot() { 46 | binding.gameScreenShotRecylerView.adapter = adapter 47 | } 48 | 49 | private fun showData() { 50 | lifecycleScope.launchWhenResumed { 51 | // This is Detail Page 52 | viewModel.detailUiData.collectLatest { 53 | when (it) { 54 | is UiResponseState.Error -> { 55 | binding.detailProgressBar.visible(false) 56 | binding.gameScreenShotText.visible(false) 57 | } 58 | 59 | UiResponseState.Loading -> { 60 | binding.detailProgressBar.visible(true) 61 | binding.gameScreenShotText.visible(false) 62 | } 63 | is UiResponseState.Success -> { 64 | binding.detailProgressBar.visible(false) 65 | showDetailInformation(it.data) 66 | } 67 | } 68 | } 69 | } 70 | lifecycleScope.launchWhenResumed { 71 | 72 | // This is Game ScreenShot information 73 | viewModel.detailScreenShot.collectLatest { 74 | when (it) { 75 | is UiResponseState.Error -> { 76 | binding.detailProgressBar.visible(false) 77 | binding.gameScreenShotText.visible(false) 78 | } 79 | UiResponseState.Loading -> { 80 | binding.detailProgressBar.visible(true) 81 | binding.gameScreenShotText.visible(false) 82 | } 83 | 84 | is UiResponseState.Success -> { 85 | binding.detailProgressBar.visible(false) 86 | binding.gameScreenShotText.visible(true) 87 | showScreenShot(it.data) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | private fun showScreenShot(data: List) { 95 | adapter.updateItems(data) 96 | } 97 | 98 | private fun showDetailInformation(data: SingleGameUiData) { 99 | 100 | binding.gameNameText.text = data.name 101 | binding.detailImageView.downloadImage(data.backgroundImage) 102 | binding.gameDescriptionText.text = data.description 103 | binding.releasedText.text = data.released 104 | } 105 | 106 | override fun onDestroyView() { 107 | super.onDestroyView() 108 | _binding = null 109 | } 110 | 111 | 112 | } -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/DetailScreenShotAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | import android.view.ViewGroup 4 | import com.salihkinali.ui.base.BaseRecyclerAdapter 5 | 6 | class DetailScreenShotAdapter: 7 | BaseRecyclerAdapter() { 8 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameScreenShotViewHolder { 9 | return GameScreenShotViewHolder.createFrom(parent) 10 | } 11 | } -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/DetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.salihkinali.common.NetworkResponse 6 | import com.salihkinali.common.mapper.GameListMapper 7 | import com.salihkinali.common.mapper.GameMapper 8 | import com.salihkinali.domain.entity.GameScreenShotEntity 9 | import com.salihkinali.domain.entity.SingleGameEntity 10 | import com.salihkinali.domain.usecase.GetGameScreenShotUseCase 11 | import com.salihkinali.domain.usecase.GetSingleGameUseCase 12 | import com.salihkinali.ui.common.UiResponseState 13 | import dagger.hilt.android.lifecycle.HiltViewModel 14 | import kotlinx.coroutines.flow.MutableStateFlow 15 | import com.salihkinali.ui.R as uiRes 16 | import kotlinx.coroutines.flow.StateFlow 17 | import kotlinx.coroutines.flow.asStateFlow 18 | import kotlinx.coroutines.flow.collectLatest 19 | import kotlinx.coroutines.launch 20 | import javax.inject.Inject 21 | 22 | @HiltViewModel 23 | class DetailViewModel @Inject constructor( 24 | private val singleGameUseCase: GetSingleGameUseCase, 25 | private val gameScreenShotUseCase: GetGameScreenShotUseCase, 26 | private val gameScreenShotUiMapper: GameListMapper, 27 | private val singleGameMapper: GameMapper 28 | ) : ViewModel() { 29 | 30 | private val _detailUiData = 31 | MutableStateFlow>(UiResponseState.Loading) 32 | val detailUiData: StateFlow> get() = _detailUiData 33 | 34 | private val _detailScreenShot = 35 | MutableStateFlow>>(UiResponseState.Loading) 36 | val detailScreenShot = _detailScreenShot.asStateFlow() 37 | 38 | 39 | 40 | 41 | fun getDataFromSource(id: Int) { 42 | viewModelScope.launch { 43 | 44 | singleGameUseCase.invoke(id).collectLatest { 45 | 46 | when (it) { 47 | is NetworkResponse.Error -> { 48 | _detailUiData.emit(UiResponseState.Error(uiRes.string.error_message)) 49 | } 50 | is NetworkResponse.Loading -> _detailUiData.emit(UiResponseState.Loading) 51 | 52 | is NetworkResponse.Success -> { 53 | _detailUiData.emit(UiResponseState.Success(singleGameMapper.map(it.result))) 54 | } 55 | } 56 | } 57 | 58 | gameScreenShotUseCase.invoke(id).collectLatest { 59 | when (it) { 60 | 61 | is NetworkResponse.Error -> { 62 | _detailScreenShot.emit(UiResponseState.Error(uiRes.string.error_message)) 63 | } 64 | is NetworkResponse.Loading -> _detailScreenShot.emit(UiResponseState.Loading) 65 | 66 | is NetworkResponse.Success -> { 67 | _detailScreenShot.emit(UiResponseState.Success(gameMapper(it.result))) 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | private fun gameMapper(list: List): List { 75 | return gameScreenShotUiMapper.map(list) 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/GameScreenShotUiData.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | data class GameScreenShotUiData( 4 | val gameShotData : String 5 | ) -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/GameScreenShotUiMapperImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | import com.salihkinali.common.mapper.GameListMapper 4 | import com.salihkinali.domain.entity.GameScreenShotEntity 5 | import javax.inject.Inject 6 | 7 | class GameScreenShotUiMapperImpl @Inject constructor() : 8 | GameListMapper { 9 | override fun map(input: List?): List { 10 | return input?.map { 11 | GameScreenShotUiData( 12 | gameShotData = it.gameScreenShot 13 | ) 14 | } ?: emptyList() 15 | } 16 | } -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/GameScreenShotViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | import android.view.ViewGroup 4 | import com.salihkinali.feature.detail.databinding.AppScreenshotsItemBinding 5 | import com.salihkinali.ui.base.BaseViewHolder 6 | import com.salihkinali.ui.extension.downloadImage 7 | import com.salihkinali.ui.extension.inflateBinding 8 | 9 | class GameScreenShotViewHolder(private val binding: AppScreenshotsItemBinding) : 10 | BaseViewHolder(binding.root) { 11 | 12 | companion object { 13 | fun createFrom(parent: ViewGroup): GameScreenShotViewHolder { 14 | return GameScreenShotViewHolder(parent.inflateBinding(AppScreenshotsItemBinding::inflate)) 15 | } 16 | } 17 | 18 | override fun onBind(data: GameScreenShotUiData) { 19 | binding.screenShotImage.downloadImage(data.gameShotData) 20 | } 21 | } -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/SingleGameUiData.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | data class SingleGameUiData( 4 | val id: Int, 5 | val name: String, 6 | val released: String, 7 | val backgroundImage: String, 8 | val description: String, 9 | val website: String, 10 | val ratingTop: Int 11 | ) -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/SingleGameUiDataImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | import com.salihkinali.common.mapper.GameMapper 4 | import com.salihkinali.domain.entity.SingleGameEntity 5 | import javax.inject.Inject 6 | 7 | 8 | class SingleGameUiDataImpl @Inject constructor() : GameMapper { 9 | override fun map(input: SingleGameEntity): SingleGameUiData { 10 | return SingleGameUiData( 11 | id = input.id, 12 | name = input.name, 13 | released = input.released, 14 | backgroundImage = input.backgroundImage, 15 | description = input.description, 16 | website = input.website, 17 | ratingTop = input.ratingTop 18 | ) 19 | } 20 | } -------------------------------------------------------------------------------- /feature/detail/src/main/java/com/salihkinali/feature/detail/di/DetailMapperModule.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail.di 2 | 3 | import com.salihkinali.common.mapper.GameListMapper 4 | import com.salihkinali.common.mapper.GameMapper 5 | import com.salihkinali.domain.entity.GameScreenShotEntity 6 | import com.salihkinali.domain.entity.SingleGameEntity 7 | import com.salihkinali.feature.detail.GameScreenShotUiData 8 | import com.salihkinali.feature.detail.GameScreenShotUiMapperImpl 9 | import com.salihkinali.feature.detail.SingleGameUiData 10 | import com.salihkinali.feature.detail.SingleGameUiDataImpl 11 | import dagger.Binds 12 | import dagger.Module 13 | import dagger.hilt.InstallIn 14 | import dagger.hilt.android.components.ViewModelComponent 15 | import dagger.hilt.android.scopes.ViewModelScoped 16 | 17 | @Module 18 | @InstallIn(ViewModelComponent::class) 19 | abstract class DetailMapperModule { 20 | 21 | 22 | @Binds 23 | @ViewModelScoped 24 | abstract fun bindGameScreenShotMapper(gameScreenShotUiMapperImpl: GameScreenShotUiMapperImpl): 25 | GameListMapper 26 | 27 | @Binds 28 | @ViewModelScoped 29 | abstract fun bindSingleUiMapper(singleGameUiDataImpl: SingleGameUiDataImpl): GameMapper 30 | 31 | 32 | } -------------------------------------------------------------------------------- /feature/detail/src/main/res/layout/app_screenshots_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /feature/detail/src/main/res/layout/fragment_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 26 | 27 | 32 | 33 | 38 | 39 | 48 | 49 | 56 | 57 | 58 | 67 | 68 | 75 | 76 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /feature/detail/src/test/java/com/salihkinali/feature/detail/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.detail 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /feature/home/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/home/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'com.google.dagger.hilt.android' 6 | } 7 | 8 | android { 9 | namespace 'com.salihkinali.feature.home' 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | minSdk 21 14 | targetSdk 33 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | consumerProguardFiles "consumer-rules.pro" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | buildFeatures { 28 | viewBinding true 29 | } 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | kotlinOptions { 35 | jvmTarget = '1.8' 36 | } 37 | } 38 | 39 | dependencies { 40 | 41 | implementation project(path: ":core:common") 42 | implementation project(path: ":core:domain") 43 | implementation project(path: ":core:ui") 44 | implementation project(path: ":navigation") 45 | 46 | implementation 'androidx.core:core-ktx:1.7.0' 47 | implementation 'androidx.appcompat:appcompat:1.6.0' 48 | implementation 'com.google.android.material:material:1.8.0' 49 | testImplementation 'junit:junit:4.13.2' 50 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 51 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 52 | 53 | // Lifecycle 54 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" 55 | implementation 'androidx.fragment:fragment-ktx:1.5.4' 56 | implementation "androidx.activity:activity-ktx:1.6.1" 57 | implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" 58 | 59 | // Hilt 60 | implementation "com.google.dagger:hilt-android:2.44" 61 | kapt "com.google.dagger:hilt-compiler:2.44" 62 | } -------------------------------------------------------------------------------- /feature/home/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/feature/home/consumer-rules.pro -------------------------------------------------------------------------------- /feature/home/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 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 -------------------------------------------------------------------------------- /feature/home/src/androidTest/java/com/salihkinali/feature/home/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.salihkinali.feature.home.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feature/home/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /feature/home/src/main/java/com/salihkinali/feature/home/GameViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | import android.annotation.SuppressLint 4 | import android.view.ViewGroup 5 | import com.salihkinali.feature.home.databinding.AdapterListItemBinding 6 | import com.salihkinali.ui.base.BaseViewHolder 7 | import com.salihkinali.ui.extension.downloadImage 8 | import com.salihkinali.ui.extension.inflateBinding 9 | 10 | class GameViewHolder(private val binding: AdapterListItemBinding, private val itemClick: ((Int) -> Unit)?) : 11 | BaseViewHolder(binding.root) { 12 | 13 | companion object { 14 | fun createFrom(parent: ViewGroup, itemClickListener: ((Int) -> Unit)?): GameViewHolder { 15 | return GameViewHolder(parent.inflateBinding(AdapterListItemBinding::inflate),itemClickListener) 16 | } 17 | } 18 | 19 | @SuppressLint("SetTextI18n") 20 | override fun onBind(data: TotalGameUiData) { 21 | binding.apply { 22 | gameTitle.text = data.name 23 | gameImage.downloadImage(data.backgroundImage) 24 | gameRatingText.text = "Rating: ${data.rating}" 25 | gameReleasedText.text = "Released: ${data.released}" 26 | itemBody.setOnClickListener { itemClick?.invoke(data.id) } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /feature/home/src/main/java/com/salihkinali/feature/home/HomeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | import android.view.ViewGroup 4 | import com.salihkinali.ui.base.BaseRecyclerAdapter 5 | 6 | class HomeAdapter(private val itemClickListener: ((Int) -> Unit)?) : BaseRecyclerAdapter() { 7 | 8 | 9 | 10 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GameViewHolder { 11 | return GameViewHolder.createFrom(parent, itemClickListener) 12 | } 13 | } -------------------------------------------------------------------------------- /feature/home/src/main/java/com/salihkinali/feature/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.Toast 8 | import androidx.fragment.app.Fragment 9 | import androidx.fragment.app.viewModels 10 | import androidx.lifecycle.lifecycleScope 11 | import androidx.navigation.fragment.findNavController 12 | import com.salihkinali.feature.home.databinding.FragmentHomeBinding 13 | import com.salihkinali.ui.common.UiResponseState 14 | import com.salihkinali.ui.extension.visible 15 | import dagger.hilt.android.AndroidEntryPoint 16 | 17 | @AndroidEntryPoint 18 | class HomeFragment : Fragment() { 19 | 20 | private val viewModel: HomeViewModel by viewModels() 21 | private var _binding: FragmentHomeBinding? = null 22 | private val binding get() = _binding!! 23 | private val adapter by lazy { HomeAdapter(::passDataId) } 24 | 25 | override fun onCreateView( 26 | inflater: LayoutInflater, container: ViewGroup?, 27 | savedInstanceState: Bundle? 28 | ): View { 29 | _binding = FragmentHomeBinding.inflate(inflater) 30 | return binding.root 31 | } 32 | 33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 34 | super.onViewCreated(view, savedInstanceState) 35 | observeUiData() 36 | handledata() 37 | 38 | } 39 | 40 | private fun handledata() { 41 | binding.gamesRecylerview.adapter = adapter 42 | } 43 | 44 | private fun observeUiData() { 45 | 46 | lifecycleScope.launchWhenResumed { 47 | 48 | viewModel.homeUidata.collect { 49 | when (it) { 50 | is UiResponseState.Error -> { 51 | Toast.makeText( 52 | requireContext(), 53 | "This is error message.", 54 | Toast.LENGTH_SHORT 55 | ).show() 56 | binding.progressBar.visible(false) 57 | } 58 | is UiResponseState.Loading -> binding.progressBar.visible(true) 59 | 60 | is UiResponseState.Success -> { 61 | binding.progressBar.visible(false) 62 | loadDataToAdapter(it.data) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | private fun loadDataToAdapter(data: List) { 70 | adapter.updateItems(data) 71 | } 72 | 73 | private fun passDataId(dataId: Int) { 74 | val action = HomeFragmentDirections.homeToDetailFragment(dataId) 75 | findNavController().navigate(action) 76 | } 77 | 78 | override fun onDestroyView() { 79 | super.onDestroyView() 80 | _binding = null 81 | } 82 | 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /feature/home/src/main/java/com/salihkinali/feature/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import com.salihkinali.common.NetworkResponse 7 | import com.salihkinali.common.mapper.GameListMapper 8 | import com.salihkinali.domain.entity.TotalGameEntity 9 | import com.salihkinali.domain.usecase.GetTotalGameUseCase 10 | import com.salihkinali.ui.common.UiResponseState 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import com.salihkinali.ui.R as uiRes 13 | import kotlinx.coroutines.flow.MutableStateFlow 14 | import kotlinx.coroutines.flow.asStateFlow 15 | import kotlinx.coroutines.flow.collectLatest 16 | import kotlinx.coroutines.launch 17 | import javax.inject.Inject 18 | 19 | @HiltViewModel 20 | class HomeViewModel @Inject constructor( 21 | private val gameUseCase: GetTotalGameUseCase, 22 | private val gameListMapper: GameListMapper 23 | ) : ViewModel() { 24 | 25 | private val _homeUiData = MutableStateFlow>>( 26 | UiResponseState.Loading) 27 | val homeUidata = _homeUiData.asStateFlow() 28 | 29 | init { 30 | getData() 31 | } 32 | 33 | private fun getData() { 34 | viewModelScope.launch { 35 | gameUseCase.invoke().collectLatest { 36 | when (it) { 37 | is NetworkResponse.Error -> { 38 | _homeUiData.emit(UiResponseState.Error(uiRes.string.error_message)) 39 | } 40 | is NetworkResponse.Loading -> _homeUiData.emit(UiResponseState.Loading) 41 | 42 | is NetworkResponse.Success -> { 43 | _homeUiData.emit(UiResponseState.Success(gameListMapper.map(it.result))) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /feature/home/src/main/java/com/salihkinali/feature/home/TotalGameUiData.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | data class TotalGameUiData( 4 | val id: Int, 5 | val name: String, 6 | val rating: Double, 7 | val released: String, 8 | val backgroundImage: String, 9 | ) 10 | -------------------------------------------------------------------------------- /feature/home/src/main/java/com/salihkinali/feature/home/TotalGameUiMapperImpl.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | import com.salihkinali.domain.entity.TotalGameEntity 4 | import com.salihkinali.common.mapper.GameListMapper 5 | import javax.inject.Inject 6 | 7 | class TotalGameUiMapperImpl @Inject constructor() : 8 | GameListMapper { 9 | override fun map(input: List?): List { 10 | return input?.map { 11 | TotalGameUiData( 12 | id = it.id, 13 | name = it.name, 14 | rating = it.rating, 15 | released = it.released, 16 | backgroundImage = it.backgroundImage 17 | ) 18 | } ?: emptyList() 19 | } 20 | } -------------------------------------------------------------------------------- /feature/home/src/main/java/com/salihkinali/feature/home/di/MapperModule.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home.di 2 | 3 | import com.salihkinali.common.mapper.GameListMapper 4 | import com.salihkinali.domain.entity.TotalGameEntity 5 | import com.salihkinali.feature.home.TotalGameUiData 6 | import com.salihkinali.feature.home.TotalGameUiMapperImpl 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.components.ViewModelComponent 11 | import dagger.hilt.android.scopes.ViewModelScoped 12 | 13 | @Module 14 | @InstallIn(ViewModelComponent::class) 15 | abstract class MapperModule { 16 | 17 | @Binds 18 | @ViewModelScoped 19 | abstract fun bindTotalGameUiData(totalGameUiDataImpl: TotalGameUiMapperImpl): GameListMapper 20 | 21 | } -------------------------------------------------------------------------------- /feature/home/src/main/res/layout/adapter_list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 17 | 18 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 47 | 48 | 56 | 57 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /feature/home/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /feature/home/src/test/java/com/salihkinali/feature/home/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.feature.home 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 05 21:44:46 TRT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /navigation/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /navigation/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'androidx.navigation.safeargs.kotlin' 5 | } 6 | 7 | android { 8 | namespace 'com.salihkinali.navigation' 9 | compileSdk 33 10 | 11 | defaultConfig { 12 | minSdk 21 13 | targetSdk 33 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles "consumer-rules.pro" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | kotlinOptions { 30 | jvmTarget = '1.8' 31 | } 32 | 33 | sourceSets { 34 | getByName("main").res.srcDirs( 35 | "$rootDir/navigation/src/main/sharedRes", "build/generated/source/navigation-args" 36 | ) 37 | } 38 | } 39 | 40 | dependencies { 41 | 42 | implementation 'androidx.core:core-ktx:1.7.0' 43 | implementation 'androidx.appcompat:appcompat:1.6.0' 44 | implementation 'com.google.android.material:material:1.8.0' 45 | testImplementation 'junit:junit:4.13.2' 46 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 47 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 48 | 49 | api "androidx.navigation:navigation-fragment-ktx:2.5.3" 50 | api "androidx.navigation:navigation-ui-ktx:2.5.3" 51 | } -------------------------------------------------------------------------------- /navigation/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/navigation/consumer-rules.pro -------------------------------------------------------------------------------- /navigation/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /navigation/src/androidTest/java/com/salihkinali/navigation/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.navigation 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.salihkinali.navigation.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /navigation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /navigation/src/main/sharedRes/navigation/nav_graph.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 17 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /navigation/src/test/java/com/salihkinali/navigation/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.salihkinali.navigation 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /previews/android-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/android-test.png -------------------------------------------------------------------------------- /previews/clean_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/clean_arch.png -------------------------------------------------------------------------------- /previews/clean_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/clean_architecture.png -------------------------------------------------------------------------------- /previews/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/layers.png -------------------------------------------------------------------------------- /previews/screenshot_four.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/screenshot_four.png -------------------------------------------------------------------------------- /previews/screenshot_one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/screenshot_one.png -------------------------------------------------------------------------------- /previews/screenshot_three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/screenshot_three.png -------------------------------------------------------------------------------- /previews/screenshot_two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/screenshot_two.png -------------------------------------------------------------------------------- /previews/usecase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/salihkinali/GameGuide/bb9d0e7ef4df4192481274f5ffcbfeb55a746405/previews/usecase.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "Game Guide" 16 | include ':app' 17 | include ':core' 18 | include ':core:data' 19 | include ':core:domain' 20 | include ':core:common' 21 | include ':core:ui' 22 | include ':feature' 23 | include ':feature:home' 24 | include ':feature:detail' 25 | include ':navigation' 26 | --------------------------------------------------------------------------------