From 545c1a47afda13b9b6761b44c3245d73aee2bf22 Mon Sep 17 00:00:00 2001 From: Mohamed Ebrahim Date: Fri, 16 Apr 2021 17:48:14 +0400 Subject: [PATCH 1/6] add databinding and activitymain and adapter --- app/build.gradle | 148 +++++++++++++++--- .../com/tahmeel/task/ui/main/MainActivity.kt | 20 ++- .../task/ui/main/adapter/OrdersAdapter.kt | 57 +++++++ app/src/main/res/layout/activity_main.xml | 84 ++++++++-- app/src/main/res/layout/item_order.xml | 73 +++++++++ app/src/main/res/layout/toolbar_home.xml | 16 ++ app/src/main/res/values/colors.xml | 1 + build.gradle | 9 +- dependencies.gradle | 85 +++++++++- 9 files changed, 443 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt create mode 100644 app/src/main/res/layout/item_order.xml create mode 100644 app/src/main/res/layout/toolbar_home.xml diff --git a/app/build.gradle b/app/build.gradle index 6aed516..b11c591 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,3 @@ - - plugins { id 'com.android.application' id 'kotlin-android' @@ -7,25 +5,28 @@ plugins { id 'kotlin-kapt' id 'dagger.hilt.android.plugin' } + apply from: "$rootDir/dependencies.gradle" android { - compileSdkVersion 30 - + compileSdkVersion versions.compileSdk defaultConfig { applicationId "com.tahmeel.task" - minSdkVersion 21 - targetSdkVersion 30 - versionCode 1 - versionName "0.0.1" - + minSdkVersion versions.minSdk + targetSdkVersion versions.compileSdk + versionCode versions.versionCode + versionName versions.versionName + vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + javaCompileOptions { + annotationProcessorOptions { + arguments += ["room.schemaLocation": "$projectDir/schemas".toString()] + } + } + javaCompileOptions { + annotationProcessorOptions { + arguments["dagger.hilt.disableModulesHaveInstallInCheck"] = "true" + } } } compileOptions { @@ -33,19 +34,64 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = JavaVersion.VERSION_1_8.toString() + } + buildFeatures { + dataBinding true + } + lintOptions { + abortOnError false + } + sourceSets { + androidTest.java.srcDirs += "src/test-common/java" + test.java.srcDirs += "src/test-common/java" + test.assets.srcDirs += files("$projectDir/schemas".toString()) + } + testOptions { + unitTests { + includeAndroidResources = true + returnDefaultValues = true + } + } + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/ASL2.0' + exclude("META-INF/*.kotlin_module") + } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { + kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.time.ExperimentalTime"] + kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"] } } dependencies { + // android supports + implementation "com.google.android.material:material:$versions.materialVersion" + implementation "androidx.constraintlayout:constraintlayout:$versions.constraintVersion" - implementation 'androidx.core:core-ktx:1.3.2' - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.3.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + // architecture components + implementation "androidx.fragment:fragment-ktx:$versions.fragmentVersion" + implementation "androidx.lifecycle:lifecycle-extensions:$versions.lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.lifecycleVersion" + implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$versions.lifecycleVersion" + implementation "androidx.room:room-runtime:$versions.roomVersion" + implementation "androidx.room:room-ktx:$versions.roomVersion" + kapt "androidx.room:room-compiler:$versions.roomVersion" + testImplementation "androidx.arch.core:core-testing:$versions.archCompomentVersion" + + // binding + implementation("com.github.skydoves:bindables:$versions.bindablesVersion") { + exclude group: "com.google.android.material" + } + + // startup + implementation "androidx.startup:startup-runtime:$versions.startupVersion" // hilt implementation "com.google.dagger:hilt-android:$versions.hiltCoreVersion" @@ -53,4 +99,60 @@ dependencies { kapt "androidx.hilt:hilt-compiler:$versions.hiltVersion" androidTestImplementation "com.google.dagger:hilt-android-testing:$versions.hiltCoreVersion" kaptAndroidTest "com.google.dagger:hilt-compiler:$versions.hiltCoreVersion" + + // network + implementation "com.github.skydoves:sandwich:$versions.sandwichVersion" + implementation "com.squareup.retrofit2:retrofit:$versions.retrofitVersion" + implementation "com.squareup.retrofit2:converter-moshi:$versions.retrofitVersion" + implementation "com.squareup.okhttp3:logging-interceptor:$versions.okhttpVersion" + testImplementation "com.squareup.okhttp3:mockwebserver:$versions.okhttpVersion" + + // moshi + implementation "com.squareup.moshi:moshi-kotlin:$versions.moshiVersion" + kapt "com.squareup.moshi:moshi-kotlin-codegen:$versions.moshiVersion" + + // coroutines + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutinesVersion" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.coroutinesVersion" + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$versions.coroutinesVersion" + + // whatIf + implementation "com.github.skydoves:whatif:$versions.whatIfVersion" + + // glide + implementation "com.github.bumptech.glide:glide:$versions.glideVersion" + implementation "com.github.florent37:glidepalette:$versions.glidePaletteVersion" + kapt "com.github.bumptech.glide:compiler:$versions.glideVersion" + + // bundler + implementation "com.github.skydoves:bundler:$versions.bundlerVersion" + + // transformation + implementation "com.github.skydoves:transformationlayout:$versions.transformationLayout" + + // recyclerView + implementation "androidx.recyclerview:recyclerview:$versions.recyclerView" + implementation "com.github.skydoves:baserecyclerviewadapter:$versions.baseAdapter" + + // gradation + implementation "com.github.skydoves:rainbow:$versions.rainbowVersion" + + // custom views + implementation "com.github.skydoves:androidribbon:$versions.androidRibbonVersion" + implementation "com.github.skydoves:progressview:$versions.progressViewVersion" + + // debugging + implementation "com.jakewharton.timber:timber:$versions.timberVersion" + + // unit test + testImplementation "junit:junit:$versions.junitVersion" + testImplementation "androidx.test:core:$versions.androidxTest" + testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$versions.mockitoKotlinVersion" + testImplementation "org.mockito:mockito-inline:$versions.mockitoInlineVersion" + testImplementation "app.cash.turbine:turbine:$versions.turbineVersion" + testImplementation "org.robolectric:robolectric:$versions.robolectricVersion" + androidTestImplementation "com.google.truth:truth:$versions.truthVersion" + androidTestImplementation "androidx.test.ext:junit:$versions.androidxTestJunit" + androidTestImplementation "com.android.support.test:runner:$versions.androidTestRunner" + androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoVersion" } \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt b/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt index b3bd901..428e250 100644 --- a/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt +++ b/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt @@ -2,12 +2,26 @@ package com.tahmeel.task.ui.main import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.activity.viewModels +import androidx.annotation.VisibleForTesting +import com.skydoves.bindables.BindingActivity +import com.skydoves.transformationlayout.onTransformationStartContainer import com.tahmeel.task.R +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : BindingActivity(R.layout.activity_main) { + + @VisibleForTesting + val viewModel: MainViewModel by viewModels() -class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { + onTransformationStartContainer() super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - + binding { + lifecycleOwner = this@MainActivity + adapter = PokemonAdapter() + vm = viewModel + } } } \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt b/app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt new file mode 100644 index 0000000..9d8b71b --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt @@ -0,0 +1,57 @@ +package com.tahmeel.task.ui.main.adapter + +import android.os.SystemClock +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.skydoves.bindables.BindingListAdapter +import com.skydoves.bindables.binding +import com.tahmeel.task.R + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + + +class OrdersAdapter : BindingListAdapter(diffUtil) { + + private var onClickedAt = 0L + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PokemonViewHolder { + val binding = parent.binding(R.layout.item_pokemon) + return PokemonViewHolder(binding).apply { + binding.root.setOnClickListener { + val position = bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION } + ?: return@setOnClickListener + val currentClickedAt = SystemClock.elapsedRealtime() + if (currentClickedAt - onClickedAt > binding.transformationLayout.duration) { + DetailActivity.startActivity(binding.transformationLayout, getItem(position)) + onClickedAt = currentClickedAt + } + } + } + } + + override fun onBindViewHolder(holder: PokemonViewHolder, position: Int) { + holder.binding.apply { + pokemon = getItem(position) + executePendingBindings() + } + } + + class PokemonViewHolder(val binding: ItemPokemonBinding) : + RecyclerView.ViewHolder(binding.root) + + companion object { + private val diffUtil = object : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: Pokemon, newItem: Pokemon): Boolean = + oldItem.name == newItem.name + + override fun areContentsTheSame(oldItem: Pokemon, newItem: Pokemon): Boolean = + oldItem == newItem + } + } +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6654d70..514bbc9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,18 +1,68 @@ - - - - - - \ No newline at end of file + xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_order.xml b/app/src/main/res/layout/item_order.xml new file mode 100644 index 0000000..4b63a9e --- /dev/null +++ b/app/src/main/res/layout/item_order.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar_home.xml b/app/src/main/res/layout/toolbar_home.xml new file mode 100644 index 0000000..a911417 --- /dev/null +++ b/app/src/main/res/layout/toolbar_home.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f8c6127..a252d6e 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF018786 #FF000000 #FFFFFFFF + #D8D8D8 \ No newline at end of file diff --git a/build.gradle b/build.gradle index 101542f..f211c0d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. + buildscript { apply from: "$rootDir/dependencies.gradle" repositories { google() mavenCentral() + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } dependencies { - classpath "com.android.tools.build:gradle:7.0.0-alpha08" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30" + classpath "com.android.tools.build:gradle:$versions.gradleBuildTool" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin" classpath "com.google.dagger:hilt-android-gradle-plugin:$versions.hiltCoreVersion" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files } } diff --git a/dependencies.gradle b/dependencies.gradle index 70f0bd0..e2629ae 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,7 +1,88 @@ ext.versions = [ + minSdk : 21, + compileSdk : 30, + versionCode : 2, + versionName : '0.0.2', + + // gradle plugins + gradleBuildTool : '7.0.0-alpha08', + spotlessGradle : '5.9.0', + versionPlugin : '0.28.0', + ktlint : '0.40.0', + + // kotlin + kotlin : '1.4.30', + + // support library + materialVersion : '1.4.0-alpha02', + constraintVersion : '2.0.4', + + // architecture components + fragmentVersion : '1.3.2', + lifecycleVersion : '2.2.0', + roomVersion : '2.3.0-rc01', + archCompomentVersion : '2.1.0', + + // binding + bindablesVersion : '1.0.8', + + // startup + startupVersion : '1.0.0', + // di - hiltCoreVersion: '2.34-beta', - hiltVersion : '1.0.0-beta01', + hiltCoreVersion : '2.34-beta', + hiltVersion : '1.0.0-beta01', + + // network + retrofitVersion : '2.9.0', + okhttpVersion : '4.9.0', + sandwichVersion : '1.0.9', + + // moshi + moshiVersion : '1.11.0', + moshiConverterVersion: '2.7.2', + + // coroutines + coroutinesVersion : '1.4.3', + + // whatIf + whatIfVersion : '1.1.0', + + // glide + glideVersion : '4.12.0', + glidePaletteVersion : '2.1.2', + + // bundler + bundlerVersion : '1.0.3', + + // transformation + transformationLayout : '1.0.4', + + // adapter + recyclerView : '1.2.0', + baseAdapter : '1.0.4', + + // android ribbon + androidRibbonVersion : '1.0.3', + + // progress view + progressViewVersion : '1.1.1', + + // rainbow + rainbowVersion : '1.0.3', + // debugging + timberVersion : '4.7.1', + // unit test + truthVersion : '1.0.1', + junitVersion : '4.13.2', + turbineVersion : '0.4.1', + robolectricVersion : '4.4', + androidxTest : '1.3.0', + androidxTestJunit : '1.1.2', + androidTestRunner : '1.3.0-beta01', + espressoVersion : '3.3.0', + mockitoKotlinVersion : '2.2.0', + mockitoInlineVersion : '3.5.13' ] From 0690de0d4dd36f9473d841416a5d11179022b27e Mon Sep 17 00:00:00 2001 From: Mohamed Ebrahim Date: Fri, 16 Apr 2021 18:01:32 +0400 Subject: [PATCH 2/6] add model class --- .../main/java/com/tahmeel/task/model/Order.kt | 36 +++++++++++++++++++ .../task/model/PendingOrdersResponse.kt | 16 +++++++++ app/src/main/res/layout/item_order.xml | 22 ++++++------ 3 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/tahmeel/task/model/Order.kt create mode 100644 app/src/main/java/com/tahmeel/task/model/PendingOrdersResponse.kt diff --git a/app/src/main/java/com/tahmeel/task/model/Order.kt b/app/src/main/java/com/tahmeel/task/model/Order.kt new file mode 100644 index 0000000..6e0d0de --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/model/Order.kt @@ -0,0 +1,36 @@ +package com.tahmeel.task.model + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import kotlinx.parcelize.Parcelize + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + + +@Entity +@Parcelize +@JsonClass(generateAdapter = true) +data class Order( + @field:Json(name = "load_ref") @PrimaryKey val loadRef: Int?, + @field:Json(name = "created_at") val createdAt: String, + @field:Json(name = "customer_name") val customerName: String, + @field:Json(name = "order_number") val orderNumber: String, + @field:Json(name = "order_price_formatted") val orderPriceFormatted: String, + @field:Json(name = "phone_number") val phoneNumber: Int, + @field:Json(name = "tahmeel_fee_in_cents") val tahmeelFeeInCents: Int, +) : Parcelable + +//"created_at": "2021-02-22T13:38:23.071910Z", +//"customer_name": "papryk", +//"load_ref": "18689", +//"order_number": "46fa5cb5-751d-4e77-9030-218a837c6dc2", +//"order_price_formatted": "2.00 AED", +//"phone_number": "123", +//"tahmeel_fee_in_cents": 2000 \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/model/PendingOrdersResponse.kt b/app/src/main/java/com/tahmeel/task/model/PendingOrdersResponse.kt new file mode 100644 index 0000000..e818249 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/model/PendingOrdersResponse.kt @@ -0,0 +1,16 @@ +package com.tahmeel.task.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + +@JsonClass(generateAdapter = true) +data class PendingOrdersResponse( + @field:Json(name = "page") val page: Int?, + @field:Json(name = "pending_orders") val pendingOrders: List +) \ No newline at end of file diff --git a/app/src/main/res/layout/item_order.xml b/app/src/main/res/layout/item_order.xml index 4b63a9e..5249a27 100644 --- a/app/src/main/res/layout/item_order.xml +++ b/app/src/main/res/layout/item_order.xml @@ -7,7 +7,7 @@ + type="com.tahmeel.task.model.Order" /> @@ -22,9 +22,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="6dp" - android:background="@color/background800" + android:background="@color/purple_200" android:foreground="?attr/selectableItemBackground" - app:cardBackgroundColor="@color/white_12" + app:cardBackgroundColor="@color/purple_200" app:cardCornerRadius="14dp" app:cardElevation="4dp"> @@ -32,20 +32,20 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:foreground="?attr/selectableItemBackground" - tools:background="@color/background800" + tools:background="@color/purple_200" tools:ignore="UnusedAttribute"> - + tools:text="Order" /> + tools:text="Order" /> From 2896ba7b6cffe68ade26448e1768d1fe0e130dad Mon Sep 17 00:00:00 2001 From: Mohamed Ebrahim Date: Fri, 16 Apr 2021 18:33:36 +0400 Subject: [PATCH 3/6] add mainviewmodle and main repositry --- .../tahmeel/task/repository/MainRepository.kt | 47 ++++++++++++++++ .../com/tahmeel/task/repository/Repository.kt | 9 +++ .../com/tahmeel/task/ui/main/MainActivity.kt | 5 +- .../com/tahmeel/task/ui/main/MainViewModel.kt | 55 ++++++++++++++++++- app/src/main/res/layout/activity_main.xml | 2 +- 5 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/tahmeel/task/repository/MainRepository.kt create mode 100644 app/src/main/java/com/tahmeel/task/repository/Repository.kt diff --git a/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt new file mode 100644 index 0000000..845d6e3 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt @@ -0,0 +1,47 @@ +package com.tahmeel.task.repository + +import androidx.annotation.WorkerThread +import androidx.lifecycle.Transformations.map +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.flow +import javax.inject.Inject + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + +class MainRepository @Inject constructor( + private val tahmeelClient: TahmeelClient, + private val tahmeelDao: TahmeelDao +) : Repository { + + @WorkerThread + fun fetchPokemonList( + page: Int, + onStart: () -> Unit, + onComplete: () -> Unit, + onError: (String?) -> Unit + ) = flow { + var orders = tahmeelDao.getPokemonList(page) + if (orders.isEmpty()) { + + val response = tahmeelClient.fetchPokemonList(page = page) + response.suspendOnSuccess { + data.whatIfNotNull { response -> + orders = response.results + orders.forEach { order -> order.page = page } + tahmeelDao.insertPokemonList(orders) + emit(tahmeelDao.getAllPokemonList(page)) + } + } + .onError { + map(ErrorResponseMapper) { onError("[Code: $code]: $message") } + } + .onException { onError(message) } + } else { + emit(tahmeelDao.getAllPokemonList(page)) + } + }.onStart { onStart() }.onCompletion { onComplete() }.flowOn(Dispatchers.IO) +} diff --git a/app/src/main/java/com/tahmeel/task/repository/Repository.kt b/app/src/main/java/com/tahmeel/task/repository/Repository.kt new file mode 100644 index 0000000..42a9638 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/repository/Repository.kt @@ -0,0 +1,9 @@ +package com.tahmeel.task.repository + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + +interface Repository \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt b/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt index 428e250..7649aca 100644 --- a/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt +++ b/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt @@ -1,12 +1,13 @@ package com.tahmeel.task.ui.main -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import com.skydoves.bindables.BindingActivity import com.skydoves.transformationlayout.onTransformationStartContainer import com.tahmeel.task.R +import com.tahmeel.task.databinding.ActivityMainBinding +import com.tahmeel.task.ui.main.adapter.OrdersAdapter import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -20,7 +21,7 @@ class MainActivity : BindingActivity(R.layout.activity_main super.onCreate(savedInstanceState) binding { lifecycleOwner = this@MainActivity - adapter = PokemonAdapter() + adapter = OrdersAdapter() vm = viewModel } } diff --git a/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt b/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt index 2ccdb94..70f9085 100644 --- a/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt @@ -1,10 +1,63 @@ package com.tahmeel.task.ui.main +import androidx.annotation.MainThread +import androidx.databinding.Bindable +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.viewModelScope +import com.skydoves.bindables.BindingViewModel +import com.skydoves.bindables.bindingProperty +import com.tahmeel.task.model.Order +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flatMapLatest +import timber.log.Timber +import javax.inject.Inject + /** * Created by @mohamedebrahim96 on 16,April,2021 * ShopiniWorld, Inc * ebrahimm131@gmail.com */ -class MainViewModel { + +@HiltViewModel +class MainViewModel @Inject constructor( + private val mainRepository: MainRepository, + private val savedStateHandle: SavedStateHandle +) : BindingViewModel() { + + @get:Bindable + var isLoading: Boolean by bindingProperty(false) + private set + + @get:Bindable + var toastMessage: String? by bindingProperty(null) + private set + + private val pendingOrdersFetchingIndex: MutableStateFlow = MutableStateFlow(0) + private val pendingOrdersListFlow = pendingOrdersFetchingIndex.flatMapLatest { page -> + mainRepository.fetchPokemonList( + page = page, + onStart = { isLoading = true }, + onComplete = { isLoading = false }, + onError = { toastMessage = it } + ) + } + + @get:Bindable + val pendingOrdersList: List by pendingOrdersListFlow.asBindingProperty( + viewModelScope, + emptyList() + ) + + init { + Timber.d("init MainViewModel") + } + + @MainThread + fun fetchNextPendingOrderList() { + if (!isLoading) { + pendingOrdersFetchingIndex.value++ + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 514bbc9..b514f7a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -49,7 +49,7 @@ app:layout_constraintTop_toBottomOf="@id/appBarLayout" app:paginationPokemonList="@{vm}" app:spanCount="2" - app:submitList="@{vm.pokemonList}" + app:submitList="@{vm.pendingOrdersList}" app:toast="@{vm.toastMessage}" tools:listitem="@layout/item_order" /> From 8fea937d372e3474fa4b7e7f791b9344bcab30a5 Mon Sep 17 00:00:00 2001 From: Mohamed Ebrahim Date: Fri, 16 Apr 2021 18:45:34 +0400 Subject: [PATCH 4/6] add TahmeelClient and TahmeelService for network calls also add retrofit --- .../task/network/HttpRequestInterceptor.kt | 20 +++++++++++++++++++ .../com/tahmeel/task/network/TahmeelClient.kt | 19 ++++++++++++++++++ .../tahmeel/task/network/TahmeelService.kt | 15 ++++++++++++++ .../tahmeel/task/repository/MainRepository.kt | 14 ++++++++----- 4 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/tahmeel/task/network/HttpRequestInterceptor.kt create mode 100644 app/src/main/java/com/tahmeel/task/network/TahmeelClient.kt create mode 100644 app/src/main/java/com/tahmeel/task/network/TahmeelService.kt diff --git a/app/src/main/java/com/tahmeel/task/network/HttpRequestInterceptor.kt b/app/src/main/java/com/tahmeel/task/network/HttpRequestInterceptor.kt new file mode 100644 index 0000000..1287578 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/network/HttpRequestInterceptor.kt @@ -0,0 +1,20 @@ +package com.tahmeel.task.network + +import okhttp3.Interceptor +import okhttp3.Response +import timber.log.Timber + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + +class HttpRequestInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + val request = originalRequest.newBuilder().url(originalRequest.url).build() + Timber.d(request.toString()) + return chain.proceed(request) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/network/TahmeelClient.kt b/app/src/main/java/com/tahmeel/task/network/TahmeelClient.kt new file mode 100644 index 0000000..165e768 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/network/TahmeelClient.kt @@ -0,0 +1,19 @@ +package com.tahmeel.task.network + +import com.skydoves.sandwich.ApiResponse +import com.tahmeel.task.model.PendingOrdersResponse +import javax.inject.Inject + + +class TahmeelClient @Inject constructor( + private val tahmeelService: TahmeelService +) { + + suspend fun fetchPendingOrdersList( + page: Int + ): ApiResponse = + tahmeelService.fetchPendingOrdersList( + offset = page + ) + +} diff --git a/app/src/main/java/com/tahmeel/task/network/TahmeelService.kt b/app/src/main/java/com/tahmeel/task/network/TahmeelService.kt new file mode 100644 index 0000000..104e5da --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/network/TahmeelService.kt @@ -0,0 +1,15 @@ +package com.tahmeel.task.network + +import com.skydoves.sandwich.ApiResponse +import com.tahmeel.task.model.PendingOrdersResponse +import retrofit2.http.GET +import retrofit2.http.Query + + +interface TahmeelService { + + @GET("technical_interview/orders/pending") + suspend fun fetchPendingOrdersList( + @Query("offset") offset: Int = 0 + ): ApiResponse +} diff --git a/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt index 845d6e3..b03fdb6 100644 --- a/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt +++ b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt @@ -1,7 +1,11 @@ package com.tahmeel.task.repository import androidx.annotation.WorkerThread -import androidx.lifecycle.Transformations.map +import com.skydoves.sandwich.onError +import com.skydoves.sandwich.onException +import com.skydoves.sandwich.suspendOnSuccess +import com.skydoves.whatif.whatIfNotNull +import com.tahmeel.task.network.TahmeelClient import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import javax.inject.Inject @@ -27,17 +31,17 @@ class MainRepository @Inject constructor( var orders = tahmeelDao.getPokemonList(page) if (orders.isEmpty()) { - val response = tahmeelClient.fetchPokemonList(page = page) + val response = tahmeelClient.fetchPendingOrdersList(page = page) response.suspendOnSuccess { data.whatIfNotNull { response -> - orders = response.results - orders.forEach { order -> order.page = page } + orders = response.pendingOrders + //orders.forEach { order -> order.page = page } tahmeelDao.insertPokemonList(orders) emit(tahmeelDao.getAllPokemonList(page)) } } .onError { - map(ErrorResponseMapper) { onError("[Code: $code]: $message") } + onError("[Code: ") } .onException { onError(message) } } else { From 766560f23fdd9b7a092a3afc6feb7df2593f9503 Mon Sep 17 00:00:00 2001 From: Mohamed Ebrahim Date: Fri, 16 Apr 2021 19:03:16 +0400 Subject: [PATCH 5/6] add dependancy injection files for room and retrofit --- .../1.json | 70 +++++++++++++++++++ .../java/com/tahmeel/task/di/NetworkModule.kt | 50 +++++++++++++ .../com/tahmeel/task/di/PersistenceModule.kt | 44 ++++++++++++ .../com/tahmeel/task/di/RepositoryModule.kt | 26 +++++++ .../tahmeel/task/persistence/AppDatabase.kt | 17 +++++ .../tahmeel/task/persistence/TahmeelDao.kt | 21 ++++++ .../tahmeel/task/repository/MainRepository.kt | 10 ++- .../com/tahmeel/task/ui/main/MainViewModel.kt | 2 + .../task/ui/main/adapter/OrdersAdapter.kt | 31 +++----- 9 files changed, 248 insertions(+), 23 deletions(-) create mode 100644 app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json create mode 100644 app/src/main/java/com/tahmeel/task/di/NetworkModule.kt create mode 100644 app/src/main/java/com/tahmeel/task/di/PersistenceModule.kt create mode 100644 app/src/main/java/com/tahmeel/task/di/RepositoryModule.kt create mode 100644 app/src/main/java/com/tahmeel/task/persistence/AppDatabase.kt create mode 100644 app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt diff --git a/app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json b/app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json new file mode 100644 index 0000000..653ab4f --- /dev/null +++ b/app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json @@ -0,0 +1,70 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "78efee5bb3c17c1a8f4b4dd40da64011", + "entities": [ + { + "tableName": "Order", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loadRef` INTEGER, `createdAt` TEXT NOT NULL, `customerName` TEXT NOT NULL, `orderNumber` TEXT NOT NULL, `orderPriceFormatted` TEXT NOT NULL, `phoneNumber` INTEGER NOT NULL, `tahmeelFeeInCents` INTEGER NOT NULL, PRIMARY KEY(`loadRef`))", + "fields": [ + { + "fieldPath": "loadRef", + "columnName": "loadRef", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "customerName", + "columnName": "customerName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderNumber", + "columnName": "orderNumber", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "orderPriceFormatted", + "columnName": "orderPriceFormatted", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "phoneNumber", + "columnName": "phoneNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tahmeelFeeInCents", + "columnName": "tahmeelFeeInCents", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "loadRef" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '78efee5bb3c17c1a8f4b4dd40da64011')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/di/NetworkModule.kt b/app/src/main/java/com/tahmeel/task/di/NetworkModule.kt new file mode 100644 index 0000000..bf6c4ab --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/di/NetworkModule.kt @@ -0,0 +1,50 @@ +package com.tahmeel.task.di + +import com.skydoves.sandwich.coroutines.CoroutinesResponseCallAdapterFactory +import com.tahmeel.task.network.HttpRequestInterceptor +import com.tahmeel.task.network.TahmeelClient +import com.tahmeel.task.network.TahmeelService +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient { + return OkHttpClient.Builder() + .addInterceptor(HttpRequestInterceptor()) + .build() + } + + @Provides + @Singleton + fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { + return Retrofit.Builder() + .client(okHttpClient) + .baseUrl("https://api.stagingtahmeelapp.com/") + .addConverterFactory(MoshiConverterFactory.create()) + .addCallAdapterFactory(CoroutinesResponseCallAdapterFactory()) + .build() + } + + @Provides + @Singleton + fun provideTahmeelService(retrofit: Retrofit): TahmeelService { + return retrofit.create(TahmeelService::class.java) + } + + @Provides + @Singleton + fun provideTahmeelClient(tahmeelService: TahmeelService): TahmeelClient { + return TahmeelClient(tahmeelService) + } +} diff --git a/app/src/main/java/com/tahmeel/task/di/PersistenceModule.kt b/app/src/main/java/com/tahmeel/task/di/PersistenceModule.kt new file mode 100644 index 0000000..91eef57 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/di/PersistenceModule.kt @@ -0,0 +1,44 @@ +package com.tahmeel.task.di + +import android.app.Application +import androidx.room.Room +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tahmeel.task.persistence.AppDatabase +import com.tahmeel.task.persistence.TahmeelDao +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object PersistenceModule { + + @Provides + @Singleton + fun provideMoshi(): Moshi { + return Moshi.Builder() + .addLast(KotlinJsonAdapterFactory()) + .build() + } + + @Provides + @Singleton + fun provideAppDatabase( + application: Application, + ): AppDatabase { + return Room + .databaseBuilder(application, AppDatabase::class.java, "Tahmeel.db") + .fallbackToDestructiveMigration() + .build() + } + + @Provides + @Singleton + fun provideTahmeelDao(appDatabase: AppDatabase): TahmeelDao { + return appDatabase.tahmeelDao() + } + +} diff --git a/app/src/main/java/com/tahmeel/task/di/RepositoryModule.kt b/app/src/main/java/com/tahmeel/task/di/RepositoryModule.kt new file mode 100644 index 0000000..9717182 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/di/RepositoryModule.kt @@ -0,0 +1,26 @@ +package com.tahmeel.task.di + + +import com.tahmeel.task.network.TahmeelClient +import com.tahmeel.task.persistence.TahmeelDao +import com.tahmeel.task.repository.MainRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.components.ViewModelComponent +import dagger.hilt.android.scopes.ViewModelScoped + +@Module +@InstallIn(ViewModelComponent::class) +object RepositoryModule { + + @Provides + @ViewModelScoped + fun provideMainRepository( + tahmeelClient: TahmeelClient, + tahmeelDao: TahmeelDao + ): MainRepository { + return MainRepository(tahmeelClient, tahmeelDao) + } + +} diff --git a/app/src/main/java/com/tahmeel/task/persistence/AppDatabase.kt b/app/src/main/java/com/tahmeel/task/persistence/AppDatabase.kt new file mode 100644 index 0000000..50a22e2 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/persistence/AppDatabase.kt @@ -0,0 +1,17 @@ +package com.tahmeel.task.persistence + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.tahmeel.task.model.Order + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + +@Database(entities = [Order::class], version = 1, exportSchema = true) +abstract class AppDatabase : RoomDatabase() { + + abstract fun tahmeelDao(): TahmeelDao +} diff --git a/app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt b/app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt new file mode 100644 index 0000000..8c2df1f --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt @@ -0,0 +1,21 @@ +package com.tahmeel.task.persistence + + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import com.tahmeel.task.model.Order + +@Dao +interface TahmeelDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertPokemonList(pokemonList: List) + +// @Query("SELECT * FROM Order WHERE page = :page_") +// suspend fun getOrdersList(page_: Int): List + + @Query("SELECT * FROM `Order`") + suspend fun getAllOrdersList(page_: Int): List +} \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt index b03fdb6..a580b80 100644 --- a/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt +++ b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt @@ -6,8 +6,12 @@ import com.skydoves.sandwich.onException import com.skydoves.sandwich.suspendOnSuccess import com.skydoves.whatif.whatIfNotNull import com.tahmeel.task.network.TahmeelClient +import com.tahmeel.task.persistence.TahmeelDao import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onStart import javax.inject.Inject /** @@ -28,7 +32,7 @@ class MainRepository @Inject constructor( onComplete: () -> Unit, onError: (String?) -> Unit ) = flow { - var orders = tahmeelDao.getPokemonList(page) + var orders = tahmeelDao.getAllOrdersList(page) if (orders.isEmpty()) { val response = tahmeelClient.fetchPendingOrdersList(page = page) @@ -37,7 +41,7 @@ class MainRepository @Inject constructor( orders = response.pendingOrders //orders.forEach { order -> order.page = page } tahmeelDao.insertPokemonList(orders) - emit(tahmeelDao.getAllPokemonList(page)) + emit(tahmeelDao.getAllOrdersList(page)) } } .onError { @@ -45,7 +49,7 @@ class MainRepository @Inject constructor( } .onException { onError(message) } } else { - emit(tahmeelDao.getAllPokemonList(page)) + emit(tahmeelDao.getAllOrdersList(page)) } }.onStart { onStart() }.onCompletion { onComplete() }.flowOn(Dispatchers.IO) } diff --git a/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt b/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt index 70f9085..804f502 100644 --- a/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt @@ -5,8 +5,10 @@ import androidx.databinding.Bindable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope import com.skydoves.bindables.BindingViewModel +import com.skydoves.bindables.asBindingProperty import com.skydoves.bindables.bindingProperty import com.tahmeel.task.model.Order +import com.tahmeel.task.repository.MainRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flatMapLatest diff --git a/app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt b/app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt index 9d8b71b..b8ddbef 100644 --- a/app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt +++ b/app/src/main/java/com/tahmeel/task/ui/main/adapter/OrdersAdapter.kt @@ -1,12 +1,13 @@ package com.tahmeel.task.ui.main.adapter -import android.os.SystemClock import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.skydoves.bindables.BindingListAdapter import com.skydoves.bindables.binding import com.tahmeel.task.R +import com.tahmeel.task.databinding.ItemOrderBinding +import com.tahmeel.task.model.Order /** * Created by @mohamedebrahim96 on 16,April,2021 @@ -15,42 +16,32 @@ import com.tahmeel.task.R */ -class OrdersAdapter : BindingListAdapter(diffUtil) { +class OrdersAdapter : BindingListAdapter(diffUtil) { private var onClickedAt = 0L override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PokemonViewHolder { - val binding = parent.binding(R.layout.item_pokemon) - return PokemonViewHolder(binding).apply { - binding.root.setOnClickListener { - val position = bindingAdapterPosition.takeIf { it != RecyclerView.NO_POSITION } - ?: return@setOnClickListener - val currentClickedAt = SystemClock.elapsedRealtime() - if (currentClickedAt - onClickedAt > binding.transformationLayout.duration) { - DetailActivity.startActivity(binding.transformationLayout, getItem(position)) - onClickedAt = currentClickedAt - } - } - } + val binding = parent.binding(R.layout.item_order) + return PokemonViewHolder(binding) } override fun onBindViewHolder(holder: PokemonViewHolder, position: Int) { holder.binding.apply { - pokemon = getItem(position) + order = getItem(position) executePendingBindings() } } - class PokemonViewHolder(val binding: ItemPokemonBinding) : + class PokemonViewHolder(val binding: ItemOrderBinding) : RecyclerView.ViewHolder(binding.root) companion object { - private val diffUtil = object : DiffUtil.ItemCallback() { + private val diffUtil = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Pokemon, newItem: Pokemon): Boolean = - oldItem.name == newItem.name + override fun areItemsTheSame(oldItem: Order, newItem: Order): Boolean = + oldItem.loadRef == newItem.loadRef - override fun areContentsTheSame(oldItem: Pokemon, newItem: Pokemon): Boolean = + override fun areContentsTheSame(oldItem: Order, newItem: Order): Boolean = oldItem == newItem } } From 595de440b28ee57d1262c28a6958f3953eb2cedb Mon Sep 17 00:00:00 2001 From: Mohamed Ebrahim Date: Fri, 16 Apr 2021 19:21:12 +0400 Subject: [PATCH 6/6] add view binding and fix order phone number --- .../1.json | 20 ++++---- .../task/binding/RecyclerViewBinding.kt | 46 +++++++++++++++++++ .../com/tahmeel/task/binding/ViewBinding.kt | 33 +++++++++++++ .../main/java/com/tahmeel/task/model/Order.kt | 12 ++--- .../tahmeel/task/persistence/TahmeelDao.kt | 2 +- .../tahmeel/task/repository/MainRepository.kt | 16 ++++--- .../com/tahmeel/task/ui/main/MainActivity.kt | 2 - .../com/tahmeel/task/ui/main/MainViewModel.kt | 7 ++- app/src/main/res/layout/activity_main.xml | 4 +- app/src/main/res/values-night/themes.xml | 5 +- app/src/main/res/values/themes.xml | 10 ++-- 11 files changed, 119 insertions(+), 38 deletions(-) create mode 100644 app/src/main/java/com/tahmeel/task/binding/RecyclerViewBinding.kt create mode 100644 app/src/main/java/com/tahmeel/task/binding/ViewBinding.kt diff --git a/app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json b/app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json index 653ab4f..8edbfef 100644 --- a/app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json +++ b/app/schemas/com.tahmeel.task.persistence.AppDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "78efee5bb3c17c1a8f4b4dd40da64011", + "identityHash": "bb00a4eab3771ce92f75e47927ce02c5", "entities": [ { "tableName": "Order", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loadRef` INTEGER, `createdAt` TEXT NOT NULL, `customerName` TEXT NOT NULL, `orderNumber` TEXT NOT NULL, `orderPriceFormatted` TEXT NOT NULL, `phoneNumber` INTEGER NOT NULL, `tahmeelFeeInCents` INTEGER NOT NULL, PRIMARY KEY(`loadRef`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`loadRef` INTEGER, `createdAt` TEXT, `customerName` TEXT, `orderNumber` TEXT, `orderPriceFormatted` TEXT, `phoneNumber` TEXT, `tahmeelFeeInCents` INTEGER, PRIMARY KEY(`loadRef`))", "fields": [ { "fieldPath": "loadRef", @@ -18,37 +18,37 @@ "fieldPath": "createdAt", "columnName": "createdAt", "affinity": "TEXT", - "notNull": true + "notNull": false }, { "fieldPath": "customerName", "columnName": "customerName", "affinity": "TEXT", - "notNull": true + "notNull": false }, { "fieldPath": "orderNumber", "columnName": "orderNumber", "affinity": "TEXT", - "notNull": true + "notNull": false }, { "fieldPath": "orderPriceFormatted", "columnName": "orderPriceFormatted", "affinity": "TEXT", - "notNull": true + "notNull": false }, { "fieldPath": "phoneNumber", "columnName": "phoneNumber", - "affinity": "INTEGER", - "notNull": true + "affinity": "TEXT", + "notNull": false }, { "fieldPath": "tahmeelFeeInCents", "columnName": "tahmeelFeeInCents", "affinity": "INTEGER", - "notNull": true + "notNull": false } ], "primaryKey": { @@ -64,7 +64,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '78efee5bb3c17c1a8f4b4dd40da64011')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bb00a4eab3771ce92f75e47927ce02c5')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/binding/RecyclerViewBinding.kt b/app/src/main/java/com/tahmeel/task/binding/RecyclerViewBinding.kt new file mode 100644 index 0000000..e4903e7 --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/binding/RecyclerViewBinding.kt @@ -0,0 +1,46 @@ +package com.tahmeel.task.binding + +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.skydoves.baserecyclerviewadapter.RecyclerViewPaginator +import com.skydoves.bindables.BindingListAdapter +import com.skydoves.whatif.whatIfNotNullAs +import com.tahmeel.task.ui.main.MainViewModel + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + +object RecyclerViewBinding { + + @JvmStatic + @BindingAdapter("adapter") + fun bindAdapter(view: RecyclerView, adapter: RecyclerView.Adapter<*>) { + view.adapter = adapter.apply { + stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY + } + } + + @JvmStatic + @BindingAdapter("submitList") + fun bindSubmitList(view: RecyclerView, itemList: List?) { + view.adapter.whatIfNotNullAs> { adapter -> + adapter.submitList(itemList) + } + } + + @JvmStatic + @BindingAdapter("paginationPendingOrder") + fun paginationPendingOrderList(view: RecyclerView, viewModel: MainViewModel) { + RecyclerViewPaginator( + recyclerView = view, + isLoading = { viewModel.isLoading }, + loadMore = { viewModel.fetchNextPendingOrderList() }, + onLast = { false } + ).run { + threshold = 8 + } + } +} diff --git a/app/src/main/java/com/tahmeel/task/binding/ViewBinding.kt b/app/src/main/java/com/tahmeel/task/binding/ViewBinding.kt new file mode 100644 index 0000000..30515bb --- /dev/null +++ b/app/src/main/java/com/tahmeel/task/binding/ViewBinding.kt @@ -0,0 +1,33 @@ +package com.tahmeel.task.binding + +import android.view.View +import android.widget.Toast +import androidx.databinding.BindingAdapter +import com.skydoves.whatif.whatIfNotNullOrEmpty + +/** + * Created by @mohamedebrahim96 on 16,April,2021 + * ShopiniWorld, Inc + * ebrahimm131@gmail.com + */ + +object ViewBinding { + + @JvmStatic + @BindingAdapter("toast") + fun bindToast(view: View, text: String?) { + text.whatIfNotNullOrEmpty { + Toast.makeText(view.context, it, Toast.LENGTH_SHORT).show() + } + } + + @JvmStatic + @BindingAdapter("gone") + fun bindGone(view: View, shouldBeGone: Boolean) { + view.visibility = if (shouldBeGone) { + View.GONE + } else { + View.VISIBLE + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/model/Order.kt b/app/src/main/java/com/tahmeel/task/model/Order.kt index 6e0d0de..7c1f66c 100644 --- a/app/src/main/java/com/tahmeel/task/model/Order.kt +++ b/app/src/main/java/com/tahmeel/task/model/Order.kt @@ -19,12 +19,12 @@ import kotlinx.parcelize.Parcelize @JsonClass(generateAdapter = true) data class Order( @field:Json(name = "load_ref") @PrimaryKey val loadRef: Int?, - @field:Json(name = "created_at") val createdAt: String, - @field:Json(name = "customer_name") val customerName: String, - @field:Json(name = "order_number") val orderNumber: String, - @field:Json(name = "order_price_formatted") val orderPriceFormatted: String, - @field:Json(name = "phone_number") val phoneNumber: Int, - @field:Json(name = "tahmeel_fee_in_cents") val tahmeelFeeInCents: Int, + @field:Json(name = "created_at") val createdAt: String?, + @field:Json(name = "customer_name") val customerName: String?, + @field:Json(name = "order_number") val orderNumber: String?, + @field:Json(name = "order_price_formatted") val orderPriceFormatted: String?, + @field:Json(name = "phone_number") val phoneNumber: String?, + @field:Json(name = "tahmeel_fee_in_cents") val tahmeelFeeInCents: Int?, ) : Parcelable //"created_at": "2021-02-22T13:38:23.071910Z", diff --git a/app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt b/app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt index 8c2df1f..19906f7 100644 --- a/app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt +++ b/app/src/main/java/com/tahmeel/task/persistence/TahmeelDao.kt @@ -17,5 +17,5 @@ interface TahmeelDao { // suspend fun getOrdersList(page_: Int): List @Query("SELECT * FROM `Order`") - suspend fun getAllOrdersList(page_: Int): List + suspend fun getAllOrdersList(): List } \ No newline at end of file diff --git a/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt index a580b80..ec3ac7c 100644 --- a/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt +++ b/app/src/main/java/com/tahmeel/task/repository/MainRepository.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onStart +import timber.log.Timber import javax.inject.Inject /** @@ -26,13 +27,13 @@ class MainRepository @Inject constructor( ) : Repository { @WorkerThread - fun fetchPokemonList( + fun fetchOrderList( page: Int, onStart: () -> Unit, onComplete: () -> Unit, onError: (String?) -> Unit ) = flow { - var orders = tahmeelDao.getAllOrdersList(page) + var orders = tahmeelDao.getAllOrdersList() if (orders.isEmpty()) { val response = tahmeelClient.fetchPendingOrdersList(page = page) @@ -41,15 +42,18 @@ class MainRepository @Inject constructor( orders = response.pendingOrders //orders.forEach { order -> order.page = page } tahmeelDao.insertPokemonList(orders) - emit(tahmeelDao.getAllOrdersList(page)) + emit(tahmeelDao.getAllOrdersList()) } } .onError { - onError("[Code: ") + onError("error") + } + .onException { + onError(message) + Timber.e(message) } - .onException { onError(message) } } else { - emit(tahmeelDao.getAllOrdersList(page)) + emit(tahmeelDao.getAllOrdersList()) } }.onStart { onStart() }.onCompletion { onComplete() }.flowOn(Dispatchers.IO) } diff --git a/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt b/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt index 7649aca..8a90ce7 100644 --- a/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt +++ b/app/src/main/java/com/tahmeel/task/ui/main/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.activity.viewModels import androidx.annotation.VisibleForTesting import com.skydoves.bindables.BindingActivity -import com.skydoves.transformationlayout.onTransformationStartContainer import com.tahmeel.task.R import com.tahmeel.task.databinding.ActivityMainBinding import com.tahmeel.task.ui.main.adapter.OrdersAdapter @@ -17,7 +16,6 @@ class MainActivity : BindingActivity(R.layout.activity_main val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { - onTransformationStartContainer() super.onCreate(savedInstanceState) binding { lifecycleOwner = this@MainActivity diff --git a/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt b/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt index 804f502..4aeaf87 100644 --- a/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt +++ b/app/src/main/java/com/tahmeel/task/ui/main/MainViewModel.kt @@ -38,11 +38,14 @@ class MainViewModel @Inject constructor( private val pendingOrdersFetchingIndex: MutableStateFlow = MutableStateFlow(0) private val pendingOrdersListFlow = pendingOrdersFetchingIndex.flatMapLatest { page -> - mainRepository.fetchPokemonList( + mainRepository.fetchOrderList( page = page, onStart = { isLoading = true }, onComplete = { isLoading = false }, - onError = { toastMessage = it } + onError = { + Timber.e(it) + toastMessage = it + } ) } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b514f7a..3f7d655 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -43,11 +43,11 @@ android:scrollbarStyle="outsideOverlay" android:scrollbars="vertical" app:adapter="@{adapter}" - app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/appBarLayout" - app:paginationPokemonList="@{vm}" + app:paginationPendingOrder="@{vm}" app:spanCount="2" app:submitList="@{vm.pendingOrdersList}" app:toast="@{vm.toastMessage}" diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 27209b3..c826b4f 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,7 +1,6 @@ - \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 7b126dd..c37dfd6 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,16 +1,14 @@ - \ No newline at end of file