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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + + + + + + +