From 21fa2ba0ef7fc8cba487c3e596c841846d1f080b Mon Sep 17 00:00:00 2001 From: yusmle Date: Sat, 25 Jul 2020 23:43:45 +0430 Subject: [PATCH 01/24] Manage dependencies --- .idea/misc.xml | 2 +- app/build.gradle | 165 ++++++++++++++---- build.gradle | 34 +++- buildSrc/build.gradle.kts | 7 + buildSrc/plugin-android-app.gradle.kts | 1 + buildSrc/plugin-android-library.gradle.kts | 1 + buildSrc/plugin-java-library.gradle.kts | 1 + .../com/mydigipay/challenge/Classpaths.kt | 5 + .../kotlin/com/mydigipay/challenge/Configs.kt | 29 +++ .../com/mydigipay/challenge/Dependencies.kt | 82 +++++++++ .../kotlin/com/mydigipay/challenge/Plugins.kt | 10 ++ .../com/mydigipay/challenge/Versions.kt | 47 +++++ gradle/wrapper/gradle-wrapper.properties | 4 +- 13 files changed, 344 insertions(+), 44 deletions(-) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/plugin-android-app.gradle.kts create mode 100644 buildSrc/plugin-android-library.gradle.kts create mode 100644 buildSrc/plugin-java-library.gradle.kts create mode 100644 buildSrc/src/main/kotlin/com/mydigipay/challenge/Classpaths.kt create mode 100644 buildSrc/src/main/kotlin/com/mydigipay/challenge/Configs.kt create mode 100644 buildSrc/src/main/kotlin/com/mydigipay/challenge/Dependencies.kt create mode 100644 buildSrc/src/main/kotlin/com/mydigipay/challenge/Plugins.kt create mode 100644 buildSrc/src/main/kotlin/com/mydigipay/challenge/Versions.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 37a75096..7bfef59d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/build.gradle b/app/build.gradle index c31daba5..32313f82 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,50 +1,141 @@ -apply plugin: 'com.android.application' - -apply plugin: 'kotlin-android' - -apply plugin: 'kotlin-android-extensions' +apply plugin: Plugins.androidApplication +apply plugin: Plugins.kotlinAndroid +apply plugin: Plugins.kotlinAndroidExtensions +apply plugin: Plugins.kotlinKapt android { - compileSdkVersion 29 - buildToolsVersion "29.0.1" + compileSdkVersion Configs.compileSdkVersion + defaultConfig { - applicationId "com.mydigipay.challenge.github" - minSdkVersion 17 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + applicationId Configs.applicationId + minSdkVersion Configs.minSdkVersion + targetSdkVersion Configs.targetSdkVersion + versionCode Configs.versionCode + versionName Configs.versionName + + testInstrumentationRunner Configs.testInstrumentationRunner + multiDexEnabled true } + buildTypes { + debug { + minifyEnabled false + shrinkResources false + //useProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "DigiPay Challenge v${defaultConfig.versionName} - (${variant.buildType.name}).apk" + } + } + } release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + shrinkResources false + //useProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + applicationVariants.all { variant -> + variant.outputs.all { + outputFileName = "DigiPay Challenge v${defaultConfig.versionName}.apk" + } + } } } + + /* This is how to use Java 8 Language Lambda feature. */ + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + /* Specify JVM target of Kotlin to Java v1.8 */ + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } + + /* This is how to use data binding feature of Android Studio. */ + dataBinding { + enabled = true + } } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.0.2' - implementation 'androidx.core:core-ktx:1.0.2' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'com.google.android.material:material:1.0.0' - implementation 'com.squareup.retrofit2:retrofit:2.5.0' - implementation 'com.squareup.okhttp3:okhttp:3.11.0' - implementation 'org.koin:koin-android:2.0.1' - implementation 'org.koin:koin-android-viewmodel:2.0.1' - - - implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1' - implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' - implementation 'androidx.preference:preference-ktx:1.1.0' - implementation 'com.squareup.retrofit2:converter-gson:2.4.0' - implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' - - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + + /* JAR Dependencies */ + implementation fileTree(include: ['*.jar'], dir: 'libs') + + /* Kotlin */ + implementation Dependencies.kotlin + + /* Testing */ + testImplementation Dependencies.jUnit + androidTestImplementation Dependencies.androidXTestRunner + androidTestImplementation Dependencies.androidXTestExt + androidTestImplementation(Dependencies.espessoCore, { + exclude group: Dependencies.excludeGroup, module: Dependencies.excludeModule + }) + + /* AndroidX Support */ + implementation Dependencies.androidXCoreKtx + implementation Dependencies.androidXAppCompat + implementation Dependencies.androidXConstraintLayout + implementation Dependencies.androidXRecyclerView + implementation Dependencies.androidXCardView + implementation Dependencies.androidXSwipeRefreshLayout + implementation Dependencies.googleAndroidMaterial + + /* Jetpack Lifecycle Component */ + implementation Dependencies.lifeCycleExtensions + kapt Dependencies.lifeCycleCommonJava8 + + /* Jetpack Room Component */ + implementation Dependencies.roomRuntime + kapt Dependencies.roomCompiler + androidTestImplementation Dependencies.roomTesting + implementation Dependencies.roomRxJava + + /* Jetpack Preference Component */ + implementation Dependencies.preference + + /* Jetpack Navigation Component */ + implementation Dependencies.navigationFragment + implementation Dependencies.navigationFragmentKtx + implementation Dependencies.navigationUi + implementation Dependencies.navigationUiKtx + implementation Dependencies.navigationDynamicFeaturesFragment + androidTestImplementation Dependencies.navigationTesting + + /* RxJava */ + implementation Dependencies.rxJva + implementation Dependencies.rxAndroid + + /* Kotlin Coroutines */ + implementation Dependencies.kotlinXCoroutinesCore + implementation Dependencies.kotlinXCoroutinesAndroid + + /* Dependency Injection using Dagger2 */ + implementation Dependencies.dagger + implementation Dependencies.daggerAndroidSupport + kapt Dependencies.daggerCompiler + kapt Dependencies.daggerAndroidProcessor + + /* Dependency Injection using Koin */ + implementation Dependencies.koin + implementation Dependencies.koinViewModel + + /* Remote API Call using Retrofit2 */ + implementation Dependencies.retrofit + implementation Dependencies.converterGson + implementation Dependencies.adapterRxJava + implementation Dependencies.okHttp + implementation Dependencies.loggingInterceptor + implementation Dependencies.gson + implementation Dependencies.adapterCoroutines + + /* Other */ + + implementation(Dependencies.glide) { + exclude group: Dependencies.excludeGroup + } + kapt Dependencies.glideCompiler } diff --git a/build.gradle b/build.gradle index eccd7a6e..7220fe0f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,27 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +apply plugin: Plugins.gradleVersions + buildscript { - ext.kotlin_version = '1.3.50' + ext { + kotlin_version = '1.3.72' + } repositories { google() jcenter() - } + dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + // Android Gradle Plugin + classpath Classpaths.androidGradlePlugin + + // Kotlin Gradle Plugin + classpath Classpaths.kotlinGradlePlugin + + // Gradle Versions Plugin + classpath Classpaths.gradleVersionsPlugin classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -19,10 +31,24 @@ allprojects { repositories { google() jcenter() - + maven { + url 'https://maven.google.com' + } + maven { + url 'https://kotlin.bintray.com/kotlinx/' + } + maven { + url 'https://jitpack.io' + } } } task clean(type: Delete) { delete rootProject.buildDir } + +task { + dependencyUpdates { + checkConstraints = true + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..473f8cfb --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + jcenter() +} diff --git a/buildSrc/plugin-android-app.gradle.kts b/buildSrc/plugin-android-app.gradle.kts new file mode 100644 index 00000000..dabf5ef6 --- /dev/null +++ b/buildSrc/plugin-android-app.gradle.kts @@ -0,0 +1 @@ +// TODO implement \ No newline at end of file diff --git a/buildSrc/plugin-android-library.gradle.kts b/buildSrc/plugin-android-library.gradle.kts new file mode 100644 index 00000000..dabf5ef6 --- /dev/null +++ b/buildSrc/plugin-android-library.gradle.kts @@ -0,0 +1 @@ +// TODO implement \ No newline at end of file diff --git a/buildSrc/plugin-java-library.gradle.kts b/buildSrc/plugin-java-library.gradle.kts new file mode 100644 index 00000000..dabf5ef6 --- /dev/null +++ b/buildSrc/plugin-java-library.gradle.kts @@ -0,0 +1 @@ +// TODO implement \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/com/mydigipay/challenge/Classpaths.kt b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Classpaths.kt new file mode 100644 index 00000000..1aeb1775 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Classpaths.kt @@ -0,0 +1,5 @@ +object Classpaths { + val androidGradlePlugin = "com.android.tools.build:gradle:${Versions.androidGradlePlugin}" + val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" + val gradleVersionsPlugin = "com.github.ben-manes:gradle-versions-plugin:${Versions.gradleVersionsPlugin}" +} diff --git a/buildSrc/src/main/kotlin/com/mydigipay/challenge/Configs.kt b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Configs.kt new file mode 100644 index 00000000..166a3ffe --- /dev/null +++ b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Configs.kt @@ -0,0 +1,29 @@ +object Configs { + const val applicationId = "com.mydigipay.challenge" + const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + const val consumerProguardFiles = "consumer-rules.pro" + + const val minSdkVersion = 21 + const val targetSdkVersion = 29 + const val compileSdkVersion = 29 + + const val versionMajor = 1 + const val versionMinor = 0 + const val versionPatch = 0 + const val versionClassifier = "" + + val versionCode = generateVersionCode() + val versionName = generateVersionName() + + private fun generateVersionCode(): Int { + return minSdkVersion * 10000000 + versionMajor * 10000 + versionMinor * 100 + versionPatch + } + + private fun generateVersionName(): String { + var versionName = "${versionMajor}.${versionMinor}.${versionPatch}" + if (versionClassifier != null && versionClassifier.isNotEmpty()) { + versionName = versionName + "-" + versionClassifier + } + return versionName; + } +} diff --git a/buildSrc/src/main/kotlin/com/mydigipay/challenge/Dependencies.kt b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Dependencies.kt new file mode 100644 index 00000000..8cbff8c1 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Dependencies.kt @@ -0,0 +1,82 @@ +object Dependencies { + + /* Kotlin */ + val kotlin = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}" + + /* Testing */ + val jUnit = "junit:junit:${Versions.jUnit}" + val androidXTestRunner = "androidx.test:runner:${Versions.androidXTestRunner}" + val androidXTestExt = "androidx.test.ext:junit:${Versions.androidXTestExt}" + val espessoCore = "androidx.test.espresso:espresso-core:${Versions.espresso}" + + /* AndroidX Support */ + val androidXCoreKtx = "androidx.core:core-ktx:${Versions.androidXKtx}" + val androidXAppCompat = "androidx.appcompat:appcompat:${Versions.androidXAppCompat}" + val androidXConstraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.androidXConstraintLayout}" + val androidXRecyclerView = "androidx.recyclerview:recyclerview:${Versions.androidXRecyclerView}" + val androidXCardView = "androidx.cardview:cardview:${Versions.androidXCardView}" + val androidXSwipeRefreshLayout = "androidx.swiperefreshlayout:swiperefreshlayout:${Versions.androidXSwipeRefreshLayout}" + val googleAndroidMaterial = "com.google.android.material:material:${Versions.googleAndroidMaterial}" + + /* Jetpack Lifecycle Component */ + val lifeCycleExtensions = "androidx.lifecycle:lifecycle-extensions:${Versions.jetpackLifeCycle}" + val lifeCycleCommonJava8 = "androidx.lifecycle:lifecycle-common-java8:${Versions.jetpackLifeCycle}" + + /* Jetpack Room Component */ + val roomRuntime = "androidx.room:room-runtime:${Versions.jetpackRoom}" + val roomCompiler = "androidx.room:room-compiler:${Versions.jetpackRoom}" + val roomTesting = "androidx.room:room-testing:${Versions.jetpackRoom}" + val roomRxJava = "androidx.room:room-rxjava2:${Versions.jetpackRoom}" + + /* Jetpack Preference Component */ + val preference = "androidx.preference:preference-ktx:${Versions.jetpackPreference}" + + /* Jetpack Navigation Component */ + val navigationFragment = "androidx.navigation:navigation-fragment:${Versions.jetpackNavigation}" + val navigationFragmentKtx = "androidx.navigation:navigation-fragment-ktx:${Versions.jetpackNavigation}" + val navigationUi = "androidx.navigation:navigation-ui:${Versions.jetpackNavigation}" + val navigationUiKtx = "androidx.navigation:navigation-ui-ktx:${Versions.jetpackNavigation}" + val navigationDynamicFeaturesFragment = "androidx.navigation:navigation-dynamic-features-fragment:${Versions.jetpackNavigation}" + val navigationTesting = "androidx.navigation:navigation-testing:${Versions.jetpackNavigation}" + + /* RxJava */ + val rxJva = "io.reactivex.rxjava2:rxjava:${Versions.rxJava}" + val rxAndroid = "io.reactivex.rxjava2:rxandroid:${Versions.rxAndroid}" + + /* Kotlin Coroutines */ + val kotlinXCoroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinXCoroutines}" + val kotlinXCoroutinesAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.kotlinXCoroutines}" + + /* Dependency Injection using Dagger2 */ + val dagger = "com.google.dagger:dagger:${Versions.dagger}" + val daggerAndroidSupport = "com.google.dagger:dagger-android-support:${Versions.dagger}" + val daggerCompiler = "com.google.dagger:dagger-compiler:${Versions.dagger}" + val daggerAndroidProcessor = "com.google.dagger:dagger-android-processor:${Versions.dagger}" + + + val koin = "org.koin:koin-android:${Versions.koin}" + val koinViewModel = "org.koin:koin-android-viewmodel:${Versions.koin}" + + /* Remote API call using Retrofit2 */ + val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}" + val converterGson = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}" + val adapterRxJava = "com.squareup.retrofit2:adapter-rxjava2:${Versions.retrofit}" + val okHttp = "com.squareup.okhttp3:okhttp:${Versions.okHttp}" + val loggingInterceptor = "com.squareup.okhttp3:logging-interceptor:${Versions.okHttp}" + val gson = "com.google.code.gson:gson:${Versions.googleGson}" + val adapterCoroutines = "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:${Versions.jakeWhartonCoroutineAdapter}" + + /* Other */ + + // Media management and image loading framework + val glide = "com.github.bumptech.glide:glide:${Versions.glide}" + val glideCompiler = "com.github.bumptech.glide:compiler:${Versions.glide}" + + /** + * To add an external maven dependency: + * compile "groupId:artifactId:version" + */ + + const val excludeGroup = "com.android.support" + const val excludeModule = "support-annotations" +} diff --git a/buildSrc/src/main/kotlin/com/mydigipay/challenge/Plugins.kt b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Plugins.kt new file mode 100644 index 00000000..28c11035 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Plugins.kt @@ -0,0 +1,10 @@ +object Plugins { + const val androidApplication = "com.android.application" + const val androidLibrary = "com.android.library" + const val javaLibrary = "java-library" + const val kotlin = "kotlin" + const val kotlinAndroid = "kotlin-android" + const val kotlinAndroidExtensions = "kotlin-android-extensions" + const val kotlinKapt = "kotlin-kapt" + const val gradleVersions = "com.github.ben-manes.versions" +} diff --git a/buildSrc/src/main/kotlin/com/mydigipay/challenge/Versions.kt b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Versions.kt new file mode 100644 index 00000000..b0e1c063 --- /dev/null +++ b/buildSrc/src/main/kotlin/com/mydigipay/challenge/Versions.kt @@ -0,0 +1,47 @@ +object Versions { + /* Classpaths */ + const val kotlin = "1.3.71" + const val androidGradlePlugin = "3.6.3" + const val gradleVersionsPlugin = "0.27.0" + + /* Testing */ + const val jUnit = "4.12" + const val androidXTestRunner = "1.2.0" + const val androidXTestExt = "1.1.1" + const val espresso = "3.1.1-alpha01" + + /* AndroidX Support */ + const val androidXKtx = "1.2.0" + const val androidXAppCompat = "1.1.0" + const val androidXConstraintLayout = "2.0.0-beta4" + const val androidXRecyclerView = "1.1.0" + const val androidXCardView = "1.0.0" + const val androidXSwipeRefreshLayout = "1.0.0" + const val googleAndroidMaterial = "1.2.0-alpha05" + + /* Jetpack Architecture Components */ + const val jetpackLifeCycle = "2.2.0" + const val jetpackRoom = "2.2.5" + const val jetpackPreference = "1.1.0" + const val jetpackNavigation = "2.3.0-alpha03" + + /* Reactive Extensions */ + const val rxJava = "2.1.3" + const val rxAndroid = "2.0.1" + + /* KotlinX Support */ + const val kotlinXCoroutines = "1.3.2" + + /* Dependency Injection */ + const val dagger = "2.23.2" + const val koin = "2.1.6" + + /* Remote API Call */ + const val retrofit = "2.6.0" + const val okHttp = "4.0.1" + const val googleGson = "2.6.2" + const val jakeWhartonCoroutineAdapter = "0.9.2" + + /* Other */ + const val glide = "4.11.0" +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2d0cdcbf..4840eb2a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Jan 11 10:25:49 IRST 2020 +#Sat Jul 25 23:20:56 IRDT 2020 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 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip From 414ef6770993e8b9ad908c6b572640b537060e5c Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 00:58:50 +0430 Subject: [PATCH 02/24] Add core module --- .idea/compiler.xml | 8 +++++++ app/build.gradle | 3 +++ core/.gitignore | 1 + core/build.gradle | 21 +++++++++++++++++++ .../java/com/mydigipay/challenge/MyClass.kt | 4 ++++ settings.gradle | 3 ++- 6 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .idea/compiler.xml create mode 100644 core/.gitignore create mode 100644 core/build.gradle create mode 100644 core/src/main/java/com/mydigipay/challenge/MyClass.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..547ba2b1 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 32313f82..74b3df91 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -61,6 +61,9 @@ android { dependencies { + /* Modules */ + implementation project(path: ':core') + /* JAR Dependencies */ implementation fileTree(include: ['*.jar'], dir: 'libs') diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 00000000..540b9fc0 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,21 @@ +apply plugin: Plugins.javaLibrary +apply plugin: Plugins.kotlin +apply plugin: Plugins.kotlinKapt + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation Dependencies.kotlin + + /* RxJava */ + implementation Dependencies.rxJva + implementation Dependencies.rxAndroid + + /* Dependency Injection using Dagger2 */ + implementation Dependencies.dagger + implementation Dependencies.daggerAndroidSupport + kapt Dependencies.daggerCompiler + kapt Dependencies.daggerAndroidProcessor +} + +sourceCompatibility = "8" +targetCompatibility = "8" diff --git a/core/src/main/java/com/mydigipay/challenge/MyClass.kt b/core/src/main/java/com/mydigipay/challenge/MyClass.kt new file mode 100644 index 00000000..6369e87a --- /dev/null +++ b/core/src/main/java/com/mydigipay/challenge/MyClass.kt @@ -0,0 +1,4 @@ +package com.mydigipay.challenge + +class MyClass { +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 56145669..adf72245 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ -include ':app' rootProject.name='Github' +include ':app' +include ':core' From 9eb90e646be5df511aee5ce9aa2b2ea03d67caf9 Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 03:32:12 +0430 Subject: [PATCH 03/24] Add Model, DataSource, Repository and UseCase for AccessToken --- .../java/com/mydigipay/challenge/MyClass.kt | 4 --- .../challenge/authorization/AccessToken.kt | 6 +++++ .../authorization/AccessTokenDataSource.kt | 14 ++++++++++ .../authorization/AccessTokenRepository.kt | 16 +++++++++++ .../authorization/AccessTokenUseCase.kt | 16 +++++++++++ .../com/mydigipay/challenge/model/Resource.kt | 27 +++++++++++++++++++ .../com/mydigipay/challenge/model/Status.kt | 13 +++++++++ 7 files changed, 92 insertions(+), 4 deletions(-) delete mode 100644 core/src/main/java/com/mydigipay/challenge/MyClass.kt create mode 100644 core/src/main/java/com/mydigipay/challenge/authorization/AccessToken.kt create mode 100644 core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenDataSource.kt create mode 100644 core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenRepository.kt create mode 100644 core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenUseCase.kt create mode 100644 core/src/main/java/com/mydigipay/challenge/model/Resource.kt create mode 100644 core/src/main/java/com/mydigipay/challenge/model/Status.kt diff --git a/core/src/main/java/com/mydigipay/challenge/MyClass.kt b/core/src/main/java/com/mydigipay/challenge/MyClass.kt deleted file mode 100644 index 6369e87a..00000000 --- a/core/src/main/java/com/mydigipay/challenge/MyClass.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.mydigipay.challenge - -class MyClass { -} \ No newline at end of file diff --git a/core/src/main/java/com/mydigipay/challenge/authorization/AccessToken.kt b/core/src/main/java/com/mydigipay/challenge/authorization/AccessToken.kt new file mode 100644 index 00000000..5704c809 --- /dev/null +++ b/core/src/main/java/com/mydigipay/challenge/authorization/AccessToken.kt @@ -0,0 +1,6 @@ +package com.mydigipay.challenge.authorization + +data class AccessToken( + val accessToken: String, + val tokenType: String +) diff --git a/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenDataSource.kt b/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenDataSource.kt new file mode 100644 index 00000000..d909e4fc --- /dev/null +++ b/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenDataSource.kt @@ -0,0 +1,14 @@ +package com.mydigipay.challenge.authorization + +import com.mydigipay.challenge.model.Resource + +interface AccessTokenDataSource { + + suspend fun getAccessToken( + clientId: String, + clientSecret: String, + code: String, + redirectUri: String, + state: String = "0" + ): Resource +} diff --git a/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenRepository.kt b/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenRepository.kt new file mode 100644 index 00000000..3873a6f8 --- /dev/null +++ b/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenRepository.kt @@ -0,0 +1,16 @@ +package com.mydigipay.challenge.authorization + +import com.mydigipay.challenge.model.Resource +import javax.inject.Inject + +class AccessTokenRepository @Inject constructor(private val dataSource: AccessTokenDataSource) { + + suspend fun getAccessToken( + clientId: String, + clientSecret: String, + code: String, + redirectUri: String, + state: String = "0" + ): Resource = + dataSource.getAccessToken(clientId, clientSecret, code, redirectUri, state) +} diff --git a/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenUseCase.kt b/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenUseCase.kt new file mode 100644 index 00000000..60181626 --- /dev/null +++ b/core/src/main/java/com/mydigipay/challenge/authorization/AccessTokenUseCase.kt @@ -0,0 +1,16 @@ +package com.mydigipay.challenge.authorization + +import com.mydigipay.challenge.model.Resource +import javax.inject.Inject + +class AccessTokenUseCase @Inject constructor(private val repository: AccessTokenRepository) { + + suspend operator fun invoke( + clientId: String, + clientSecret: String, + code: String, + redirectUri: String, + state: String = "0" + ): Resource = + repository.getAccessToken(clientId, clientSecret, code, redirectUri, state) +} diff --git a/core/src/main/java/com/mydigipay/challenge/model/Resource.kt b/core/src/main/java/com/mydigipay/challenge/model/Resource.kt new file mode 100644 index 00000000..8f204d74 --- /dev/null +++ b/core/src/main/java/com/mydigipay/challenge/model/Resource.kt @@ -0,0 +1,27 @@ +package com.mydigipay.challenge.model + +/** + * Reference: + * [https://developer.android.com/jetpack/docs/guide#addendum] + */ + +class Resource constructor(val status: Status, val data: T?, val error: Throwable? = null) { + + companion object { + + fun loading(): Resource = + Resource(Status.LOADING, null) + + fun success(data: T): Resource { + return Resource(Status.SUCCESS, data) + } + + fun error(throwable: Throwable): Resource { + return Resource( + status = Status.ERROR, + data = null, + error = throwable + ) + } + } +} diff --git a/core/src/main/java/com/mydigipay/challenge/model/Status.kt b/core/src/main/java/com/mydigipay/challenge/model/Status.kt new file mode 100644 index 00000000..1730c739 --- /dev/null +++ b/core/src/main/java/com/mydigipay/challenge/model/Status.kt @@ -0,0 +1,13 @@ +package com.mydigipay.challenge.model + +/** + * Reference: + * [https://developer.android.com/jetpack/docs/guide#addendum] + */ + +enum class Status { + + LOADING, + SUCCESS, + ERROR +} From fe51e7067d3e60bff8a595b6b8b4bae33e657196 Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:19:49 +0430 Subject: [PATCH 04/24] Add basic Activity and ViewModel --- .../challenge/common/BaseActivity.kt | 225 ++++++++++++++++++ .../challenge/common/BaseViewModel.kt | 24 ++ 2 files changed, 249 insertions(+) create mode 100644 app/src/main/java/com/mydigipay/challenge/common/BaseActivity.kt create mode 100644 app/src/main/java/com/mydigipay/challenge/common/BaseViewModel.kt diff --git a/app/src/main/java/com/mydigipay/challenge/common/BaseActivity.kt b/app/src/main/java/com/mydigipay/challenge/common/BaseActivity.kt new file mode 100644 index 00000000..257a93ab --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/common/BaseActivity.kt @@ -0,0 +1,225 @@ +package com.mydigipay.challenge.common + +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.ContextMenu +import android.view.ContextMenu.ContextMenuInfo +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.mydigipay.challenge.github.R +import com.mydigipay.challenge.presentation.design.TAG +import dagger.android.AndroidInjection +import dagger.android.DispatchingAndroidInjector +import dagger.android.support.HasSupportFragmentInjector +import javax.inject.Inject + +abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector { + + @Inject + lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector + + override fun supportFragmentInjector() = dispatchingAndroidInjector + + /**************************************************** + * ACTIVITY LIFECYCLE + ***************************************************/ + + override fun onCreate(savedInstanceState: Bundle?) { + AndroidInjection.inject(this) + super.onCreate(savedInstanceState) + + // DEBUG + Log.d(TAG, "Lifecycle - onCreate") + + // FIRST, initialize the Activity with [savedInstanceState] + initializeActivity(savedInstanceState) + + // Setup Views + setupViews() + + // Set Action Bar + setupActionBar() + + // Init fragments and then, open home fragment + setupNavigation() + + // Setup Observers + setupObservers() + + // LAST, extract required params from incoming Intent + extractIntentParams(intent) + } + + override fun onStart() { + super.onStart() + + // DEBUG + Log.d(TAG, "Lifecycle - onStart") + } + + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + super.onRestoreInstanceState(savedInstanceState) + + // DEBUG + Log.d(TAG, "Lifecycle - onRestoreInstanceState") + } + + override fun onResume() { + super.onResume() + + // DEBUG + Log.d(TAG, "Lifecycle - onResume") + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + + // DEBUG + Log.d(TAG, String.format("Lifecycle - onWindowFocusChanged(%s)", hasFocus)) + } + + override fun onBackPressed() { + // DEBUG + Log.d(TAG, "Lifecycle - onBackPressed") + super.onBackPressed() + } + + override fun onPause() { + // DEBUG + Log.d(TAG, "Lifecycle - onPause") + super.onPause() + } + + override fun onSaveInstanceState(outState: Bundle) { + // DEBUG + Log.d(TAG, "Lifecycle - onSaveInstanceState") + super.onSaveInstanceState(outState) + } + + override fun onStop() { + // DEBUG + Log.d(TAG, "Lifecycle - onStop") + super.onStop() + } + + override fun onRestart() { + super.onRestart() + + // DEBUG + Log.d(TAG, "Lifecycle - onRestart") + } + + override fun onDestroy() { + // DEBUG + Log.d(TAG, "Lifecycle - onDestroy") + super.onDestroy() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + + // DEBUG + Log.d(TAG, "Lifecycle - onNewIntent") + } + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + data: Intent? + ) { + super.onActivityResult(requestCode, resultCode, data) + + // DEBUG + Log.d(TAG, "Lifecycle - onActivityResult") + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + when (item.itemId) { + R.id.action_settings -> { + // Nothing + return true + } + android.R.id.home -> { + // App icon in action bar clicked, so go home or finish this activity. + finish() + return true + } + } + return super.onOptionsItemSelected(item) + } + + override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenuInfo) { + super.onCreateContextMenu(menu, v, menuInfo) + } + + override fun onContextItemSelected(item: MenuItem): Boolean { + return true + } + + /**************************************************** + * ACTIVITY STATE + ***************************************************/ + + /** + * If [savedInstanceState] equals to null, initialize state from incoming Intent, + * else restore state from savedInstanceState. + */ + protected abstract fun initializeActivity(savedInstanceState: Bundle?) + + /** + * Extract data params from incoming Intent. + */ + protected abstract fun extractIntentParams(data: Intent?) + + /**************************************************** + * VIEW/DATA BINDING + ***************************************************/ + + /** + * Set Content View, + * Set Action Bar, + * Init fragments and then, open HomeFragment as default, + * Bottom Navigation View, + * ... + */ + protected abstract fun setupViews() + protected abstract fun setupActionBar() + protected abstract fun setupNavigation() + + /**************************************************** + * OBSERVERS + ***************************************************/ + + protected open fun setupObservers() { + // Nothing + } + + /**************************************************** + * SERVICE BINDING + ***************************************************/ + + // Nothing + + /**************************************************** + * FUNCTIONALITY + ***************************************************/ + + // Nothing + + companion object { + val TAG = BaseActivity::class.java.simpleName + } +} diff --git a/app/src/main/java/com/mydigipay/challenge/common/BaseViewModel.kt b/app/src/main/java/com/mydigipay/challenge/common/BaseViewModel.kt new file mode 100644 index 00000000..0a1dc496 --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/common/BaseViewModel.kt @@ -0,0 +1,24 @@ +package com.mydigipay.challenge.common + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import io.reactivex.disposables.CompositeDisposable + +/** + * Reference: + * [https://github.com/googlesamples/android-architecture-components] + */ + +open class BaseViewModel(application: Application) : AndroidViewModel(application) { + + private val disposable = CompositeDisposable() + + override fun onCleared() { + if (!disposable.isDisposed) { + disposable.dispose() + } + + super.onCleared() + } +} From 24fd818ab980743cf55f5df1a12c02b5d08164c6 Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:20:46 +0430 Subject: [PATCH 05/24] Add some helper classes --- .../challenge/common/help/SingleLiveEvent.kt | 42 +++++++++++++++++++ .../challenge/common/help/ViewModelFactory.kt | 37 ++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 app/src/main/java/com/mydigipay/challenge/common/help/SingleLiveEvent.kt create mode 100644 app/src/main/java/com/mydigipay/challenge/common/help/ViewModelFactory.kt diff --git a/app/src/main/java/com/mydigipay/challenge/common/help/SingleLiveEvent.kt b/app/src/main/java/com/mydigipay/challenge/common/help/SingleLiveEvent.kt new file mode 100644 index 00000000..43b5e573 --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/common/help/SingleLiveEvent.kt @@ -0,0 +1,42 @@ +package com.mydigipay.challenge.common.help + +import androidx.annotation.MainThread +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import java.util.concurrent.atomic.AtomicBoolean + +/** + * A lifecycle-aware observable that sends only new updates after subscription, used for events like + * navigation and SnackBar messages, in our case ViewEffect. + * + * This avoids a common problem with events: on configuration change (like rotation) an update + * can be emitted if the observer is active. This LiveData only calls the observable if there's an + * explicit call to setValue() or call(). + * + * Note: ONLY ONE OBSERVER IS GOING TO BE NOTIFIED OF CHANGES AND THERE IS NO GUARANTEE WHICH ONE. + * IF THIS CAUSES ANY ISSUES THEN USE 'EVENT WRAPPER'. + * Event Wrapper: + * [https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150] + * + * Reference: + * [https://github.com/android/architecture-samples] + */ +class SingleLiveEvent : MutableLiveData() { + private val pending = AtomicBoolean(false) + + @MainThread + override fun observe(owner: LifecycleOwner, observer: Observer) { + super.observe(owner, Observer { t -> + if (pending.compareAndSet(true, false)) { + observer.onChanged(t) + } + }) + } + + @MainThread + override fun setValue(t: T?) { + pending.set(true) + super.setValue(t) + } +} diff --git a/app/src/main/java/com/mydigipay/challenge/common/help/ViewModelFactory.kt b/app/src/main/java/com/mydigipay/challenge/common/help/ViewModelFactory.kt new file mode 100644 index 00000000..2b9e684f --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/common/help/ViewModelFactory.kt @@ -0,0 +1,37 @@ +package com.mydigipay.challenge.common.help + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton + +/** + * Reference: + * [https://github.com/googlesamples/android-architecture-components] + */ + +@Singleton +class ViewModelFactory @Inject constructor( + private val viewModelMap: Map, + @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + var viewModel = viewModelMap[modelClass] + + if (viewModel == null) { + for (entry in viewModelMap) { + if (modelClass.isAssignableFrom(entry.key)) { + viewModel = entry.value + break + } + } + } + + if (viewModel == null) throw IllegalArgumentException("Unknown model class: $modelClass") + + return viewModel.get() as T + } +} From efd3c0d0665940c8cf99a2def8e3a970b536ec0b Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:23:34 +0430 Subject: [PATCH 06/24] Add abstraction to folow MVI design --- .../presentation/design/MviActivity.kt | 42 ++++++++++++ .../presentation/design/MviViewModel.kt | 65 +++++++++++++++++++ .../challenge/presentation/design/Utils.kt | 35 ++++++++++ 3 files changed, 142 insertions(+) create mode 100644 app/src/main/java/com/mydigipay/challenge/presentation/design/MviActivity.kt create mode 100644 app/src/main/java/com/mydigipay/challenge/presentation/design/MviViewModel.kt create mode 100644 app/src/main/java/com/mydigipay/challenge/presentation/design/Utils.kt diff --git a/app/src/main/java/com/mydigipay/challenge/presentation/design/MviActivity.kt b/app/src/main/java/com/mydigipay/challenge/presentation/design/MviActivity.kt new file mode 100644 index 00000000..c0a918eb --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/presentation/design/MviActivity.kt @@ -0,0 +1,42 @@ +package com.mydigipay.challenge.presentation.design + +import android.os.Bundle +import android.util.Log +import androidx.lifecycle.Observer +import com.mydigipay.challenge.common.BaseActivity + +/** + * Reference: + * [https://github.com/RohitSurwase/AAC-MVI-Architecture] + */ + +abstract class MviActivity> : + BaseActivity() { + + abstract val viewModel: ViewModel + + private val viewStateObserver = Observer { + // DEBUG + Log.d(TAG, "observed viewState : $it") + + renderViewState(it) + } + + private val viewEffectObserver = Observer { + // DEBUG + Log.d(TAG, "observed viewEffect : $it") + + renderViewEffect(it) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + viewModel.viewStates().observe(this, viewStateObserver) + viewModel.viewEffects().observe(this, viewEffectObserver) + } + + abstract fun renderViewState(viewState: STATE) + + abstract fun renderViewEffect(viewEffect: EFFECT) +} diff --git a/app/src/main/java/com/mydigipay/challenge/presentation/design/MviViewModel.kt b/app/src/main/java/com/mydigipay/challenge/presentation/design/MviViewModel.kt new file mode 100644 index 00000000..77bb29a3 --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/presentation/design/MviViewModel.kt @@ -0,0 +1,65 @@ +package com.mydigipay.challenge.presentation.design + +import android.app.Application +import android.util.Log +import androidx.annotation.CallSuper +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.mydigipay.challenge.common.BaseViewModel +import com.mydigipay.challenge.common.help.SingleLiveEvent + +/** + * Reference: + * [https://github.com/RohitSurwase/AAC-MVI-Architecture] + */ + +open class MviViewModel(application: Application) : + BaseViewModel(application), ViewModelContract { + + private val _viewStates: MutableLiveData = MutableLiveData() + fun viewStates(): LiveData = _viewStates + + private var _viewState: STATE? = null + protected var viewState: STATE + get() = _viewState + ?: throw UninitializedPropertyAccessException("\"viewState\" was queried before being initialized") + set(value) { + Log.d(TAG, "setting viewState : $value") + _viewState = value + _viewStates.value = value + } + + + private val _viewEffects: SingleLiveEvent = SingleLiveEvent() + fun viewEffects(): SingleLiveEvent = _viewEffects + + private var _viewEffect: EFFECT? = null + protected var viewEffect: EFFECT + get() = _viewEffect + ?: throw UninitializedPropertyAccessException("\"viewEffect\" was queried before being initialized") + set(value) { + Log.d(TAG, "setting viewEffect : $value") + _viewEffect = value + _viewEffects.value = value + } + + @CallSuper + override fun process(viewEvent: EVENT) { + if (!viewStates().hasObservers()) { + throw NoObserverAttachedException( + "No observer attached. In case of custom View \"startObserving()\" function needs to be called manually." + ) + } + + // DEBUG + Log.d(TAG, "processing viewEvent: $viewEvent") + } + + override fun onCleared() { + super.onCleared() + + // DEBUG + Log.d(TAG, "onCleared") + } +} diff --git a/app/src/main/java/com/mydigipay/challenge/presentation/design/Utils.kt b/app/src/main/java/com/mydigipay/challenge/presentation/design/Utils.kt new file mode 100644 index 00000000..bb611200 --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/presentation/design/Utils.kt @@ -0,0 +1,35 @@ +package com.mydigipay.challenge.presentation.design + +/** + * Reference: + * [https://github.com/RohitSurwase/AAC-MVI-Architecture] + */ + +internal val Any.TAG: String + get() { + return if (!javaClass.isAnonymousClass) { + val name = javaClass.simpleName + // first 23 chars + if (name.length <= 23) name else name.substring(0, 23) + } + else { + val name = javaClass.name + // last 23 chars + if (name.length <= 23) name else name.substring(name.length - 23, name.length) + } + } + +/** + * Internal Contract to be implemented by ViewModel + * Required to intercept and log ViewEvents + */ +internal interface ViewModelContract { + fun process(viewEvent: EVENT) +} + +/** + * This is a custom NoObserverAttachedException and it does what it's name suggests. + * Constructs a new exception with the specified detail message. + * This is thrown, if you have not attached any observer to the LiveData. + */ +class NoObserverAttachedException(message: String) : Exception(message) From a719bd53b4a404230af7fe2b2f2523f19de6a638 Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:25:03 +0430 Subject: [PATCH 07/24] Add AccessToken view state --- .../viewstate/AccessTokenViewState.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 app/src/main/java/com/mydigipay/challenge/presentation/viewstate/AccessTokenViewState.kt diff --git a/app/src/main/java/com/mydigipay/challenge/presentation/viewstate/AccessTokenViewState.kt b/app/src/main/java/com/mydigipay/challenge/presentation/viewstate/AccessTokenViewState.kt new file mode 100644 index 00000000..064f580d --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/presentation/viewstate/AccessTokenViewState.kt @@ -0,0 +1,27 @@ +package com.mydigipay.challenge.presentation.viewstate + +import com.mydigipay.challenge.authorization.AccessToken + +data class AccessTokenViewState( + val fetchStatus: FetchStatus, + val errorMessage: String? = null, + val accessToken: AccessToken? = null +) + +sealed class AccessTokenViewEffect { + data class ShowToast(val message: String) : AccessTokenViewEffect() + data class ShowSnackBar(val message: String) : AccessTokenViewEffect() + data class ShowDialog(val message: String) : AccessTokenViewEffect() + object RetrieveAuthorizationCode : AccessTokenViewEffect() +} + +sealed class AccessTokenViewEvent { + object AuthorizationButtonClicked : AccessTokenViewEvent() + data class AuthorizationCodeRetrieved(val authorizationCode: String) : AccessTokenViewEvent() +} + +sealed class FetchStatus { + object Fetching : FetchStatus() + object Fetched : FetchStatus() + object NotFetched : FetchStatus() +} From 9aab6ed911a5ad77d4daa0f25aa7d94a94896e1d Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:25:20 +0430 Subject: [PATCH 08/24] Add AccessToken view model --- .../viewmodel/AccessTokenViewModel.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 app/src/main/java/com/mydigipay/challenge/presentation/viewmodel/AccessTokenViewModel.kt diff --git a/app/src/main/java/com/mydigipay/challenge/presentation/viewmodel/AccessTokenViewModel.kt b/app/src/main/java/com/mydigipay/challenge/presentation/viewmodel/AccessTokenViewModel.kt new file mode 100644 index 00000000..0aa3c7e0 --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/presentation/viewmodel/AccessTokenViewModel.kt @@ -0,0 +1,64 @@ +package com.mydigipay.challenge.presentation.viewmodel + +import android.app.Application +import androidx.lifecycle.viewModelScope +import com.mydigipay.challenge.authorization.AccessTokenRepository +import com.mydigipay.challenge.model.Status +import com.mydigipay.challenge.presentation.design.MviViewModel +import com.mydigipay.challenge.presentation.viewstate.AccessTokenViewEffect +import com.mydigipay.challenge.presentation.viewstate.AccessTokenViewEvent +import com.mydigipay.challenge.presentation.viewstate.AccessTokenViewState +import com.mydigipay.challenge.presentation.viewstate.FetchStatus +import kotlinx.coroutines.launch +import java.lang.IllegalArgumentException +import javax.inject.Inject + +class AccessTokenViewModel @Inject constructor( + application: Application, + private val accessTokenRepository: AccessTokenRepository +) : MviViewModel(application) { + + init { + viewState = AccessTokenViewState(fetchStatus = FetchStatus.NotFetched) + } + + override fun process(viewEvent: AccessTokenViewEvent) { + super.process(viewEvent) + + when (viewEvent) { + AccessTokenViewEvent.AuthorizationButtonClicked -> authorizationButtonClicked() + is AccessTokenViewEvent.AuthorizationCodeRetrieved -> retrieveAccessToken(viewEvent.authorizationCode) + } + } + + private fun authorizationButtonClicked() { + viewEffect = AccessTokenViewEffect.RetrieveAuthorizationCode + } + + private fun retrieveAccessToken(authorizationCode: String) { + viewState = viewState.copy(fetchStatus = FetchStatus.Fetching) + + viewModelScope.launch { + val result = accessTokenRepository.getAccessToken( + "", + "", + authorizationCode, + "" + ) + + when (result.status) { + Status.ERROR -> { + viewState = viewState.copy(fetchStatus = FetchStatus.Fetched) + viewEffect = AccessTokenViewEffect.ShowToast( + message = result.error?.message ?: "Un-known API call error" + ) + } + Status.SUCCESS -> { + viewState = + viewState.copy(fetchStatus = FetchStatus.Fetched, accessToken = result.data) + } + else -> throw IllegalArgumentException("Un-expected API call status") + } + } + } +} From 8bb357ef90566349d990c6eda5a5476a6dc3e18b Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:25:47 +0430 Subject: [PATCH 09/24] Add AccessToken Activity --- .../view/ui/AccessTokenActivity.kt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/src/main/java/com/mydigipay/challenge/presentation/view/ui/AccessTokenActivity.kt diff --git a/app/src/main/java/com/mydigipay/challenge/presentation/view/ui/AccessTokenActivity.kt b/app/src/main/java/com/mydigipay/challenge/presentation/view/ui/AccessTokenActivity.kt new file mode 100644 index 00000000..881d1fa5 --- /dev/null +++ b/app/src/main/java/com/mydigipay/challenge/presentation/view/ui/AccessTokenActivity.kt @@ -0,0 +1,77 @@ +package com.mydigipay.challenge.presentation.view.ui + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import androidx.activity.viewModels +import androidx.appcompat.widget.Toolbar +import androidx.databinding.DataBindingUtil +import com.mydigipay.challenge.R +import com.mydigipay.challenge.databinding.ActivityAccessTokenBinding +import com.mydigipay.challenge.presentation.design.MviActivity +import com.mydigipay.challenge.presentation.viewmodel.AccessTokenViewModel +import com.mydigipay.challenge.presentation.viewstate.AccessTokenViewEffect +import com.mydigipay.challenge.presentation.viewstate.AccessTokenViewEvent +import com.mydigipay.challenge.presentation.viewstate.AccessTokenViewState + +class AccessTokenActivity : + MviActivity() { + + /** + * Values + */ + + override val viewModel: AccessTokenViewModel by viewModels() + + private lateinit var dataBinding: ActivityAccessTokenBinding + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + + extractIntentParams(intent) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu; this adds items to the action bar if it is present. + menuInflater.inflate(R.menu.menu_main, menu) + + return true + } + + override fun initializeActivity(savedInstanceState: Bundle?) { + // Nothing + } + + override fun extractIntentParams(data: Intent?) { + // Nothing + } + + override fun setupViews() { + // Set Content View + dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_access_token) + } + + override fun setupActionBar() { + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + val actionBar = supportActionBar + actionBar!!.setDisplayShowTitleEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(false) + } + + override fun setupNavigation() { + /* Nothing */ + } + + override fun renderViewState(viewState: AccessTokenViewState) { + TODO("Not yet implemented") + } + + override fun renderViewEffect(viewEffect: AccessTokenViewEffect) { + TODO("Not yet implemented") + } +} From 082f616809ca4f61fa478b6682a53e016678d3ba Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:26:33 +0430 Subject: [PATCH 10/24] Add and modify colors, dimens and styles --- app/src/main/res/values/colors.xml | 12 +++++++++--- app/src/main/res/values/dimens.xml | 5 +++++ app/src/main/res/values/styles.xml | 6 ++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 69b22338..1530b086 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,12 @@ - #008577 - #00574B - #D81B60 + #6200EE + #3700B3 + #03DAC5 + #EEEEEE + + + #1B1D1D + #767676 + #767676 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 59a0b0c4..0620a569 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,3 +1,8 @@ + 32dp + 32dp 16dp + 8dp + 16dp + 24dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 545b9c6d..a6b5fec4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -17,4 +17,10 @@ From 2591c3ebb6c1f2954bbb979a0a17592e66f2ce7f Mon Sep 17 00:00:00 2001 From: yusmle Date: Sun, 26 Jul 2020 09:42:34 +0430 Subject: [PATCH 11/24] Refactor UI layout of AccessToken Activity --- .../main/res/layout/activity_access_token.xml | 36 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 17 --------- .../main/res/layout/content_access_token.xml | 36 +++++++++++++++++++ .../main/res/layout/login_uri_activity.xml | 19 ---------- 4 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 app/src/main/res/layout/activity_access_token.xml delete mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/content_access_token.xml delete mode 100644 app/src/main/res/layout/login_uri_activity.xml diff --git a/app/src/main/res/layout/activity_access_token.xml b/app/src/main/res/layout/activity_access_token.xml new file mode 100644 index 00000000..1052eb78 --- /dev/null +++ b/app/src/main/res/layout/activity_access_token.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index fe87f339..00000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - -