├── .gitignore
├── .idea
└── .gitignore
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── ekar
│ │ └── assignment
│ │ └── ExampleInstrumentedTest.kt
│ ├── debug
│ └── res
│ │ └── values
│ │ └── google_maps_api.xml
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── ekar
│ │ │ └── assignment
│ │ │ ├── EkarApp.kt
│ │ │ ├── core
│ │ │ ├── base
│ │ │ │ └── BaseViewModel.kt
│ │ │ ├── domain
│ │ │ │ ├── Mapper.kt
│ │ │ │ └── UseCase.kt
│ │ │ └── network
│ │ │ │ ├── BaseRepository.kt
│ │ │ │ └── RestResult.kt
│ │ │ ├── data
│ │ │ ├── api
│ │ │ │ └── ApiService.kt
│ │ │ ├── mock
│ │ │ │ └── DummyLocationProvider.kt
│ │ │ ├── model
│ │ │ │ ├── request
│ │ │ │ │ └── CarDetailRequest.kt
│ │ │ │ └── response
│ │ │ │ │ └── CarDetailResponse.kt
│ │ │ └── repository
│ │ │ │ └── CarDetailRepository.kt
│ │ │ ├── di
│ │ │ ├── NetworkModule.kt
│ │ │ ├── RepositoryModule.kt
│ │ │ ├── ServiceModule.kt
│ │ │ ├── UseCaseModule.kt
│ │ │ └── coroutine
│ │ │ │ ├── CoroutineModule.kt
│ │ │ │ ├── CoroutineThread.kt
│ │ │ │ └── CoroutineThreadImpl.kt
│ │ │ ├── domain
│ │ │ ├── decider
│ │ │ │ └── CarAttributeDecider.kt
│ │ │ ├── mapper
│ │ │ │ └── CarSpecUIModelMapper.kt
│ │ │ ├── uimodel
│ │ │ │ └── CarDetailUIModel.kt
│ │ │ └── usecase
│ │ │ │ └── GetCarDetail.kt
│ │ │ ├── ui
│ │ │ ├── Screen.kt
│ │ │ ├── activity
│ │ │ │ └── MainActivity.kt
│ │ │ ├── map
│ │ │ │ └── MapScreen.kt
│ │ │ ├── splash
│ │ │ │ ├── SplashScreen.kt
│ │ │ │ └── SplashViewModel.kt
│ │ │ ├── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Padding.kt
│ │ │ │ ├── Shapes.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ └── vehicle
│ │ │ │ ├── VehicleScreen.kt
│ │ │ │ └── VehicleViewModel.kt
│ │ │ └── uicomponent
│ │ │ ├── ButtonType1.kt
│ │ │ ├── ButtonType2.kt
│ │ │ ├── Dropdown.kt
│ │ │ ├── LoadingView.kt
│ │ │ ├── ShowCaseLabelType1.kt
│ │ │ ├── ShowcaseLabelType2.kt
│ │ │ └── map
│ │ │ └── MapUtils.kt
│ └── res
│ │ ├── drawable-v24
│ │ ├── ekar_car.png
│ │ ├── ekar_logo.png
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── layout_map.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
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── style.xml
│ │ └── themes.xml
│ ├── release
│ └── res
│ │ └── values
│ │ └── google_maps_api.xml
│ └── test
│ └── java
│ └── com
│ └── ekar
│ └── assignment
│ └── ExampleUnitTest.kt
├── build.gradle
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ ├── Config.kt
│ ├── Libs.kt
│ └── Versions.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.toptal.com/developers/gitignore/api/android,androidstudio,java,kotlin,macos,windows
3 | # Edit at https://www.toptal.com/developers/gitignore?templates=android,androidstudio,java,kotlin,macos,windows
4 |
5 | ### Android ###
6 | # Built application files
7 | *.apk
8 | *.aar
9 | *.ap_
10 | *.aab
11 |
12 | # Files for the ART/Dalvik VM
13 | *.dex
14 |
15 | # Java class files
16 | *.class
17 |
18 | # Generated files
19 | bin/
20 | gen/
21 | out/
22 | # Uncomment the following line in case you need and you don't have the release build type files in your app
23 | # release/
24 |
25 | # Gradle files
26 | .gradle/
27 | build/
28 |
29 | # Local configuration file (sdk path, etc)
30 | local.properties
31 |
32 | # Proguard folder generated by Eclipse
33 | proguard/
34 |
35 | # Log Files
36 | *.log
37 |
38 | # Android Studio Navigation editor temp files
39 | .navigation/
40 |
41 | # Android Studio captures folder
42 | captures/
43 |
44 | # IntelliJ
45 | *.iml
46 | .idea/workspace.xml
47 | .idea/tasks.xml
48 | .idea/gradle.xml
49 | .idea/assetWizardSettings.xml
50 | .idea/dictionaries
51 | .idea/libraries
52 | # Android Studio 3 in .gitignore file.
53 | .idea/caches
54 | .idea/modules.xml
55 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
56 | .idea/navEditor.xml
57 |
58 | # Keystore files
59 | # Uncomment the following lines if you do not want to check your keystore files in.
60 | #*.jks
61 | #*.keystore
62 |
63 | # External native build folder generated in Android Studio 2.2 and later
64 | .externalNativeBuild
65 | .cxx/
66 |
67 | # Google Services (e.g. APIs or Firebase)
68 | # google-services.json
69 |
70 | # Freeline
71 | freeline.py
72 | freeline/
73 | freeline_project_description.json
74 |
75 | # fastlane
76 | fastlane/report.xml
77 | fastlane/Preview.html
78 | fastlane/screenshots
79 | fastlane/test_output
80 | fastlane/readme.md
81 |
82 | # Version control
83 | vcs.xml
84 |
85 | # lint
86 | lint/intermediates/
87 | lint/generated/
88 | lint/outputs/
89 | lint/tmp/
90 | # lint/reports/
91 |
92 | ### Android Patch ###
93 | gen-external-apklibs
94 | output.json
95 |
96 | # Replacement of .externalNativeBuild directories introduced
97 | # with Android Studio 3.5.
98 |
99 | ### Java ###
100 | # Compiled class file
101 |
102 | # Log file
103 |
104 | # BlueJ files
105 | *.ctxt
106 |
107 | # Mobile Tools for Java (J2ME)
108 | .mtj.tmp/
109 |
110 | # Package Files #
111 | *.jar
112 | *.war
113 | *.nar
114 | *.ear
115 | *.zip
116 | *.tar.gz
117 | *.rar
118 |
119 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
120 | hs_err_pid*
121 |
122 | ### Kotlin ###
123 | # Compiled class file
124 |
125 | # Log file
126 |
127 | # BlueJ files
128 |
129 | # Mobile Tools for Java (J2ME)
130 |
131 | # Package Files #
132 |
133 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
134 |
135 | ### macOS ###
136 | # General
137 | .DS_Store
138 | .AppleDouble
139 | .LSOverride
140 |
141 | # Icon must end with two \r
142 | Icon
143 |
144 |
145 | # Thumbnails
146 | ._*
147 |
148 | # Files that might appear in the root of a volume
149 | .DocumentRevisions-V100
150 | .fseventsd
151 | .Spotlight-V100
152 | .TemporaryItems
153 | .Trashes
154 | .VolumeIcon.icns
155 | .com.apple.timemachine.donotpresent
156 |
157 | # Directories potentially created on remote AFP share
158 | .AppleDB
159 | .AppleDesktop
160 | Network Trash Folder
161 | Temporary Items
162 | .apdisk
163 |
164 | ### Windows ###
165 | # Windows thumbnail cache files
166 | Thumbs.db
167 | Thumbs.db:encryptable
168 | ehthumbs.db
169 | ehthumbs_vista.db
170 |
171 | # Dump file
172 | *.stackdump
173 |
174 | # Folder config file
175 | [Dd]esktop.ini
176 |
177 | # Recycle Bin used on file shares
178 | $RECYCLE.BIN/
179 |
180 | # Windows Installer files
181 | *.cab
182 | *.msi
183 | *.msix
184 | *.msm
185 | *.msp
186 |
187 | # Windows shortcuts
188 | *.lnk
189 |
190 | ### AndroidStudio ###
191 | # Covers files to be ignored for android development using Android Studio.
192 |
193 | # Built application files
194 |
195 | # Files for the ART/Dalvik VM
196 |
197 | # Java class files
198 |
199 | # Generated files
200 |
201 | # Gradle files
202 | .gradle
203 |
204 | # Signing files
205 | .signing/
206 |
207 | # Local configuration file (sdk path, etc)
208 |
209 | # Proguard folder generated by Eclipse
210 |
211 | # Log Files
212 |
213 | # Android Studio
214 | /*/build/
215 | /*/local.properties
216 | /*/out
217 | /*/*/build
218 | /*/*/production
219 | *.ipr
220 | *~
221 | *.swp
222 |
223 | # Keystore files
224 | *.jks
225 | *.keystore
226 |
227 | # Google Services (e.g. APIs or Firebase)
228 | # google-services.json
229 |
230 | # Android Patch
231 |
232 | # External native build folder generated in Android Studio 2.2 and later
233 |
234 | # NDK
235 | obj/
236 |
237 | # IntelliJ IDEA
238 | *.iws
239 | /out/
240 |
241 | # User-specific configurations
242 | .idea/caches/
243 | .idea/libraries/
244 | .idea/shelf/
245 | .idea/.name
246 | .idea/compiler.xml
247 | .idea/copyright/profiles_settings.xml
248 | .idea/encodings.xml
249 | .idea/misc.xml
250 | .idea/scopes/scope_settings.xml
251 | .idea/vcs.xml
252 | .idea/jsLibraryMappings.xml
253 | .idea/datasources.xml
254 | .idea/dataSources.ids
255 | .idea/sqlDataSources.xml
256 | .idea/dynamic.xml
257 | .idea/uiDesigner.xml
258 | .idea/jarRepositories.xml
259 |
260 | # OS-specific files
261 | .DS_Store?
262 |
263 | # Legacy Eclipse project files
264 | .classpath
265 | .project
266 | .cproject
267 | .settings/
268 |
269 | # Mobile Tools for Java (J2ME)
270 |
271 | # Package Files #
272 |
273 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
274 |
275 | ## Plugin-specific files:
276 |
277 | # mpeltonen/sbt-idea plugin
278 | .idea_modules/
279 |
280 | # JIRA plugin
281 | atlassian-ide-plugin.xml
282 |
283 | # Mongo Explorer plugin
284 | .idea/mongoSettings.xml
285 |
286 | # Crashlytics plugin (for Android Studio and IntelliJ)
287 | com_crashlytics_export_strings.xml
288 | crashlytics.properties
289 | crashlytics-build.properties
290 | fabric.properties
291 |
292 | ### AndroidStudio Patch ###
293 |
294 | !/gradle/wrapper/gradle-wrapper.jar
295 |
296 | # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,java,kotlin,macos,windows
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EkarAssignment
2 | Ekar Assignment for Mobile Engineers
3 |
4 | ## UI
5 | App consist of 3 different fragments and 1 root activity. Activity holds a container layout in order to manage fragments which will be controlled by navigation component.
6 |
7 | Fragments :
8 | * SplashScreen
9 | * MapScreen
10 | * VehicleScreen
11 |
12 | ## Screenshots
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## App Flow
21 | #### SplashScreen
22 | App opens with splash screen fragment and navigate to the map screen after showing the ekar logo to the user for 2 seconds.
23 |
24 | #### MapScreen
25 | Map Screen is the main part of the app. In first launch, random markers are placed on the map around Dubai zone. Features is listed below:
26 | * Different colored markers placed in map.
27 | * Click marker to navigate vehicle screen.
28 |
29 | #### VehicleScreen
30 | This screen responsible for showing all details of vehicle such as price, standard seating, booking fee and the other things.
31 |
32 | ## Architecture
33 | This app adopts Clean Architecture behaviour. Here is the package structure:
34 |
35 | #### Core
36 | It is the package that contains all the common and base classes used within the application.
37 | Extensions, deciders, utils and base classes are included in this package.
38 |
39 | #### Data
40 | Data package should include response models, data source and api methods. It shouldn't know any logic.
41 |
42 | #### UI
43 | Ui like a feature. It contains Fragments, view models and feature related classes like a domains, mappers and ui models.
44 | Make sure that all classes here are specific to the this feature. If it is a class that is also used in other features, it should be moved to the common package.
45 |
46 | #### Di
47 | This package may actually be inside the common module. But I prefer to carry outside of core package to be more visible.
48 |
49 | #### Ui-Component
50 | In large projects, we need to use a view component in more than one place. So i moved common view components under ui-component package.
51 |
52 | ## Tech Stack
53 | * [Kotlin](https://kotlinlang.org/) , [Coroutines](https://github.com/Kotlin/kotlinx.coroutines) , [Flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/)
54 | * [Dagger-Hilt](https://developer.android.com/training/dependency-injection/hilt-android) - Dependency Injection
55 | * [MVVM Architecture](https://developer.android.com/jetpack/guide) - Modern, maintainable, and Google suggested app architecture
56 | * [Retrofit2 & OkHttp3](https://github.com/square/retrofit)
57 | * [Gson](https://github.com/google/gson)
58 | * [Navigation Component](https://developer.android.com/guide/navigation) - Single activity multiple fragments approach
59 |
60 |
61 | ## TODOs and Improvements
62 | - UI test.
63 | - Better Design
64 | - Unit tests for different screnios
65 | - Implementation of static code analysis tool(ktlint or detekt)
66 | - Styling definitions for textviews and buttons etc.
67 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | plugins {
3 | id 'com.android.application'
4 | id 'kotlin-android'
5 | id 'kotlin-kapt'
6 | id 'dagger.hilt.android.plugin'
7 | }
8 |
9 | android {
10 | compileSdk Config.compileSdkVersion
11 |
12 | defaultConfig {
13 | applicationId Config.applicationId
14 | minSdk Config.minSdkVersion
15 | targetSdk Config.targetSdkVersion
16 | versionCode Config.versionCode
17 | versionName Config.versionName
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | compileOptions {
29 | sourceCompatibility JavaVersion.VERSION_1_8
30 | targetCompatibility JavaVersion.VERSION_1_8
31 | }
32 |
33 | buildFeatures {
34 | compose true
35 | viewBinding true
36 |
37 | }
38 | composeOptions {
39 | kotlinCompilerExtensionVersion Versions.compose
40 | kotlinCompilerVersion '1.5.21'
41 | }
42 |
43 | kotlinOptions {
44 | jvmTarget = '1.8'
45 | }
46 | }
47 |
48 | dependencies {
49 |
50 | implementation Libs.coreKtx
51 | implementation Libs.appCompat
52 | implementation Libs.material
53 |
54 | // Lifecycle
55 | implementation Libs.lifecycleViewModel
56 | implementation Libs.lifecycleRuntime
57 | implementation Libs.lifecycleLiveData
58 |
59 | //Retrofit & OkHttp
60 | implementation Libs.converter
61 | implementation Libs.retrofit
62 | implementation Libs.okhttp
63 | implementation Libs.interceptor
64 |
65 | //Coroutines
66 | implementation Libs.coroutinesAndroid
67 | implementation Libs.coroutinesCore
68 |
69 | //Compose
70 | implementation Libs.composeUi
71 | implementation Libs.composeMaterial
72 | implementation Libs.composeToolingPreview
73 | implementation Libs.composeActivity
74 | implementation Libs.composeNavigation
75 |
76 | //Map
77 | implementation Libs.mapKtx
78 | implementation Libs.googleMaps
79 |
80 | //Fragment
81 | implementation Libs.fragment
82 |
83 | //Logging
84 | implementation Libs.timber
85 |
86 | //Hilt
87 | implementation Libs.hiltAndroid
88 | kapt Libs.hiltCompiler
89 | implementation Libs.hiltCompose
90 |
91 | testImplementation Libs.junit
92 | androidTestImplementation Libs.junitExt
93 | androidTestImplementation Libs.espresso
94 | }
--------------------------------------------------------------------------------
/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/ekar/assignment/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.ekar.assignment
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.ekar.assignment", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/debug/res/values/google_maps_api.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | google_api_key_here
4 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
12 |
13 |
21 |
22 |
30 |
33 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ekar/assignment/EkarApp.kt:
--------------------------------------------------------------------------------
1 | package com.ekar.assignment
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 | import timber.log.Timber
6 |
7 | /**
8 | * @author yusuf.onder
9 | * Created on 2.01.2022
10 | */
11 | @HiltAndroidApp
12 | class EkarApp : Application() {
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 | setupTimber()
17 | }
18 |
19 | private fun setupTimber(){
20 | if (BuildConfig.DEBUG) {
21 | Timber.plant(Timber.DebugTree())
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ekar/assignment/core/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.ekar.assignment.core.base
2 |
3 | /**
4 | * @author yusuf.onder
5 | * Created on 2.01.2022
6 | */
7 | import androidx.lifecycle.ViewModel
8 | import androidx.lifecycle.viewModelScope
9 | import com.ekar.assignment.core.network.RestResult
10 | import kotlinx.coroutines.flow.Flow
11 | import kotlinx.coroutines.flow.collect
12 | import kotlinx.coroutines.launch
13 |
14 | abstract class BaseViewModel : ViewModel() {
15 |
16 | fun request(
17 | flow: Flow>,
18 | onSuccess: ((data: T) -> Unit)? = null,
19 | onError: ((t: Exception) -> Unit)? = null,
20 | onLoading: (() -> Unit)? = null
21 | ) = viewModelScope.launch {
22 | flow.collect { result ->
23 | when (result) {
24 | is RestResult.Loading -> onLoading?.invoke()
25 | is RestResult.Success -> onSuccess?.invoke(result.data)
26 | is RestResult.Error -> { onError?.invoke(result.exception) }
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ekar/assignment/core/domain/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.ekar.assignment.core.domain
2 |
3 | /**
4 | * @author yusuf.onder
5 | * Created on 2.01.2022
6 | */
7 | interface Mapper {
8 | fun map(input: Input): Output
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ekar/assignment/core/domain/UseCase.kt:
--------------------------------------------------------------------------------
1 | package com.ekar.assignment.core.domain
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | /**
6 | * @author yusuf.onder
7 | * Created on 2.01.2022
8 | */
9 | interface UseCase {
10 | suspend operator fun invoke(input: Input): Flow