diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..a3bbfaed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+*.iml
+.gradle
+/local.properties
+.idea/caches
+.idea/libraries
+.idea/modules.xml
+.idea/workspace.xml
+.idea/navEditor.xml
+.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..45b56541
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..79ee123c
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 00000000..cc5dc671
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..f5c6d9eb
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 00000000..7f68460d
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 00000000..3570881a
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,210 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 00000000..ac43f7d1
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,54 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion Versions.compileSdk
+ defaultConfig {
+ applicationId ApplicationId.id
+ minSdkVersion Versions.minSdk
+ targetSdkVersion Versions.targetSdk
+ versionCode Releases.versionCode
+ versionName Releases.versionName
+ multiDexEnabled true
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ }
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+}
+
+dependencies {
+ implementation project(Modules.network)
+ implementation project(Modules.navigation)
+ implementation project(Modules.presentation)
+ implementation project(Modules.extensions)
+ implementation project(Modules.getLocation)
+ implementation project(Modules.showWeather)
+
+ implementation Libraries.kotlin
+ implementation SupportLibraries.design
+ implementation SupportLibraries.cardview
+ implementation SupportLibraries.appcompat
+ implementation SupportLibraries.recyclerview
+ implementation SupportLibraries.constraintLayout
+
+ implementation Libraries.koinAndroid
+ implementation Libraries.koinViewModel
+ implementation Libraries.navigation
+ implementation Libraries.navigationFrag
+
+ implementation Libraries.lifecycleExtensions
+
+ implementation Libraries.rxjava
+ implementation Libraries.rxkotlin
+
+ implementation Libraries.moshiConverter
+ debugImplementation Libraries.leakCanaryAndroid
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/app/src/androidTest/java/com/russellmorris/weatherwatcher/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/russellmorris/weatherwatcher/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..57662c69
--- /dev/null
+++ b/app/src/androidTest/java/com/russellmorris/weatherwatcher/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.russellmorris.weatherwatcher
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.russellmorris.weatherwatcher", appContext.packageName)
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..39d785cf
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/russellmorris/weatherwatcher/App.kt b/app/src/main/java/com/russellmorris/weatherwatcher/App.kt
new file mode 100644
index 00000000..914588fc
--- /dev/null
+++ b/app/src/main/java/com/russellmorris/weatherwatcher/App.kt
@@ -0,0 +1,13 @@
+package com.russellmorris.weatherwatcher
+
+import android.app.Application
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.context.startKoin
+
+class App : Application() {
+
+ override fun onCreate() {
+ super.onCreate()
+ startKoin { androidContext(this@App) }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/russellmorris/weatherwatcher/MainActivity.kt b/app/src/main/java/com/russellmorris/weatherwatcher/MainActivity.kt
new file mode 100644
index 00000000..5d662f06
--- /dev/null
+++ b/app/src/main/java/com/russellmorris/weatherwatcher/MainActivity.kt
@@ -0,0 +1,33 @@
+package com.russellmorris.weatherwatcher
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.navigation.NavController
+import androidx.navigation.findNavController
+import androidx.navigation.ui.AppBarConfiguration
+import androidx.navigation.ui.navigateUp
+import androidx.navigation.ui.setupActionBarWithNavController
+import com.russellmorris.weatherwatch.R
+
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var navController: NavController
+ private lateinit var appBarConfiguration: AppBarConfiguration
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ setSupportActionBar(findViewById(R.id.toolbar))
+ configureNavController()
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
+ }
+
+ private fun configureNavController() {
+ navController = findNavController(R.id.nav_host_fragment)
+ appBarConfiguration = AppBarConfiguration(navController.graph)
+ setupActionBarWithNavController(navController, appBarConfiguration)
+ }
+}
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 00000000..1f6bb290
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 00000000..0d025f9b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..c962e5a7
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 00000000..eca70cfe
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..898f3ed5
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..dffca360
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..64ba76f7
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..dae5e082
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..e5ed4659
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..14ed0af3
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b0907cac
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..d8ae0315
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..2c18de9e
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..beed3cdd
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..69b22338
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..c948718f
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Weather Watch
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 00000000..0eb88fe3
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/test/java/com/russellmorris/weatherwatcher/ExampleUnitTest.kt b/app/src/test/java/com/russellmorris/weatherwatcher/ExampleUnitTest.kt
new file mode 100644
index 00000000..a2c738ca
--- /dev/null
+++ b/app/src/test/java/com/russellmorris/weatherwatcher/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.russellmorris.weatherwatcher
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..b09b0127
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,25 @@
+buildscript {
+
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:${Versions.gradle}"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}"
+ classpath "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.safeArgs}" }
+}
+
+allprojects {
+
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+task x(type: GradleBuild) { tasks = ["lintDebug", "testDebugUnitTest"] }
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 00000000..a53bb063
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,9 @@
+import org.gradle.kotlin.dsl.`kotlin-dsl`
+
+plugins {
+ `kotlin-dsl`
+}
+
+repositories {
+ jcenter()
+}
\ No newline at end of file
diff --git a/buildSrc/buildSrc.iml b/buildSrc/buildSrc.iml
new file mode 100644
index 00000000..cee05450
--- /dev/null
+++ b/buildSrc/buildSrc.iml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt
new file mode 100644
index 00000000..bc80af91
--- /dev/null
+++ b/buildSrc/src/main/java/Dependencies.kt
@@ -0,0 +1,121 @@
+object ApplicationId {
+ val id = "com.russellmorris.weather"
+}
+
+object Modules {
+ val app = ":app"
+ val navigation = ":navigation"
+
+ val network = ":common:network"
+ val presentation = ":common:presentation"
+ val extensions = ":common:extensions"
+ val locationProvider = ":common:location"
+
+ val getLocation = ":features:getlocation"
+ val showWeather = ":features:showweather"
+
+}
+
+object Releases {
+ val versionCode = 1
+ val versionName = "1.0"
+}
+
+object API {
+ val weatherServiceKey = "" //TODO: DO NOT COMMIT TO GITHUB!!
+ val weatherServiceEndpoint = "https://worksample-api.herokuapp.com/"
+
+ val playServicesPlacesKey = "" //TODO: DO NOT COMMIT TO GITHUB!!
+}
+
+object Versions {
+ val gradle = "3.5.3"
+
+ val compileSdk = 28
+ val minSdk = 21
+ val targetSdk = 28
+
+ val appcompat = "1.0.2"
+ val design = "1.0.0"
+ val cardview = "1.0.0"
+ val recyclerview = "1.0.0"
+ val constraintLayout = "1.1.3"
+ val playServices = "17.0.0"
+ val places = "1.1.0"
+ val legacySupport = "1.0.0"
+
+ val ktx = "1.0.0-alpha1"
+
+ val kotlin = "1.3.61"
+ val timber = "4.7.1"
+ val rxjava = "2.2.10"
+ val rxkotlin = "2.3.0"
+ val retrofit = "2.6.0"
+ val loggingInterceptor = "4.0.0"
+ val moshi = "1.8.0"
+ val lifecycle = "2.0.0"
+ val leakCanary = "2.0-alpha-2"
+ val koin = "2.0.0-beta-1"
+
+ val nav = "2.0.0"
+ val safeArgs = "2.1.0-alpha01"
+
+ val junit = "4.12"
+ val assertjCore = "3.12.2"
+ val mockitoKotlin = "2.1.0"
+ val mockitoInline = "3.0.0"
+ val espressoCore = "3.2.0-alpha02"
+}
+
+object Libraries {
+ val kotlin = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}"
+
+ val ktx = "androidx.core:core-ktx:${Versions.ktx}"
+
+ val timber = "com.jakewharton.timber:timber:${Versions.timber}"
+
+ val rxjava = "io.reactivex.rxjava2:rxjava:${Versions.rxjava}"
+ val rxkotlin = "io.reactivex.rxjava2:rxkotlin:${Versions.rxkotlin}"
+
+ val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
+ val rxjavaAdapter = "com.squareup.retrofit2:adapter-rxjava2:${Versions.retrofit}"
+ val moshiConverter = "com.squareup.retrofit2:converter-moshi:${Versions.retrofit}"
+ val loggingInterceptor =
+ "com.squareup.okhttp3:logging-interceptor:${Versions.loggingInterceptor}"
+
+ val moshi = "com.squareup.moshi:moshi:${Versions.moshi}"
+
+ val lifecycleExtensions = "androidx.lifecycle:lifecycle-extensions:${Versions.lifecycle}"
+ val lifecycleCompiler = "androidx.lifecycle:lifecycle-compiler:${Versions.lifecycle}"
+ val lifecycleViewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}"
+
+ val leakCanaryAndroid = "com.squareup.leakcanary:leakcanary-android:${Versions.leakCanary}"
+
+ val koinAndroid = "org.koin:koin-android:${Versions.koin}"
+ val koinViewModel = "org.koin:koin-androidx-viewmodel:${Versions.koin}"
+
+ val navigation = "androidx.navigation:navigation-ui-ktx:${Versions.nav}"
+ val navigationFrag = "androidx.navigation:navigation-fragment-ktx:${Versions.nav}"
+ val playServicesLocation = "com.google.android.gms:play-services-location:${Versions.playServices}"
+ val places = "com.google.android.libraries.places:places:${Versions.places}"
+}
+
+object SupportLibraries {
+ val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
+ val design = "com.google.android.material:material:${Versions.design}"
+ val cardview = "androidx.cardview:cardview:${Versions.cardview}"
+ val recyclerview = "androidx.recyclerview:recyclerview:${Versions.recyclerview}"
+ val constraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.constraintLayout}"
+ val legacySupport = "androidx.legacy:legacy-support-v4:${Versions.legacySupport}"
+}
+
+
+object TestLibraries {
+ val junit = "junit:junit:${Versions.junit}"
+ val assertjCore = "org.assertj:assertj-core:${Versions.assertjCore}"
+ val mockitoKotlin = "com.nhaarman.mockitokotlin2:mockito-kotlin:${Versions.mockitoKotlin}"
+ val mockitoInline = "org.mockito:mockito-inline:${Versions.mockitoInline}"
+ val lifecycleTesting = "androidx.arch.core:core-testing:${Versions.lifecycle}"
+ val espresso = "androidx.test.espresso:espresso-core:${Versions.espressoCore}"
+ val espressoContrib = "androidx.test.espresso:espresso-contrib:${Versions.espressoCore}"
+}
diff --git a/common-android-feature.gradle b/common-android-feature.gradle
new file mode 100644
index 00000000..3aed6647
--- /dev/null
+++ b/common-android-feature.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion Versions.compileSdk
+
+ defaultConfig {
+ minSdkVersion Versions.minSdk
+ targetSdkVersion Versions.targetSdk
+ }
+}
+
+dependencies {
+ implementation project(Modules.app)
+ implementation Libraries.kotlin
+}
diff --git a/common-android-library.gradle b/common-android-library.gradle
new file mode 100644
index 00000000..7e7497fe
--- /dev/null
+++ b/common-android-library.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion Versions.compileSdk
+
+ defaultConfig {
+ minSdkVersion Versions.minSdk
+ targetSdkVersion Versions.targetSdk
+ versionCode Releases.versionCode
+ versionName Releases.versionName
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ }
+ }
+}
+
+dependencies {
+ implementation Libraries.kotlin
+
+ testImplementation TestLibraries.junit
+ testImplementation TestLibraries.assertjCore
+ testImplementation TestLibraries.mockitoKotlin
+ testImplementation TestLibraries.mockitoInline
+ testImplementation TestLibraries.lifecycleTesting
+ testImplementation TestLibraries.espresso
+ testImplementation TestLibraries.espressoContrib
+
+ androidTestImplementation TestLibraries.junit
+ androidTestImplementation TestLibraries.assertjCore
+ androidTestImplementation TestLibraries.mockitoKotlin
+ androidTestImplementation TestLibraries.mockitoInline
+ androidTestImplementation TestLibraries.lifecycleTesting
+ androidTestImplementation TestLibraries.espresso
+ androidTestImplementation TestLibraries.espressoContrib
+}
diff --git a/common-kotlin-library.gradle b/common-kotlin-library.gradle
new file mode 100644
index 00000000..348c766a
--- /dev/null
+++ b/common-kotlin-library.gradle
@@ -0,0 +1,5 @@
+apply plugin: 'kotlin'
+
+dependencies {
+ implementation Libraries.kotlin
+}
diff --git a/common/common.iml b/common/common.iml
new file mode 100644
index 00000000..47259575
--- /dev/null
+++ b/common/common.iml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/extensions/.gitignore b/common/extensions/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/common/extensions/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/common/extensions/build.gradle b/common/extensions/build.gradle
new file mode 100644
index 00000000..90607b60
--- /dev/null
+++ b/common/extensions/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'kotlin-kapt'
+apply from: "$rootDir/common-android-library.gradle"
+
+dependencies {
+ implementation Libraries.lifecycleExtensions
+ kapt Libraries.lifecycleCompiler
+ testImplementation TestLibraries.lifecycleTesting
+}
diff --git a/common/extensions/extensions.iml b/common/extensions/extensions.iml
new file mode 100644
index 00000000..5faaf950
--- /dev/null
+++ b/common/extensions/extensions.iml
@@ -0,0 +1,214 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/extensions/src/main/AndroidManifest.xml b/common/extensions/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..37f8d1d3
--- /dev/null
+++ b/common/extensions/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/common/extensions/src/main/java/com/russellmorris/extensions/Extensions.kt b/common/extensions/src/main/java/com/russellmorris/extensions/Extensions.kt
new file mode 100644
index 00000000..bbf93a86
--- /dev/null
+++ b/common/extensions/src/main/java/com/russellmorris/extensions/Extensions.kt
@@ -0,0 +1,25 @@
+package com.russellmorris.extensions
+
+import java.math.BigDecimal
+import java.math.RoundingMode
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun String.parseUtcDate(): String {
+ val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
+ val formatter = SimpleDateFormat("E, MMM dd yyyy", Locale.getDefault())
+ formatter.timeZone = TimeZone.getTimeZone("UTC")
+ return formatter.format(parser.parse(this))
+}
+
+fun Long.convertToTime(timeZone: Int): String {
+ val date = Date((this + timeZone) * 1000)
+ val format = SimpleDateFormat("HH:mm", Locale.getDefault())
+ return format.format(date)
+}
+
+fun Double.convertToSingleDecimal(): String {
+ return BigDecimal(this * MS_TO_KPH).setScale(1, RoundingMode.HALF_EVEN).toString()
+}
+
+const val MS_TO_KPH = 3.6
diff --git a/common/extensions/src/main/java/com/russellmorris/extensions/LiveDataExtensions.kt b/common/extensions/src/main/java/com/russellmorris/extensions/LiveDataExtensions.kt
new file mode 100644
index 00000000..3d9d6885
--- /dev/null
+++ b/common/extensions/src/main/java/com/russellmorris/extensions/LiveDataExtensions.kt
@@ -0,0 +1,12 @@
+package com.russellmorris.extensions
+
+import androidx.lifecycle.MutableLiveData
+
+fun MutableLiveData>.setSuccess(data: T) =
+ postValue(Resource(ResourceState.SUCCESS, data))
+
+fun MutableLiveData>.setLoading() =
+ postValue(Resource(ResourceState.LOADING, value?.data))
+
+fun MutableLiveData>.setError(message: String? = null) =
+ postValue(Resource(ResourceState.ERROR, value?.data, message))
diff --git a/common/extensions/src/main/java/com/russellmorris/extensions/Resource.kt b/common/extensions/src/main/java/com/russellmorris/extensions/Resource.kt
new file mode 100644
index 00000000..6b69dff5
--- /dev/null
+++ b/common/extensions/src/main/java/com/russellmorris/extensions/Resource.kt
@@ -0,0 +1,7 @@
+package com.russellmorris.extensions
+
+data class Resource constructor(
+ val state: ResourceState,
+ val data: T? = null,
+ val message: String? = null
+)
diff --git a/common/extensions/src/main/java/com/russellmorris/extensions/ResourceState.kt b/common/extensions/src/main/java/com/russellmorris/extensions/ResourceState.kt
new file mode 100644
index 00000000..a5ccdf83
--- /dev/null
+++ b/common/extensions/src/main/java/com/russellmorris/extensions/ResourceState.kt
@@ -0,0 +1,7 @@
+package com.russellmorris.extensions
+
+sealed class ResourceState {
+ object LOADING : ResourceState()
+ object SUCCESS : ResourceState()
+ object ERROR : ResourceState()
+}
diff --git a/common/extensions/src/main/java/com/russellmorris/extensions/ViewExtensions.kt b/common/extensions/src/main/java/com/russellmorris/extensions/ViewExtensions.kt
new file mode 100644
index 00000000..fafc3468
--- /dev/null
+++ b/common/extensions/src/main/java/com/russellmorris/extensions/ViewExtensions.kt
@@ -0,0 +1,29 @@
+package com.russellmorris.extensions
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+
+fun View.visible() {
+ visibility = View.VISIBLE
+}
+
+fun View.invisible() {
+ visibility = View.INVISIBLE
+}
+
+fun View.gone() {
+ visibility = View.GONE
+}
+
+fun SwipeRefreshLayout.startRefreshing() {
+ isRefreshing = true
+}
+
+fun SwipeRefreshLayout.stopRefreshing() {
+ isRefreshing = false
+}
+
+fun ViewGroup.inflate(layoutId: Int, attachToRoot: Boolean = false): View =
+ LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
diff --git a/common/extensions/src/test/java/com/russellmorris/extensions/LiveDataExtensionsTest.kt b/common/extensions/src/test/java/com/russellmorris/extensions/LiveDataExtensionsTest.kt
new file mode 100644
index 00000000..628ee539
--- /dev/null
+++ b/common/extensions/src/test/java/com/russellmorris/extensions/LiveDataExtensionsTest.kt
@@ -0,0 +1,91 @@
+package com.russellmorris.extensions
+
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.lifecycle.MutableLiveData
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+
+class LiveDataExtensionsTest {
+
+ private lateinit var liveData: MutableLiveData>
+
+ private val data = 1
+ private val errorMessage = "Error"
+
+ @Rule
+ @JvmField
+ val instantTaskExecutorRule: TestRule = InstantTaskExecutorRule()
+
+ @Before
+ fun setUp() {
+ liveData = MutableLiveData()
+ }
+
+ @Test
+ fun `loading to success`() {
+ // given
+ liveData.setLoading()
+
+ // when
+ liveData.setSuccess(data)
+
+ // then
+ assertEquals(Resource(ResourceState.SUCCESS, data, null), liveData.value)
+ }
+
+ @Test
+ fun `loading to success to loading`() {
+ // given
+ liveData.setLoading()
+ liveData.setSuccess(data)
+
+ // when
+ liveData.setLoading()
+
+ // then
+ assertEquals(Resource(ResourceState.LOADING, data, null), liveData.value)
+ }
+
+ @Test
+ fun `loading to success to loading to error`() {
+ // given
+ liveData.setLoading()
+ liveData.setSuccess(data)
+ liveData.setLoading()
+
+ // when
+ liveData.setError(errorMessage)
+
+ // then
+ assertEquals(Resource(ResourceState.ERROR, data, errorMessage), liveData.value)
+ }
+
+ @Test
+ fun `loading to error`() {
+ // given
+ liveData.setLoading()
+
+ // when
+ liveData.setError(errorMessage)
+
+ // then
+ assertEquals(Resource(ResourceState.ERROR, null, errorMessage), liveData.value)
+ }
+
+ @Test
+ fun `loading to error to loading to success`() {
+ // given
+ liveData.setLoading()
+ liveData.setError(errorMessage)
+ liveData.setLoading()
+
+ // when
+ liveData.setSuccess(data)
+
+ // then
+ assertEquals(Resource(ResourceState.SUCCESS, data, null), liveData.value)
+ }
+}
diff --git a/common/location/.gitignore b/common/location/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/common/location/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/common/location/build.gradle b/common/location/build.gradle
new file mode 100644
index 00000000..45d00587
--- /dev/null
+++ b/common/location/build.gradle
@@ -0,0 +1,5 @@
+apply from: "$rootDir/common-android-library.gradle"
+
+dependencies {
+ implementation Libraries.playServicesLocation
+}
\ No newline at end of file
diff --git a/common/location/consumer-rules.pro b/common/location/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/common/location/proguard-rules.pro b/common/location/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/common/location/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/common/location/src/main/AndroidManifest.xml b/common/location/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..90426dce
--- /dev/null
+++ b/common/location/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/common/location/src/main/java/com/russellmorris/location/LocationProvider.kt b/common/location/src/main/java/com/russellmorris/location/LocationProvider.kt
new file mode 100644
index 00000000..33dc7f81
--- /dev/null
+++ b/common/location/src/main/java/com/russellmorris/location/LocationProvider.kt
@@ -0,0 +1,8 @@
+package com.russellmorris.location
+
+import android.app.Activity
+
+interface LocationProvider {
+ fun getLocation(activity: Activity)
+ fun initialiseLocationClient()
+}
\ No newline at end of file
diff --git a/common/location/src/main/java/com/russellmorris/location/LocationProviderImpl.kt b/common/location/src/main/java/com/russellmorris/location/LocationProviderImpl.kt
new file mode 100644
index 00000000..1a304397
--- /dev/null
+++ b/common/location/src/main/java/com/russellmorris/location/LocationProviderImpl.kt
@@ -0,0 +1,170 @@
+package com.russellmorris.location
+
+import android.Manifest
+import android.app.Activity
+import android.content.Context
+import android.content.IntentSender.SendIntentException
+import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.location.Location
+import android.location.LocationManager
+import android.os.Looper
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import com.google.android.gms.common.ConnectionResult
+import com.google.android.gms.common.GoogleApiAvailability
+import com.google.android.gms.common.api.ApiException
+import com.google.android.gms.common.api.ResolvableApiException
+import com.google.android.gms.location.*
+import com.google.android.gms.tasks.Task
+
+class LocationProviderImpl (private val context: Context,
+ private val locationResultListener: LocationResultListener) : LocationProvider {
+ private val fusedLocationProviderClient: FusedLocationProviderClient
+ = LocationServices.getFusedLocationProviderClient(context)
+ private val locationCallback: LocationCallback =
+ object : LocationCallback() {
+ override fun onLocationResult(locationResult: LocationResult) {
+ super.onLocationResult(locationResult)
+ locationResultListener.setLocation(locationResult.lastLocation)
+ }
+ }
+ private val locationRequest: LocationRequest = LocationRequest.create()
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
+ .setInterval(LOCATION_REQUEST_INTERVAL.toLong())
+ .setFastestInterval(LOCATION_REQUEST_FASTEST_INTERVAL.toLong())
+ private val locationManager: LocationManager =
+ context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
+ private val sharedPreferences: SharedPreferences =
+ context.getSharedPreferences(LOCATION_PERMISSION_PREFS, Context.MODE_PRIVATE)
+ private val sharedPreferencesEditor = sharedPreferences.edit()
+
+ override fun initialiseLocationClient() {
+ fusedLocationProviderClient.removeLocationUpdates(locationCallback)
+ }
+
+ override fun getLocation(activity: Activity) {
+ if (!isGooglePlayServicesAvailable(activity)) {
+ return
+ }
+ if (!isPermissionGranted) {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(activity, FINE_LOCATION) &&
+ ActivityCompat.shouldShowRequestPermissionRationale(activity, COARSE_LOCATION)) {
+ requestPermission(activity)
+ } else {
+ if (isFirstTimeAskingForPermissions(permissions)) {
+ firstLocationPermissionRequest(FINE_LOCATION)
+ firstLocationPermissionRequest(COARSE_LOCATION)
+ requestPermission(activity)
+ } else {
+ locationResultListener.locationPermissionPreviouslyDeniedWithNeverAskAgain()
+ }
+ }
+ return
+ }
+ if (!isLocationEnabled) {
+ promptUserToEnableLocation(activity)
+ return
+ }
+ lastKnownLocation
+ }
+
+ private val isLocationEnabled: Boolean
+ get() = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) &&
+ locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
+
+ private val isPermissionGranted: Boolean
+ get() = ContextCompat.checkSelfPermission(context, FINE_LOCATION) == GRANTED &&
+ ContextCompat.checkSelfPermission(context, COARSE_LOCATION) == GRANTED
+
+
+ private fun promptUserToEnableLocation(activiy: Activity) {
+ val builder =
+ LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
+ builder.setAlwaysShow(true)
+ LocationServices
+ .getSettingsClient(activiy)
+ .checkLocationSettings(builder.build())
+ .addOnSuccessListener { _: LocationSettingsResponse? -> lastKnownLocation }
+ .addOnFailureListener { e: Exception ->
+ val status = (e as ApiException).statusCode
+ if (status == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) {
+ try {
+ val resolvableApiException =
+ e as ResolvableApiException
+ resolvableApiException.startResolutionForResult(
+ activiy,
+ LOCATION_REQUEST_CODE
+ )
+ } catch (exception: SendIntentException) {
+ exception.printStackTrace()
+ }
+ }
+ }
+ }
+
+ private fun isFirstTimeAskingForPermissions(permissions: Array): Boolean {
+ return sharedPreferences.getBoolean(permissions[0], true) &&
+ sharedPreferences.getBoolean(permissions[1], true)
+ }
+
+ private val lastKnownLocation: Unit
+ get() { fusedLocationProviderClient.lastLocation.addOnCompleteListener { locationTask: Task ->
+ val location = locationTask.result
+ if (location == null) {
+ fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
+ } else {
+ locationResultListener.setLocation(location)
+ }
+ }
+ }
+
+ fun requestPermission(activiy: Activity) {
+ ActivityCompat.requestPermissions(
+ activiy,
+ permissions,
+ LOCATION_REQUEST_CODE
+ )
+ }
+
+ private fun firstLocationPermissionRequest(permission: String) {
+ sharedPreferencesEditor.putBoolean(permission, false)
+ commitPrefs()
+ }
+
+ private fun commitPrefs() {
+ sharedPreferencesEditor.commit()
+ }
+
+ private fun isGooglePlayServicesAvailable(activiy: Activity): Boolean {
+ val googleApiAvailability = GoogleApiAvailability.getInstance()
+ val status = googleApiAvailability.isGooglePlayServicesAvailable(context)
+ if (status != ConnectionResult.SUCCESS) {
+ if (googleApiAvailability.isUserResolvableError(status)) {
+ googleApiAvailability.getErrorDialog(
+ activiy,
+ status,
+ GOOGLE_API_ERROR_CODE
+ ).show()
+ }
+ return false
+ }
+ return true
+ }
+
+ companion object {
+ private const val LOCATION_REQUEST_CODE = 1000
+ private const val GOOGLE_API_ERROR_CODE = 1001
+ private const val LOCATION_REQUEST_INTERVAL = 0
+ private const val LOCATION_REQUEST_FASTEST_INTERVAL = 0
+
+ private const val FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION
+ private const val COARSE_LOCATION =
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ private const val GRANTED = PackageManager.PERMISSION_GRANTED
+ private const val LOCATION_PERMISSION_PREFS = "location_prefs"
+ private val permissions =
+ arrayOf(FINE_LOCATION, COARSE_LOCATION)
+ }
+
+}
\ No newline at end of file
diff --git a/common/location/src/main/java/com/russellmorris/location/LocationResultListener.kt b/common/location/src/main/java/com/russellmorris/location/LocationResultListener.kt
new file mode 100644
index 00000000..f7d0360e
--- /dev/null
+++ b/common/location/src/main/java/com/russellmorris/location/LocationResultListener.kt
@@ -0,0 +1,8 @@
+package com.russellmorris.location
+
+import android.location.Location
+
+interface LocationResultListener {
+ fun locationPermissionPreviouslyDeniedWithNeverAskAgain()
+ fun setLocation(location: Location)
+}
\ No newline at end of file
diff --git a/common/location/src/main/res/values/strings.xml b/common/location/src/main/res/values/strings.xml
new file mode 100644
index 00000000..97dcc0c2
--- /dev/null
+++ b/common/location/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Location
+
diff --git a/common/location/src/test/java/com/russellmorris/location/ExampleUnitTest.kt b/common/location/src/test/java/com/russellmorris/location/ExampleUnitTest.kt
new file mode 100644
index 00000000..3d93c337
--- /dev/null
+++ b/common/location/src/test/java/com/russellmorris/location/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.russellmorris.location
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/common/network/.gitignore b/common/network/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/common/network/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/common/network/build.gradle b/common/network/build.gradle
new file mode 100644
index 00000000..073d6c79
--- /dev/null
+++ b/common/network/build.gradle
@@ -0,0 +1,11 @@
+apply from: "$rootDir/common-kotlin-library.gradle"
+
+dependencies {
+ implementation Libraries.rxjava
+ implementation Libraries.rxkotlin
+
+ implementation Libraries.retrofit
+ implementation Libraries.moshiConverter
+ implementation Libraries.loggingInterceptor
+ implementation Libraries.rxjavaAdapter
+}
diff --git a/common/network/network.iml b/common/network/network.iml
new file mode 100644
index 00000000..3b36b277
--- /dev/null
+++ b/common/network/network.iml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/network/src/main/java/com/russellmorris/network/Network.kt b/common/network/src/main/java/com/russellmorris/network/Network.kt
new file mode 100644
index 00000000..03be59b2
--- /dev/null
+++ b/common/network/src/main/java/com/russellmorris/network/Network.kt
@@ -0,0 +1,28 @@
+package com.russellmorris.network
+
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
+import retrofit2.converter.moshi.MoshiConverterFactory
+
+fun createNetworkClient(baseUrl: String, debug: Boolean = false) =
+ retrofitClient(baseUrl, httpClient(debug))
+
+private fun httpClient(debug: Boolean): OkHttpClient {
+ val httpLoggingInterceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger.DEFAULT)
+ val clientBuilder = OkHttpClient.Builder()
+ if (debug) {
+ httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
+ clientBuilder.addInterceptor(httpLoggingInterceptor)
+ }
+ return clientBuilder.build()
+}
+
+private fun retrofitClient(baseUrl: String, httpClient: OkHttpClient): Retrofit =
+ Retrofit.Builder()
+ .baseUrl(baseUrl)
+ .client(httpClient)
+ .addConverterFactory(MoshiConverterFactory.create())
+ .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
+ .build()
\ No newline at end of file
diff --git a/common/presentation/.gitignore b/common/presentation/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/common/presentation/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/common/presentation/build.gradle b/common/presentation/build.gradle
new file mode 100644
index 00000000..fa9d66ac
--- /dev/null
+++ b/common/presentation/build.gradle
@@ -0,0 +1,8 @@
+apply from: "$rootDir/common-android-library.gradle"
+
+dependencies {
+ implementation SupportLibraries.appcompat
+ implementation Libraries.navigation
+ implementation Libraries.navigationFrag
+ implementation project(Modules.navigation)
+}
diff --git a/common/presentation/presentation.iml b/common/presentation/presentation.iml
new file mode 100644
index 00000000..a9aeb30f
--- /dev/null
+++ b/common/presentation/presentation.iml
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/common/presentation/src/main/AndroidManifest.xml b/common/presentation/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..37f8d1d3
--- /dev/null
+++ b/common/presentation/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/common/presentation/src/main/java/com/russellmorris/presentation/base/BaseFragment.kt b/common/presentation/src/main/java/com/russellmorris/presentation/base/BaseFragment.kt
new file mode 100644
index 00000000..b82b5377
--- /dev/null
+++ b/common/presentation/src/main/java/com/russellmorris/presentation/base/BaseFragment.kt
@@ -0,0 +1,38 @@
+package com.russellmorris.presentation.base
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.Observer
+import androidx.navigation.fragment.FragmentNavigator
+import androidx.navigation.fragment.FragmentNavigatorExtras
+import androidx.navigation.fragment.findNavController
+import com.google.android.material.snackbar.Snackbar
+import com.russellmorris.navigation.NavigationCommand
+import com.russellmorris.presentation.extension.setupSnackbar
+
+abstract class BaseFragment : Fragment() {
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ observeNavigation(getViewModel())
+ setupSnackbar(this, getViewModel().snackBarError, Snackbar.LENGTH_LONG)
+ }
+
+ abstract fun getViewModel(): BaseViewModel
+
+ private fun observeNavigation(viewModel: BaseViewModel) {
+ viewModel.navigation.observe(viewLifecycleOwner, Observer {
+ it?.getContentIfNotHandled()?.let { command ->
+ when (command) {
+ is NavigationCommand.To -> findNavController().navigate(
+ command.directions,
+ getExtras()
+ )
+ is NavigationCommand.Back -> findNavController().navigateUp()
+ }
+ }
+ })
+ }
+
+ open fun getExtras(): FragmentNavigator.Extras = FragmentNavigatorExtras()
+}
\ No newline at end of file
diff --git a/common/presentation/src/main/java/com/russellmorris/presentation/base/BaseViewModel.kt b/common/presentation/src/main/java/com/russellmorris/presentation/base/BaseViewModel.kt
new file mode 100644
index 00000000..322a5cc9
--- /dev/null
+++ b/common/presentation/src/main/java/com/russellmorris/presentation/base/BaseViewModel.kt
@@ -0,0 +1,21 @@
+package com.russellmorris.presentation.base
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.navigation.NavDirections
+import com.russellmorris.navigation.NavigationCommand
+import com.russellmorris.presentation.utils.Event
+
+abstract class BaseViewModel : ViewModel() {
+
+ protected val _snackbarError = MutableLiveData>()
+ val snackBarError: LiveData> get() = _snackbarError
+
+ private val _navigation = MutableLiveData>()
+ val navigation: LiveData> = _navigation
+
+ fun navigate(directions: NavDirections) {
+ _navigation.value = Event(NavigationCommand.To(directions))
+ }
+}
\ No newline at end of file
diff --git a/common/presentation/src/main/java/com/russellmorris/presentation/extension/ViewExt.kt b/common/presentation/src/main/java/com/russellmorris/presentation/extension/ViewExt.kt
new file mode 100644
index 00000000..0d558fd3
--- /dev/null
+++ b/common/presentation/src/main/java/com/russellmorris/presentation/extension/ViewExt.kt
@@ -0,0 +1,31 @@
+package com.russellmorris.presentation.extension
+
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import com.google.android.material.snackbar.Snackbar
+import com.russellmorris.presentation.utils.Event
+
+fun Fragment.showSnackbar(snackbarText: String, timeLength: Int) {
+ activity?.let {
+ Snackbar.make(
+ it.findViewById(android.R.id.content),
+ snackbarText,
+ timeLength
+ ).show()
+ }
+}
+
+fun Fragment.setupSnackbar(
+ lifecycleOwner: LifecycleOwner,
+ snackbarEvent: LiveData>,
+ timeLength: Int
+) {
+ snackbarEvent.observe(lifecycleOwner, Observer { event ->
+ event.getContentIfNotHandled()?.let { res ->
+ context?.let { showSnackbar(it.getString(res), timeLength) }
+ }
+ })
+}
\ No newline at end of file
diff --git a/common/presentation/src/main/java/com/russellmorris/presentation/utils/Event.kt b/common/presentation/src/main/java/com/russellmorris/presentation/utils/Event.kt
new file mode 100644
index 00000000..213304e5
--- /dev/null
+++ b/common/presentation/src/main/java/com/russellmorris/presentation/utils/Event.kt
@@ -0,0 +1,23 @@
+package com.russellmorris.presentation.utils
+
+open class Event(private val content: T) {
+
+ private var hasBeenHandled = false
+
+ /**
+ * Returns the content and prevents its use again.
+ */
+ fun getContentIfNotHandled(): T? {
+ return if (hasBeenHandled) {
+ null
+ } else {
+ hasBeenHandled = true
+ content
+ }
+ }
+
+ /**
+ * Returns the content, even if it's already been handled.
+ */
+ fun peekContent(): T = content
+}
\ No newline at end of file
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_01d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_01d.webp
new file mode 100644
index 00000000..933b205f
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_01d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_01n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_01n.webp
new file mode 100644
index 00000000..6b6a59c6
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_01n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_02d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_02d.webp
new file mode 100644
index 00000000..8f55aab4
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_02d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_02n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_02n.webp
new file mode 100644
index 00000000..a11698e7
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_02n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_03d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_03d.webp
new file mode 100644
index 00000000..eb0c23f5
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_03d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_03n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_03n.webp
new file mode 100644
index 00000000..8d2f1135
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_03n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_04d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_04d.webp
new file mode 100644
index 00000000..2d590efa
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_04d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_04n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_04n.webp
new file mode 100644
index 00000000..6b47675d
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_04n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_09d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_09d.webp
new file mode 100644
index 00000000..b7b3d65e
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_09d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_09n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_09n.webp
new file mode 100644
index 00000000..567e05fb
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_09n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_10d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_10d.webp
new file mode 100644
index 00000000..b7b3d65e
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_10d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_10n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_10n.webp
new file mode 100644
index 00000000..567e05fb
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_10n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_11d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_11d.webp
new file mode 100644
index 00000000..1a9b1253
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_11d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_11n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_11n.webp
new file mode 100644
index 00000000..6a427af2
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_11n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_13d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_13d.webp
new file mode 100644
index 00000000..66b93d98
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_13d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_13n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_13n.webp
new file mode 100644
index 00000000..f66a57be
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_13n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_50d.webp b/common/presentation/src/main/res/drawable-hdpi/bg_50d.webp
new file mode 100644
index 00000000..ebf3ff31
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_50d.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_50n.webp b/common/presentation/src/main/res/drawable-hdpi/bg_50n.webp
new file mode 100644
index 00000000..474228b6
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_50n.webp differ
diff --git a/common/presentation/src/main/res/drawable-hdpi/bg_splash.webp b/common/presentation/src/main/res/drawable-hdpi/bg_splash.webp
new file mode 100644
index 00000000..b485fdca
Binary files /dev/null and b/common/presentation/src/main/res/drawable-hdpi/bg_splash.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_01d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_01d.webp
new file mode 100644
index 00000000..f8244715
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_01d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_01n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_01n.webp
new file mode 100644
index 00000000..a28b4b7f
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_01n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_02d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_02d.webp
new file mode 100644
index 00000000..85d0d89b
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_02d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_02n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_02n.webp
new file mode 100644
index 00000000..5de62ea5
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_02n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_03d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_03d.webp
new file mode 100644
index 00000000..807e7689
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_03d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_03n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_03n.webp
new file mode 100644
index 00000000..a556014c
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_03n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_04d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_04d.webp
new file mode 100644
index 00000000..0c6c2f75
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_04d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_04n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_04n.webp
new file mode 100644
index 00000000..516af2f9
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_04n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_09d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_09d.webp
new file mode 100644
index 00000000..7e7aa864
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_09d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_09n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_09n.webp
new file mode 100644
index 00000000..8a92a6e0
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_09n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_10d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_10d.webp
new file mode 100644
index 00000000..7e7aa864
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_10d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_10n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_10n.webp
new file mode 100644
index 00000000..8a92a6e0
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_10n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_11d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_11d.webp
new file mode 100644
index 00000000..37e80615
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_11d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_11n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_11n.webp
new file mode 100644
index 00000000..47285cdc
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_11n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_13d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_13d.webp
new file mode 100644
index 00000000..9e6365b1
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_13d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_13n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_13n.webp
new file mode 100644
index 00000000..0eb2b866
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_13n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_50d.webp b/common/presentation/src/main/res/drawable-mdpi/bg_50d.webp
new file mode 100644
index 00000000..822e7286
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_50d.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_50n.webp b/common/presentation/src/main/res/drawable-mdpi/bg_50n.webp
new file mode 100644
index 00000000..0b7007b8
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_50n.webp differ
diff --git a/common/presentation/src/main/res/drawable-mdpi/bg_splash.webp b/common/presentation/src/main/res/drawable-mdpi/bg_splash.webp
new file mode 100644
index 00000000..0163de3d
Binary files /dev/null and b/common/presentation/src/main/res/drawable-mdpi/bg_splash.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_01d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_01d.webp
new file mode 100644
index 00000000..d2664fad
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_01d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_01n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_01n.webp
new file mode 100644
index 00000000..7f99cc77
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_01n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_02d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_02d.webp
new file mode 100644
index 00000000..495e8460
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_02d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_02n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_02n.webp
new file mode 100644
index 00000000..d903e404
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_02n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_03d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_03d.webp
new file mode 100644
index 00000000..b0a37788
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_03d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_03n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_03n.webp
new file mode 100644
index 00000000..70e31b44
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_03n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_04d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_04d.webp
new file mode 100644
index 00000000..6aadd6d3
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_04d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_04n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_04n.webp
new file mode 100644
index 00000000..2b33c869
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_04n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_09d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_09d.webp
new file mode 100644
index 00000000..627fcf0c
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_09d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_09n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_09n.webp
new file mode 100644
index 00000000..e10d2b7b
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_09n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_10d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_10d.webp
new file mode 100644
index 00000000..627fcf0c
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_10d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_10n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_10n.webp
new file mode 100644
index 00000000..e10d2b7b
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_10n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_11d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_11d.webp
new file mode 100644
index 00000000..c24691f3
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_11d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_11n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_11n.webp
new file mode 100644
index 00000000..ffe091ff
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_11n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_13d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_13d.webp
new file mode 100644
index 00000000..0fba5a8e
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_13d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_13n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_13n.webp
new file mode 100644
index 00000000..771ff572
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_13n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_50d.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_50d.webp
new file mode 100644
index 00000000..48243d64
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_50d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_50n.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_50n.webp
new file mode 100644
index 00000000..8af0e44a
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_50n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xhdpi/bg_splash.webp b/common/presentation/src/main/res/drawable-xhdpi/bg_splash.webp
new file mode 100644
index 00000000..f38d435d
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xhdpi/bg_splash.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_01d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_01d.webp
new file mode 100644
index 00000000..ef905dea
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_01d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_01n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_01n.webp
new file mode 100644
index 00000000..0db414ff
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_01n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_02d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_02d.webp
new file mode 100644
index 00000000..614b5933
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_02d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_02n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_02n.webp
new file mode 100644
index 00000000..89c4075e
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_02n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_03d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_03d.webp
new file mode 100644
index 00000000..b9c0b22e
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_03d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_03n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_03n.webp
new file mode 100644
index 00000000..41b89b9b
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_03n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_04d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_04d.webp
new file mode 100644
index 00000000..4305bda5
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_04d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_04n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_04n.webp
new file mode 100644
index 00000000..d6caa253
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_04n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_09d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_09d.webp
new file mode 100644
index 00000000..3171829e
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_09d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_09n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_09n.webp
new file mode 100644
index 00000000..f38b60ef
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_09n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_10d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_10d.webp
new file mode 100644
index 00000000..3171829e
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_10d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_10n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_10n.webp
new file mode 100644
index 00000000..f38b60ef
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_10n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_11d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_11d.webp
new file mode 100644
index 00000000..9205174a
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_11d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_11n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_11n.webp
new file mode 100644
index 00000000..9b237c93
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_11n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_13d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_13d.webp
new file mode 100644
index 00000000..a7b73d14
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_13d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_13n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_13n.webp
new file mode 100644
index 00000000..f003eea1
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_13n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_50d.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_50d.webp
new file mode 100644
index 00000000..999974bd
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_50d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_50n.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_50n.webp
new file mode 100644
index 00000000..083598a5
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_50n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxhdpi/bg_splash.webp b/common/presentation/src/main/res/drawable-xxhdpi/bg_splash.webp
new file mode 100644
index 00000000..f3647537
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxhdpi/bg_splash.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_01d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_01d.webp
new file mode 100644
index 00000000..279f2686
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_01d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_01n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_01n.webp
new file mode 100644
index 00000000..83d871a2
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_01n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_02d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_02d.webp
new file mode 100644
index 00000000..1d734265
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_02d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_02n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_02n.webp
new file mode 100644
index 00000000..9c7499f0
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_02n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_03d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_03d.webp
new file mode 100644
index 00000000..557be5ee
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_03d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_03n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_03n.webp
new file mode 100644
index 00000000..9cf81454
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_03n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_04d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_04d.webp
new file mode 100644
index 00000000..c2a5ae47
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_04d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_04n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_04n.webp
new file mode 100644
index 00000000..6bbadb54
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_04n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_09d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_09d.webp
new file mode 100644
index 00000000..01b520f3
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_09d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_09n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_09n.webp
new file mode 100644
index 00000000..a6c26715
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_09n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_10d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_10d.webp
new file mode 100644
index 00000000..01b520f3
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_10d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_10n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_10n.webp
new file mode 100644
index 00000000..a6c26715
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_10n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_11d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_11d.webp
new file mode 100644
index 00000000..e59fa2f7
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_11d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_11n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_11n.webp
new file mode 100644
index 00000000..45886123
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_11n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_13d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_13d.webp
new file mode 100644
index 00000000..1e364f16
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_13d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_13n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_13n.webp
new file mode 100644
index 00000000..52593c85
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_13n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_50d.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_50d.webp
new file mode 100644
index 00000000..d62e8a23
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_50d.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_50n.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_50n.webp
new file mode 100644
index 00000000..b17c6438
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_50n.webp differ
diff --git a/common/presentation/src/main/res/drawable-xxxhdpi/bg_splash.webp b/common/presentation/src/main/res/drawable-xxxhdpi/bg_splash.webp
new file mode 100644
index 00000000..083c8fb9
Binary files /dev/null and b/common/presentation/src/main/res/drawable-xxxhdpi/bg_splash.webp differ
diff --git a/common/presentation/src/main/res/values/strings.xml b/common/presentation/src/main/res/values/strings.xml
new file mode 100644
index 00000000..088096d0
--- /dev/null
+++ b/common/presentation/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ presentation
+
diff --git a/features/features.iml b/features/features.iml
new file mode 100644
index 00000000..e69126e8
--- /dev/null
+++ b/features/features.iml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/features/getlocation/.gitignore b/features/getlocation/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/features/getlocation/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/features/getlocation/build.gradle b/features/getlocation/build.gradle
new file mode 100644
index 00000000..099041b9
--- /dev/null
+++ b/features/getlocation/build.gradle
@@ -0,0 +1,46 @@
+apply from: "$rootDir/common-android-library.gradle"
+apply plugin: "androidx.navigation.safeargs.kotlin"
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+
+ buildTypes {
+ release {
+ buildConfigField "String", "PLACES_API_KEY", "\"${API.playServicesPlacesKey}\""
+ }
+ debug {
+ buildConfigField "String", "PLACES_API_KEY", "\"${API.playServicesPlacesKey}\""
+ }
+ }
+
+ testOptions {
+ animationsDisabled = true
+ }
+ compileOptions {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+ }
+}
+
+dependencies {
+ implementation project(Modules.network)
+ implementation project(Modules.navigation)
+ implementation project(Modules.presentation)
+ implementation project(Modules.extensions)
+ implementation project(Modules.locationProvider)
+
+ implementation SupportLibraries.design
+ implementation SupportLibraries.appcompat
+ implementation SupportLibraries.constraintLayout
+ implementation SupportLibraries.legacySupport
+
+ implementation Libraries.koinAndroid
+ implementation Libraries.koinViewModel
+ implementation Libraries.ktx
+ implementation Libraries.lifecycleExtensions
+ implementation Libraries.lifecycleViewModel
+ implementation Libraries.navigation
+ implementation Libraries.navigationFrag
+ implementation Libraries.places
+}
diff --git a/features/getlocation/consumer-rules.pro b/features/getlocation/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/features/getlocation/proguard-rules.pro b/features/getlocation/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/features/getlocation/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/features/getlocation/src/main/AndroidManifest.xml b/features/getlocation/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..8be82359
--- /dev/null
+++ b/features/getlocation/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/features/getlocation/src/main/java/com/russellmorris/getlocation/Modules.kt b/features/getlocation/src/main/java/com/russellmorris/getlocation/Modules.kt
new file mode 100644
index 00000000..0b3a43b5
--- /dev/null
+++ b/features/getlocation/src/main/java/com/russellmorris/getlocation/Modules.kt
@@ -0,0 +1,26 @@
+package com.russellmorris.getlocation
+
+import com.russellmorris.getlocation.ui.viewmodel.GetLocationViewModel
+import com.russellmorris.location.LocationProvider
+import com.russellmorris.location.LocationProviderImpl
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.core.context.loadKoinModules
+import org.koin.core.module.Module
+import org.koin.dsl.module
+
+fun injectFeature() = loadFeature
+
+private val loadFeature by lazy {
+ loadKoinModules(
+ viewModelModule,
+ locationProviderModule
+ )
+}
+
+val viewModelModule: Module = module {
+ viewModel { GetLocationViewModel() }
+}
+
+val locationProviderModule: Module = module {
+ single { LocationProviderImpl(context = get(), locationResultListener = get()) as LocationProvider }
+}
\ No newline at end of file
diff --git a/features/getlocation/src/main/java/com/russellmorris/getlocation/ui/view/GetLocationFragment.kt b/features/getlocation/src/main/java/com/russellmorris/getlocation/ui/view/GetLocationFragment.kt
new file mode 100644
index 00000000..ee250639
--- /dev/null
+++ b/features/getlocation/src/main/java/com/russellmorris/getlocation/ui/view/GetLocationFragment.kt
@@ -0,0 +1,105 @@
+package com.russellmorris.getlocation.ui.view
+
+import android.content.Intent
+import android.location.Location
+import android.net.Uri
+import android.os.Bundle
+import android.provider.Settings
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.navigation.fragment.findNavController
+import com.google.android.gms.common.api.Status
+import com.google.android.libraries.places.api.Places
+import com.google.android.libraries.places.api.model.Place
+import com.google.android.libraries.places.api.model.TypeFilter
+import com.google.android.libraries.places.widget.AutocompleteSupportFragment
+import com.google.android.libraries.places.widget.listener.PlaceSelectionListener
+import com.russellmorris.getlocation.BuildConfig
+import com.russellmorris.getlocation.R
+import com.russellmorris.getlocation.injectFeature
+import com.russellmorris.getlocation.ui.viewmodel.GetLocationViewModel
+import com.russellmorris.location.LocationProvider
+import com.russellmorris.location.LocationProviderImpl
+import com.russellmorris.location.LocationResultListener
+import com.russellmorris.presentation.base.BaseFragment
+import com.russellmorris.presentation.base.BaseViewModel
+import kotlinx.android.synthetic.main.get_location_fragment.*
+import org.koin.androidx.viewmodel.ext.viewModel
+
+class GetLocationFragment : BaseFragment(), LocationResultListener {
+
+ private val getLocationViewModel: GetLocationViewModel by viewModel()
+ lateinit var locationProvider: LocationProvider
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ locationProvider = LocationProviderImpl(requireContext(), this)
+ locationProvider.initialiseLocationClient()
+ if (!Places.isInitialized()) {
+ Places.initialize(requireContext(), BuildConfig.PLACES_API_KEY)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.get_location_fragment, container, false)
+ }
+
+ override fun getViewModel(): BaseViewModel = getLocationViewModel
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ injectFeature()
+ locationPermissionButton.setOnClickListener{_ -> activity?.let {
+ locationProvider.getLocation(it)
+ } }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ setUpAutoComplete()
+ }
+
+ override fun locationPermissionPreviouslyDeniedWithNeverAskAgain() {
+ val settingsIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ val settingUri = Uri.fromParts("package", activity?.packageName, null)
+ settingsIntent.setData(settingUri)
+ startActivity(settingsIntent)
+ }
+
+ override fun setLocation(location: Location) {
+ launchNextView(location.latitude, location.longitude)
+ }
+
+ private fun setUpAutoComplete() {
+ val autocompleteSupportFragment = childFragmentManager.findFragmentById(R.id.placeAutoComplete) as AutocompleteSupportFragment
+ autocompleteSupportFragment.setPlaceFields(arrayListOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG))
+ autocompleteSupportFragment.setTypeFilter(TypeFilter.CITIES)
+ autocompleteSupportFragment.setHint(getString(R.string.seach_for_a_location))
+ autocompleteSupportFragment.setOnPlaceSelectedListener(object:PlaceSelectionListener{
+ override fun onPlaceSelected(place: Place) {
+ launchNextView(place.latLng?.latitude, place.latLng?.longitude)
+ }
+
+ override fun onError(status: Status) {
+ getLocationViewModel.snackBarError
+ }
+
+ })
+ }
+
+ private fun launchNextView(latitude: Double?, longitude: Double?) {
+ if (findNavController().currentDestination?.id == R.id.locationFragment) {
+ getLocationViewModel.navigate(
+ GetLocationFragmentDirections.actionLaunchesFragmentToDetailFragment(
+ latitude.toString(), longitude.toString()
+ )
+ )
+ }
+ }
+
+}
diff --git a/features/getlocation/src/main/java/com/russellmorris/getlocation/ui/viewmodel/GetLocationViewModel.kt b/features/getlocation/src/main/java/com/russellmorris/getlocation/ui/viewmodel/GetLocationViewModel.kt
new file mode 100644
index 00000000..c95e279d
--- /dev/null
+++ b/features/getlocation/src/main/java/com/russellmorris/getlocation/ui/viewmodel/GetLocationViewModel.kt
@@ -0,0 +1,5 @@
+package com.russellmorris.getlocation.ui.viewmodel
+
+import com.russellmorris.presentation.base.BaseViewModel
+
+class GetLocationViewModel : BaseViewModel()
\ No newline at end of file
diff --git a/features/getlocation/src/main/res/drawable/ic_search_black_24dp.xml b/features/getlocation/src/main/res/drawable/ic_search_black_24dp.xml
new file mode 100644
index 00000000..affc7ba2
--- /dev/null
+++ b/features/getlocation/src/main/res/drawable/ic_search_black_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/getlocation/src/main/res/layout/get_location_fragment.xml b/features/getlocation/src/main/res/layout/get_location_fragment.xml
new file mode 100644
index 00000000..7a07f93f
--- /dev/null
+++ b/features/getlocation/src/main/res/layout/get_location_fragment.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/features/getlocation/src/main/res/values/strings.xml b/features/getlocation/src/main/res/values/strings.xml
new file mode 100644
index 00000000..db00e64e
--- /dev/null
+++ b/features/getlocation/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+ Get Location
+ Search for a location
+ or
+ Use current location
+
diff --git a/features/getlocation/src/test/java/com/russellmorris/getlocation/ExampleUnitTest.kt b/features/getlocation/src/test/java/com/russellmorris/getlocation/ExampleUnitTest.kt
new file mode 100644
index 00000000..0fade91e
--- /dev/null
+++ b/features/getlocation/src/test/java/com/russellmorris/getlocation/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.russellmorris.getlocation
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/features/showweather/.gitignore b/features/showweather/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/features/showweather/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/features/showweather/build.gradle b/features/showweather/build.gradle
new file mode 100644
index 00000000..cd40489f
--- /dev/null
+++ b/features/showweather/build.gradle
@@ -0,0 +1,57 @@
+apply from: "$rootDir/common-android-library.gradle"
+apply plugin: "androidx.navigation.safeargs.kotlin"
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+
+ buildTypes {
+ release {
+ buildConfigField "String", "WEATHER_ENDPOINT", "\"${API.weatherServiceEndpoint}\""
+ buildConfigField "String", "WEATHER_API_KEY", "\"${API.weatherServiceKey}\""
+ }
+ debug {
+ buildConfigField "String", "WEATHER_ENDPOINT", "\"${API.weatherServiceEndpoint}\""
+ buildConfigField "String", "WEATHER_API_KEY", "\"${API.weatherServiceKey}\""
+ }
+ }
+
+ testOptions {
+ animationsDisabled = true
+ }
+ compileOptions {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+ }
+}
+
+dependencies {
+ implementation project(Modules.network)
+ implementation project(Modules.navigation)
+ implementation project(Modules.presentation)
+ implementation project(Modules.extensions)
+
+ implementation SupportLibraries.design
+ implementation SupportLibraries.cardview
+ implementation SupportLibraries.appcompat
+ implementation SupportLibraries.constraintLayout
+ implementation SupportLibraries.legacySupport
+
+ implementation Libraries.koinAndroid
+ implementation Libraries.koinViewModel
+ implementation Libraries.ktx
+ implementation Libraries.lifecycleExtensions
+ implementation Libraries.lifecycleViewModel
+ implementation Libraries.rxjava
+ implementation Libraries.rxkotlin
+ implementation Libraries.moshi
+ implementation Libraries.moshiConverter
+ implementation Libraries.navigation
+ implementation Libraries.navigationFrag
+
+ testImplementation TestLibraries.junit
+ testImplementation TestLibraries.assertjCore
+ testImplementation TestLibraries.mockitoKotlin
+ testImplementation TestLibraries.mockitoInline
+ testImplementation TestLibraries.lifecycleTesting
+}
diff --git a/features/showweather/consumer-rules.pro b/features/showweather/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/features/showweather/proguard-rules.pro b/features/showweather/proguard-rules.pro
new file mode 100644
index 00000000..f1b42451
--- /dev/null
+++ b/features/showweather/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/features/showweather/src/androidTest/java/com/russellmorris/showweather/ExampleInstrumentedTest.kt b/features/showweather/src/androidTest/java/com/russellmorris/showweather/ExampleInstrumentedTest.kt
new file mode 100644
index 00000000..f580b78d
--- /dev/null
+++ b/features/showweather/src/androidTest/java/com/russellmorris/showweather/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.russellmorris.showweather
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.russellmorris.showweather.test", appContext.packageName)
+ }
+}
diff --git a/features/showweather/src/main/AndroidManifest.xml b/features/showweather/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..d14183b1
--- /dev/null
+++ b/features/showweather/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/Modules.kt b/features/showweather/src/main/java/com/russellmorris/showweather/Modules.kt
new file mode 100644
index 00000000..f84a9310
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/Modules.kt
@@ -0,0 +1,53 @@
+package com.russellmorris.showweather
+
+import com.russellmorris.showweather.data.repository.WeatherRepositoryImpl
+import com.russellmorris.showweather.data.source.WeatherDataSource
+import com.russellmorris.showweather.domain.repository.WeatherRepository
+import com.russellmorris.showweather.domain.usecase.WeatherUseCase
+import com.russellmorris.showweather.service.api.WeatherApi
+import com.russellmorris.showweather.service.api.WeatherApiService
+import com.russellmorris.network.createNetworkClient
+import com.russellmorris.showweather.ui.viewmodel.ShowWeatherViewModel
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.core.context.loadKoinModules
+import org.koin.core.module.Module
+import org.koin.dsl.module
+import retrofit2.Retrofit
+
+fun injectFeature() = loadFeature
+
+private val loadFeature by lazy {
+ loadKoinModules(
+ viewModelModule,
+ useCaseModule,
+ repositoryModule,
+ dataSourceModule,
+ networkModule
+ )
+}
+
+val viewModelModule: Module = module {
+ viewModel { ShowWeatherViewModel(weatherUseCase = get()) }
+}
+
+val useCaseModule: Module = module {
+ factory { WeatherUseCase(weatherRepository = get()) }
+}
+
+val repositoryModule: Module = module {
+ single { WeatherRepositoryImpl(weatherDataSource = get()) as WeatherRepository }
+}
+
+val dataSourceModule: Module = module {
+ single { WeatherApiService(api = weatherApi) as WeatherDataSource }
+}
+
+val networkModule: Module = module {
+ single { weatherApi }
+}
+
+private const val BASE_URL = BuildConfig.WEATHER_ENDPOINT
+
+private val retrofit: Retrofit = createNetworkClient(BASE_URL, BuildConfig.DEBUG)
+
+private val weatherApi: WeatherApi = retrofit.create(WeatherApi::class.java)
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/data/repository/WeatherRepositoryImpl.kt b/features/showweather/src/main/java/com/russellmorris/showweather/data/repository/WeatherRepositoryImpl.kt
new file mode 100644
index 00000000..98069ba6
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/data/repository/WeatherRepositoryImpl.kt
@@ -0,0 +1,15 @@
+package com.russellmorris.showweather.data.repository
+
+import com.russellmorris.showweather.data.source.WeatherDataSource
+import com.russellmorris.showweather.domain.entity.WeatherEntity
+import com.russellmorris.showweather.domain.repository.WeatherRepository
+import io.reactivex.Single
+
+class WeatherRepositoryImpl constructor(
+ private val weatherDataSource: WeatherDataSource
+) : WeatherRepository {
+
+ override fun getWeather(lat: String?, lon: String?, key: String, units: String): Single {
+ return weatherDataSource.getWeather(lat, lon, key, units)
+ }
+}
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/data/source/WeatherDataSource.kt b/features/showweather/src/main/java/com/russellmorris/showweather/data/source/WeatherDataSource.kt
new file mode 100644
index 00000000..51117f20
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/data/source/WeatherDataSource.kt
@@ -0,0 +1,8 @@
+package com.russellmorris.showweather.data.source
+
+import com.russellmorris.showweather.domain.entity.WeatherEntity
+import io.reactivex.Single
+
+interface WeatherDataSource {
+ fun getWeather(latitude: String?, longitude: String?, key: String, units: String): Single
+}
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/domain/entity/WeatherEntity.kt b/features/showweather/src/main/java/com/russellmorris/showweather/domain/entity/WeatherEntity.kt
new file mode 100644
index 00000000..a9e1fe35
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/domain/entity/WeatherEntity.kt
@@ -0,0 +1,16 @@
+package com.russellmorris.showweather.domain.entity
+
+data class WeatherEntity (
+ val weatherId: Int,
+ val weatherTitle: String,
+ val weatherDescription: String,
+ val weatherIcon: String,
+ val temp: Double,
+ val feelsLike: Double,
+ val windSpeed: Double,
+ val windDirection: Int,
+ val sunrise: Long,
+ val sunset: Long,
+ val timezone: Int,
+ val city: String
+)
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/domain/repository/WeatherRepository.kt b/features/showweather/src/main/java/com/russellmorris/showweather/domain/repository/WeatherRepository.kt
new file mode 100644
index 00000000..2e94045d
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/domain/repository/WeatherRepository.kt
@@ -0,0 +1,8 @@
+package com.russellmorris.showweather.domain.repository
+
+import com.russellmorris.showweather.domain.entity.WeatherEntity
+import io.reactivex.Single
+
+interface WeatherRepository {
+ fun getWeather(lat: String?, lon: String?, key: String, units: String): Single
+}
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/domain/usecase/WeatherUseCase.kt b/features/showweather/src/main/java/com/russellmorris/showweather/domain/usecase/WeatherUseCase.kt
new file mode 100644
index 00000000..cc859d6b
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/domain/usecase/WeatherUseCase.kt
@@ -0,0 +1,10 @@
+package com.russellmorris.showweather.domain.usecase
+
+import com.russellmorris.showweather.domain.entity.WeatherEntity
+import com.russellmorris.showweather.domain.repository.WeatherRepository
+import io.reactivex.Single
+
+class WeatherUseCase constructor(private val weatherRepository: WeatherRepository) {
+ fun getWeather(lat: String?, lon: String?, key: String, units: String): Single =
+ weatherRepository.getWeather(lat, lon, key, units)
+}
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/service/api/WeatherApi.kt b/features/showweather/src/main/java/com/russellmorris/showweather/service/api/WeatherApi.kt
new file mode 100644
index 00000000..8ff8cb8f
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/service/api/WeatherApi.kt
@@ -0,0 +1,17 @@
+package com.russellmorris.showweather.service.api
+
+import com.russellmorris.showweather.service.dao.WeatherDAO
+import io.reactivex.Single
+import retrofit2.http.GET
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface WeatherApi {
+ @GET("weather?")
+ fun getWeather(
+ @Query("lat") latitude: String?,
+ @Query("lon") longitude: String?,
+ @Query("key") key: String,
+ @Query("units") units: String
+ ) : Single
+}
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/service/api/WeatherApiService.kt b/features/showweather/src/main/java/com/russellmorris/showweather/service/api/WeatherApiService.kt
new file mode 100644
index 00000000..ec05a858
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/service/api/WeatherApiService.kt
@@ -0,0 +1,21 @@
+package com.russellmorris.showweather.service.api
+
+import com.russellmorris.showweather.data.source.WeatherDataSource
+import com.russellmorris.showweather.domain.entity.WeatherEntity
+import com.russellmorris.showweather.service.dao.mapToDomain
+import io.reactivex.Single
+
+class WeatherApiService constructor(
+ private val api: WeatherApi
+) : WeatherDataSource {
+
+ override fun getWeather(
+ latitude: String?,
+ longitude: String?,
+ key: String,
+ units: String
+ ): Single {
+ return api.getWeather(latitude, longitude, key, units)
+ .map { it.mapToDomain() }
+ }
+}
\ No newline at end of file
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/service/dao/WeatherDAO.kt b/features/showweather/src/main/java/com/russellmorris/showweather/service/dao/WeatherDAO.kt
new file mode 100644
index 00000000..17ccdad2
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/service/dao/WeatherDAO.kt
@@ -0,0 +1,51 @@
+package com.russellmorris.showweather.service.dao
+
+import com.russellmorris.showweather.domain.entity.WeatherEntity
+import com.squareup.moshi.Json
+
+data class WeatherDAO(
+ @field:Json(name = "weather") val weatherList: List,
+ @field:Json(name = "main") val main: Main,
+ @field:Json(name = "wind") val wind: Wind,
+ @field:Json(name = "sys") val sys: Sys,
+ @field:Json(name = "timezone") val timezone: Int,
+ @field:Json(name = "name") val name: String
+)
+
+data class Weather(
+ @field:Json(name = "id") val id: Int,
+ @field:Json(name = "main") val main: String,
+ @field:Json(name = "description") val description: String,
+ @field:Json(name = "icon") val icon: String
+)
+
+data class Main(
+ @field:Json(name = "temp") val temp: Double,
+ @field:Json(name = "feels_like") val feelsLike: Double
+)
+
+data class Wind(
+ @field:Json(name = "speed") val speed: Double,
+ @field:Json(name = "deg") val deg: Int
+)
+
+data class Sys(
+ @field:Json(name = "sunrise") val sunrise: Long,
+ @field:Json(name = "sunset") val sunset: Long
+)
+
+fun WeatherDAO.mapToDomain(): WeatherEntity =
+ WeatherEntity(
+ weatherId = weatherList[0].id,
+ weatherTitle = weatherList[0].main,
+ weatherDescription = weatherList[0].description,
+ weatherIcon = weatherList[0].icon,
+ temp = main.temp,
+ feelsLike = main.feelsLike,
+ windSpeed = wind.speed,
+ windDirection = wind.deg,
+ sunrise = sys.sunrise,
+ sunset = sys.sunset,
+ timezone = timezone,
+ city = name)
+
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/ui/model/Weather.kt b/features/showweather/src/main/java/com/russellmorris/showweather/ui/model/Weather.kt
new file mode 100644
index 00000000..ea73f1ed
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/ui/model/Weather.kt
@@ -0,0 +1,22 @@
+package com.russellmorris.showweather.ui.model
+
+import com.russellmorris.showweather.domain.entity.WeatherEntity
+
+data class Weather(
+ val weatherId: Int,
+ val weatherTitle: String,
+ val weatherDescription: String,
+ val weatherIcon: String,
+ val temp: Double,
+ val feelsLike: Double,
+ val windSpeed: Double,
+ val windDirection: Int,
+ val sunrise: Long,
+ val sunset: Long,
+ val timezone: Int,
+ val city: String
+)
+
+fun WeatherEntity.mapToPresentation(): Weather =
+ Weather(weatherId, weatherTitle, weatherDescription, weatherIcon, temp, feelsLike, windSpeed,
+ windDirection, sunrise, sunset, timezone, city)
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/ui/view/ShowWeatherFragment.kt b/features/showweather/src/main/java/com/russellmorris/showweather/ui/view/ShowWeatherFragment.kt
new file mode 100644
index 00000000..2ca68532
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/ui/view/ShowWeatherFragment.kt
@@ -0,0 +1,95 @@
+package com.russellmorris.showweather.ui.view
+
+
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.core.content.ContextCompat.getDrawable
+import androidx.lifecycle.Observer
+import androidx.navigation.fragment.navArgs
+import com.google.android.material.snackbar.Snackbar
+import com.russellmorris.extensions.Resource
+import com.russellmorris.extensions.convertToSingleDecimal
+import com.russellmorris.extensions.convertToTime
+import com.russellmorris.extensions.parseUtcDate
+import com.russellmorris.showweather.R
+import com.russellmorris.showweather.injectFeature
+import com.russellmorris.showweather.ui.viewmodel.ShowWeatherViewModel
+import com.russellmorris.presentation.base.BaseFragment
+import com.russellmorris.presentation.base.BaseViewModel
+import com.russellmorris.showweather.ui.model.Weather
+import kotlinx.android.synthetic.main.fragment_show_weather.*
+import org.koin.androidx.viewmodel.ext.viewModel
+import java.math.BigDecimal
+import java.math.RoundingMode
+
+
+class ShowWeatherFragment : BaseFragment() {
+
+ private val showWeatherViewModel: ShowWeatherViewModel by viewModel()
+ private val args: ShowWeatherFragmentArgs by navArgs()
+
+ override fun getViewModel(): BaseViewModel = showWeatherViewModel
+
+ private val snackBar by lazy {
+ Snackbar.make(weatherDetail, "Error", Snackbar.LENGTH_INDEFINITE)
+ .setAction("Retry") {
+ showWeatherViewModel.getWeatherData(
+ latitude = args.latitude,
+ longitude = args.longitude,
+ units = "metric")
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ // Inflate the layout for this fragment
+ return inflater.inflate(R.layout.fragment_show_weather, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ injectFeature()
+ if (savedInstanceState == null) {
+ showWeatherViewModel.getWeatherData(args.latitude, args.longitude, "metric")
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ showWeatherViewModel.weather.observe(this, Observer { update(it) } )
+ }
+
+ private fun update(resource: Resource?) {
+ resource?.let {
+ it.data?.let {
+ location.text = it.city
+ temp.text = getString(R.string.degrees, it.temp.toInt())
+ windSpeed.text = getString(R.string.km_h, it.windSpeed.convertToSingleDecimal())
+ sunrise.text = it.sunrise.convertToTime(it.timezone)
+ sunset.text = it.sunset.convertToTime(it.timezone)
+ weatherDescription.text = it.weatherDescription
+ weatherIcon.setImageDrawable(getIconDrawable(it.weatherIcon))
+ weatherDetail.background = getbackgroundDrawable(it.weatherIcon)
+ }
+ it.message?.let { snackBar.show() }
+ }
+ }
+
+ private fun getIconDrawable(iconName: String): Drawable? {
+ val drawableName = "ic_$iconName"
+ val resourceId: Int = resources.getIdentifier(drawableName, "drawable", activity?.packageName)
+ return getDrawable(requireContext(), resourceId)
+ }
+
+ private fun getbackgroundDrawable(iconName: String): Drawable? {
+ val drawableName = "bg_$iconName"
+ val resourceId: Int = resources.getIdentifier(drawableName, "drawable", activity?.packageName)
+ return getDrawable(requireContext(), resourceId)
+ }
+}
diff --git a/features/showweather/src/main/java/com/russellmorris/showweather/ui/viewmodel/ShowWeatherViewModel.kt b/features/showweather/src/main/java/com/russellmorris/showweather/ui/viewmodel/ShowWeatherViewModel.kt
new file mode 100644
index 00000000..14505e6c
--- /dev/null
+++ b/features/showweather/src/main/java/com/russellmorris/showweather/ui/viewmodel/ShowWeatherViewModel.kt
@@ -0,0 +1,37 @@
+package com.russellmorris.showweather.ui.viewmodel
+
+import androidx.lifecycle.MutableLiveData
+import com.russellmorris.extensions.Resource
+import com.russellmorris.extensions.setError
+import com.russellmorris.extensions.setLoading
+import com.russellmorris.extensions.setSuccess
+import com.russellmorris.showweather.BuildConfig
+import com.russellmorris.showweather.domain.usecase.WeatherUseCase
+import com.russellmorris.showweather.ui.model.Weather
+import com.russellmorris.showweather.ui.model.mapToPresentation
+import com.russellmorris.presentation.base.BaseViewModel
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.schedulers.Schedulers
+
+class ShowWeatherViewModel constructor(private val weatherUseCase: WeatherUseCase) :
+ BaseViewModel() {
+
+ val weather = MutableLiveData>()
+ private val compositeDisposable = CompositeDisposable()
+
+ fun getWeatherData(latitude: String?, longitude: String?, units: String) =
+ compositeDisposable.add(weatherUseCase.getWeather(latitude, longitude, BuildConfig.WEATHER_API_KEY, units)
+ .doOnSubscribe{ weather.setLoading() }
+ .subscribeOn(Schedulers.io())
+ .map { it.mapToPresentation() }
+ .subscribe(
+ { weather.setSuccess(it)},
+ { weather.setError(it.message)}
+ )
+ )
+
+ override fun onCleared() {
+ compositeDisposable.dispose()
+ super.onCleared()
+ }
+}
\ No newline at end of file
diff --git a/features/showweather/src/main/res/drawable/ic_01d.xml b/features/showweather/src/main/res/drawable/ic_01d.xml
new file mode 100644
index 00000000..838787bf
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_01d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_01n.xml b/features/showweather/src/main/res/drawable/ic_01n.xml
new file mode 100644
index 00000000..2de4fa85
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_01n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_02d.xml b/features/showweather/src/main/res/drawable/ic_02d.xml
new file mode 100644
index 00000000..bff6d42a
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_02d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_02n.xml b/features/showweather/src/main/res/drawable/ic_02n.xml
new file mode 100644
index 00000000..acd7a864
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_02n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_03d.xml b/features/showweather/src/main/res/drawable/ic_03d.xml
new file mode 100644
index 00000000..f9e527cc
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_03d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_03n.xml b/features/showweather/src/main/res/drawable/ic_03n.xml
new file mode 100644
index 00000000..f9e527cc
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_03n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_04d.xml b/features/showweather/src/main/res/drawable/ic_04d.xml
new file mode 100644
index 00000000..7c86114d
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_04d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_04n.xml b/features/showweather/src/main/res/drawable/ic_04n.xml
new file mode 100644
index 00000000..7c86114d
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_04n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_09d.xml b/features/showweather/src/main/res/drawable/ic_09d.xml
new file mode 100644
index 00000000..7ee1496f
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_09d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_09n.xml b/features/showweather/src/main/res/drawable/ic_09n.xml
new file mode 100644
index 00000000..a45af3ef
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_09n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_10d.xml b/features/showweather/src/main/res/drawable/ic_10d.xml
new file mode 100644
index 00000000..3c43e14a
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_10d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_10n.xml b/features/showweather/src/main/res/drawable/ic_10n.xml
new file mode 100644
index 00000000..80643d00
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_10n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_11d.xml b/features/showweather/src/main/res/drawable/ic_11d.xml
new file mode 100644
index 00000000..e84c8e2b
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_11d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_11n.xml b/features/showweather/src/main/res/drawable/ic_11n.xml
new file mode 100644
index 00000000..597fb460
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_11n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_13d.xml b/features/showweather/src/main/res/drawable/ic_13d.xml
new file mode 100644
index 00000000..178231e9
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_13d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_13n.xml b/features/showweather/src/main/res/drawable/ic_13n.xml
new file mode 100644
index 00000000..457db3f7
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_13n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_50d.xml b/features/showweather/src/main/res/drawable/ic_50d.xml
new file mode 100644
index 00000000..d1fdc1bb
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_50d.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_50n.xml b/features/showweather/src/main/res/drawable/ic_50n.xml
new file mode 100644
index 00000000..b6d757e8
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_50n.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_wi_day_sunny.xml b/features/showweather/src/main/res/drawable/ic_wi_day_sunny.xml
new file mode 100644
index 00000000..5eb58d2c
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_wi_day_sunny.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_wi_strong_wind.xml b/features/showweather/src/main/res/drawable/ic_wi_strong_wind.xml
new file mode 100644
index 00000000..3709705a
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_wi_strong_wind.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/features/showweather/src/main/res/drawable/ic_wi_sunrise.xml b/features/showweather/src/main/res/drawable/ic_wi_sunrise.xml
new file mode 100644
index 00000000..dda0a279
--- /dev/null
+++ b/features/showweather/src/main/res/drawable/ic_wi_sunrise.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/features/showweather/src/main/res/layout/fragment_show_weather.xml b/features/showweather/src/main/res/layout/fragment_show_weather.xml
new file mode 100644
index 00000000..c65830e3
--- /dev/null
+++ b/features/showweather/src/main/res/layout/fragment_show_weather.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/features/showweather/src/main/res/values/strings.xml b/features/showweather/src/main/res/values/strings.xml
new file mode 100644
index 00000000..a1597967
--- /dev/null
+++ b/features/showweather/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+ Show Weather
+ %1$s kph
+ %1$d\u00B0C
+
diff --git a/features/showweather/src/test/java/com/russellmorris/showweather/ExampleUnitTest.kt b/features/showweather/src/test/java/com/russellmorris/showweather/ExampleUnitTest.kt
new file mode 100644
index 00000000..db1df4fd
--- /dev/null
+++ b/features/showweather/src/test/java/com/russellmorris/showweather/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.russellmorris.showweather
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..23339e0d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Automatically convert third-party libraries to use AndroidX
+android.enableJetifier=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..f6b961fd
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..d4547706
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Dec 06 21:59:37 GMT 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..cccdd3d5
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..f9553162
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/kotlin_dependencies.gradle b/kotlin_dependencies.gradle
new file mode 100644
index 00000000..2f0be65b
--- /dev/null
+++ b/kotlin_dependencies.gradle
@@ -0,0 +1,5 @@
+apply plugin: 'kotlin'
+
+dependencies {
+ implementation CoreLibraries.kotlin
+}
diff --git a/local.properties b/local.properties
new file mode 100644
index 00000000..5a08496d
--- /dev/null
+++ b/local.properties
@@ -0,0 +1,10 @@
+## This file is automatically generated by Android Studio.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file should *NOT* be checked into Version Control Systems,
+# as it contains information specific to your local configuration.
+#
+# Location of the SDK. This is only used by Gradle.
+# For customization when using a Version Control System, please read the
+# header note.
+sdk.dir=/Users/russ/Library/Android/sdk
diff --git a/navigation/.gitignore b/navigation/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/navigation/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/navigation/build.gradle b/navigation/build.gradle
new file mode 100644
index 00000000..11fe3ec7
--- /dev/null
+++ b/navigation/build.gradle
@@ -0,0 +1,7 @@
+apply from: "$rootDir/common-android-library.gradle"
+apply plugin: "androidx.navigation.safeargs.kotlin"
+
+dependencies {
+ implementation Libraries.navigation
+ implementation Libraries.navigationFrag
+}
\ No newline at end of file
diff --git a/navigation/src/main/AndroidManifest.xml b/navigation/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..bcceacc9
--- /dev/null
+++ b/navigation/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/navigation/src/main/java/com/russellmorris/navigation/NavigationCommand.kt b/navigation/src/main/java/com/russellmorris/navigation/NavigationCommand.kt
new file mode 100644
index 00000000..a033a9e5
--- /dev/null
+++ b/navigation/src/main/java/com/russellmorris/navigation/NavigationCommand.kt
@@ -0,0 +1,11 @@
+package com.russellmorris.navigation
+
+import androidx.navigation.NavDirections
+
+/**
+ * A simple sealed class to aid navigation
+ */
+sealed class NavigationCommand {
+ data class To(val directions: NavDirections) : NavigationCommand()
+ object Back : NavigationCommand()
+}
\ No newline at end of file
diff --git a/navigation/src/main/res/navigation/nav_graph_getlocation_feature.xml b/navigation/src/main/res/navigation/nav_graph_getlocation_feature.xml
new file mode 100644
index 00000000..2ff55afa
--- /dev/null
+++ b/navigation/src/main/res/navigation/nav_graph_getlocation_feature.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/navigation/src/main/res/navigation/nav_graph_weatherdetail_feature.xml b/navigation/src/main/res/navigation/nav_graph_weatherdetail_feature.xml
new file mode 100644
index 00000000..5536087d
--- /dev/null
+++ b/navigation/src/main/res/navigation/nav_graph_weatherdetail_feature.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..4ef93cf9
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include Modules.app, Modules.navigation, Modules.presentation, Modules.network,
+ Modules.extensions, Modules.locationProvider, Modules.getLocation, Modules.showWeather
\ No newline at end of file