diff --git a/app/build.gradle b/app/build.gradle index 7b7b1bc..e8e88b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,55 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'realm-android' +apply plugin: "jacoco" +jacoco { + toolVersion = "0.8.4" +} +tasks.withType(Test) { + jacoco.includeNoLocationClasses = true +} +task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) { + + reports { + xml.enabled = true + html.enabled = true + } + def fileFilter = ['**/*.html','**/*.css', + '**/*.png','**/*.jpg','**/*.JPG', + '**/io/ionic*','**/io/ionic**','**/io/ionic**/*.class', + '**/org/*','**/org/**','**/org/**/*.class','**/cordova/*','**/cordova/**','**/cordova/**/*.class', + '**/android/*','**/android/**','**/android/**/*.class','**/apache/commons/**','**/io/realm/**', + '**/com/mikhaellopez/**','**/io/michaelrocks/**','**/io/michaelrocks/**','**/org/apache/**', + '**/databinding/**/*.*', + '**/android/databinding/*Binding.*', + '**/*_ViewBinding*/**/*.class', + '**/*_ViewBinding*/**/*.java', + '**/BR.*', + '**/R.*', + '**/R$*.*', + '**/BuildConfig.*', + '**/Manifest*.*', + '**/*_MembersInjector.*', + '**/Dagger*Component.*', + '**/Dagger*Component$Builder.*', + '**/*Module_*Factory.*', + '**/*ParcelConverter.java', + '**/*Parcelable.*', + '**/*_Factory*.*', + '**/*Parcelable*.*', + '**/*_ViewBinding*.*'] + + def debugTree = fileTree(dir: "${buildDir}/intermediates/javac/debug", excludes: fileFilter) + def mainSrc = fileTree(dir: "${project.projectDir}/src/main/java/com/example/go_git", excludes: fileFilter) + + sourceDirectories = files([mainSrc]) + classDirectories = files([debugTree]) + executionData = fileTree(dir: "$buildDir", includes: [ + "jacoco/testDebugUnitTest.exec", + "outputs/code-coverage/connected/*coverage.ec" + ]) +} android { compileSdkVersion 29 buildToolsVersion "29.0.0" @@ -17,15 +65,25 @@ android { versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true } buildTypes { + debug { + testCoverageEnabled = true + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + testOptions.unitTests.includeAndroidResources = true } +def vectordrawable_version = "1.1.0" dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -34,14 +92,43 @@ dependencies { implementation 'androidx.core:core-ktx:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.google.code.gson:gson:2.8.6' + implementation 'com.google.android.material:material:1.0.0' + implementation 'io.realm:android-adapters:3.1.0' + + + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3" + + implementation 'org.koin:koin-android:2.0.1' + implementation "org.koin:koin-android-architecture:0.9.1" + + implementation "androidx.vectordrawable:vectordrawable:$vectordrawable_version" + /* To be able to animate properties of a VectorDrawable, add the following. Useful for + * illustration purposes or state changes in response to user events + */ + implementation "androidx.vectordrawable:vectordrawable-animated:$vectordrawable_version" + + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + testImplementation group: 'org.mockito', name: 'mockito-inline', version: '2.9.0' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.2' + testImplementation 'org.powermock:powermock-core:2.0.2' + testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.2' + testImplementation 'org.powermock:powermock-module-junit4:2.0.2' + testImplementation 'org.powermock:powermock-classloading-xstream:2.0.2' + implementation 'io.reactivex.rxjava2:rxjava:2.2.16' // had to add this library to support flowable converters for robolectric + + testImplementation "org.robolectric:robolectric:4.3.1" + testImplementation 'com.github.nimbl3:robolectric.shadows-supportv4:4.1-SNAPSHOT' + + + + - implementation 'com.squareup.moshi:moshi:1.8.0' - kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.8.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 56f5984..5c3e657 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="com.aks4125.gorealm"> - + + + - diff --git a/app/src/main/java/com/aks4125/gorealm/CompanyModel.kt b/app/src/main/java/com/aks4125/gorealm/CompanyModel.kt deleted file mode 100644 index d2f1d87..0000000 --- a/app/src/main/java/com/aks4125/gorealm/CompanyModel.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.aks4125.gorealm - -data class CompanyModel( - val id: Int, - val empCount: Int, - val name: String, - val address: String, - val claps: Int, - val employees: List -) - -data class Employees( - val id: Int, - val name: String, - val age: Int, - val address: String, - val department: String -) \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/MainActivity.kt b/app/src/main/java/com/aks4125/gorealm/MainActivity.kt deleted file mode 100644 index 4c64501..0000000 --- a/app/src/main/java/com/aks4125/gorealm/MainActivity.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.aks4125.gorealm - -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.activity_main.* - -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - rvCompany.apply { - layoutManager = LinearLayoutManager(context) - } - } -} diff --git a/app/src/main/java/com/aks4125/gorealm/MyApp.kt b/app/src/main/java/com/aks4125/gorealm/MyApp.kt new file mode 100644 index 0000000..8c2202f --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/MyApp.kt @@ -0,0 +1,35 @@ +package com.aks4125.gorealm + +import android.app.Application +import com.aks4125.gorealm.di.applicationModule +import io.realm.Realm +import io.realm.RealmConfiguration +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin + +class MyApp : Application() { + + override fun onCreate() { + super.onCreate() + + //koin configuration + startKoin { + androidLogger() + androidContext(this@MyApp) + modules(listOf(applicationModule)) //will add more modules later on + } + + // realm configuration + Realm.init(this) + Realm.setDefaultConfiguration( + RealmConfiguration.Builder() + .name("companies") + .deleteRealmIfMigrationNeeded() + .schemaVersion(2) + .compactOnLaunch().build() + ) + + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/adapter/CompanyAdapter.kt b/app/src/main/java/com/aks4125/gorealm/adapter/CompanyAdapter.kt new file mode 100644 index 0000000..a14e79c --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/adapter/CompanyAdapter.kt @@ -0,0 +1,144 @@ +package com.aks4125.gorealm.adapter + +import android.content.Context +import android.content.Intent +import android.graphics.drawable.AnimatedVectorDrawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Filter +import android.widget.Filterable +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat +import com.aks4125.gorealm.R +import com.aks4125.gorealm.model.CompanyModel +import com.aks4125.gorealm.ui.EmployeeActivity +import com.aks4125.gorealm.utils.GoConstants.Companion.FIELD_ID +import kotlinx.android.synthetic.main.list_item_company.view.* + + +class CompanyAdapter(val context: Context, private val modelList: MutableList) : + RecyclerView.Adapter(), Filterable { + var filterList = modelList + + private val customFilter: Filter = object : Filter() { + override fun performFiltering(constraint: CharSequence): FilterResults { + filterList = + modelList.filter { + it.name!!.contains(constraint, true) + }.toMutableList() + + + + return FilterResults().apply { + values = filterList + count = filterList.size + } + } + + override fun publishResults(constraint: CharSequence, results: FilterResults) { + notifyDataSetChanged() + } + } + + override fun getFilter(): Filter = customFilter + override fun getItemCount() = filterList.size + + /** + * diffutils to submit updates + */ + fun notifyChanges(oldList: MutableList, newList: MutableList) { + val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition].id == newList[newItemPosition].id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition] == newList[newItemPosition] + } + + override fun getOldListSize() = oldList.size + override fun getNewListSize() = newList.size + }) + diff.dispatchUpdatesTo(this) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CompanyHolder { + return CompanyHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.list_item_company, parent, false) + ) + } + + override fun onBindViewHolder(holder: CompanyHolder, position: Int) { + holder.bind(filterList[position]) + } + + fun updateList(mList: MutableList) { + with(filterList) { + clear() + addAll(mList) + notifyDataSetChanged() + } + } + + var onItemClick: ((CompanyModel) -> Unit)? = null + + inner class CompanyHolder(view: View) : RecyclerView.ViewHolder(view) { + + private val name = view.name + private val tvEmpCount = view.tvEmpCount + private val tvAddress = view.tvAddress + private val btnClaps = view.btnClaps + private val imgHeart = view.imageHeart + private val emptyView = view.emptyView + private var mVector: AnimatedVectorDrawable? = null + private var mVectorCompat: AnimatedVectorDrawableCompat? = null + private val mParent = view.mParent + + + fun bind(data: CompanyModel) { + if (adapterPosition == itemCount - 1) + emptyView.visibility = View.VISIBLE + else + emptyView.visibility = View.GONE + + + name.text = data.name + tvEmpCount.text = itemView.context.getString(R.string.total_emp, data.empCount) + tvAddress.text = data.address + btnClaps.text = data.claps.toString() + + val heartDrawable = imgHeart.drawable + + btnClaps.setOnClickListener { + //RealmRecyclerViewAdapter alternative but requires managed objects + filterList[adapterPosition].claps = filterList[adapterPosition].claps!! + 1 + notifyItemChanged(adapterPosition) + onItemClick?.invoke(filterList[adapterPosition]) + + imgHeart.alpha = 0.75f + if (heartDrawable is AnimatedVectorDrawable) { + mVector = heartDrawable + mVector?.start() + } else if (heartDrawable is AnimatedVectorDrawableCompat) { + mVectorCompat = heartDrawable + mVectorCompat?.start() + } + + } + mParent.setOnClickListener { + context.startActivity( + Intent( + context, + EmployeeActivity::class.java + ).putExtra(FIELD_ID, filterList[adapterPosition].id) + ) + } + + + } + } + +} diff --git a/app/src/main/java/com/aks4125/gorealm/adapter/EmployeeAdapter.kt b/app/src/main/java/com/aks4125/gorealm/adapter/EmployeeAdapter.kt new file mode 100644 index 0000000..d39003a --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/adapter/EmployeeAdapter.kt @@ -0,0 +1,52 @@ +package com.aks4125.gorealm.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.aks4125.gorealm.R +import com.aks4125.gorealm.model.Employees +import kotlinx.android.synthetic.main.list_item_company.view.tvAddress +import kotlinx.android.synthetic.main.list_item_employee.view.* + +class EmployeeAdapter(private val empList: MutableList) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmployeeHolder { + return EmployeeHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.list_item_employee, parent, false) + ) + } + + override fun getItemCount() = empList.size + + + override fun onBindViewHolder(holder: EmployeeHolder, position: Int) { + holder.bind(empList[position]) + } + + inner class EmployeeHolder(view: View) : RecyclerView.ViewHolder(view) { + + private val name = view.tvName + private val age = view.tvAge + private val address = view.tvAddress + private val department = view.tvEmpDeptartment + private val emptyView = view.emptyViewEmp + + + fun bind(data: Employees) { + if (adapterPosition == itemCount - 1) + emptyView.visibility = View.VISIBLE + else + emptyView.visibility = View.GONE + + + name.text = data.name + age.text = itemView.context.getString(R.string.emp_age, data.age) + address.text = data.address + department.text = itemView.context.getString(R.string.emp_dept, data.department) + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/di/applicationModule.kt b/app/src/main/java/com/aks4125/gorealm/di/applicationModule.kt new file mode 100644 index 0000000..f216019 --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/di/applicationModule.kt @@ -0,0 +1,19 @@ +package com.aks4125.gorealm.di + +import com.aks4125.gorealm.repository.RealmRepository +import com.aks4125.gorealm.ui.main.MainContractor +import com.aks4125.gorealm.ui.main.MainPresenter +import org.koin.dsl.module + + +val applicationModule = module { + + single { (view: MainContractor.IMainView) -> + MainPresenter( + view, + get() + ) + } + + single { RealmRepository() } +} \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/model/CompanyFilterModel.kt b/app/src/main/java/com/aks4125/gorealm/model/CompanyFilterModel.kt new file mode 100644 index 0000000..10d7a3c --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/model/CompanyFilterModel.kt @@ -0,0 +1,13 @@ +package com.aks4125.gorealm.model + +import com.aks4125.gorealm.utils.GoConstants.Companion.COMPANY_FILTER_UUID +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +open class CompanyFilterModel : RealmObject() { + var ascending: Boolean = false + var groupId: Int = 0 + @PrimaryKey + var pID: String = COMPANY_FILTER_UUID + +} \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/model/CompanyModel.kt b/app/src/main/java/com/aks4125/gorealm/model/CompanyModel.kt new file mode 100644 index 0000000..fb6da0c --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/model/CompanyModel.kt @@ -0,0 +1,25 @@ +package com.aks4125.gorealm.model + +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +open class CompanyModel( + @PrimaryKey + var id: Int? = null, + var empCount: Int? = null, + var name: String? = null, + var address: String? = null, + var claps: Int? = null, + var employees: RealmList = RealmList() +) : RealmObject() + + +open class Employees( + @PrimaryKey + var id: Int? = null, + var name: String? = null, + var age: Int? = null, + var address: String? = null, + var department: String? = null +) : RealmObject() \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/repository/RealmRepository.java b/app/src/main/java/com/aks4125/gorealm/repository/RealmRepository.java new file mode 100644 index 0000000..b78f298 --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/repository/RealmRepository.java @@ -0,0 +1,119 @@ +package com.aks4125.gorealm.repository; + +import com.aks4125.gorealm.model.CompanyFilterModel; +import com.aks4125.gorealm.model.CompanyModel; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import io.realm.Realm; +import io.realm.RealmResults; +import io.realm.Sort; + +import static com.aks4125.gorealm.utils.GoConstants.FIELD_CLAPS; +import static com.aks4125.gorealm.utils.GoConstants.FIELD_ID; +import static com.aks4125.gorealm.utils.GoConstants.FIELD_NAME; +import static com.aks4125.gorealm.utils.GoConstants.FILTER_BY_ID; +import static com.aks4125.gorealm.utils.GoConstants.FILTER_BY_NAME; + + +public class RealmRepository { + + + /** + * @param filterModel filter company objects + */ + public void insertOrUpdateCompanyFilter(@NotNull CompanyFilterModel filterModel) { + try (Realm mRealm = Realm.getDefaultInstance()) { + CompanyFilterModel dbModel = mRealm.where(CompanyFilterModel.class).findFirst(); // it will have only single record + if (dbModel != null) { + mRealm.executeTransaction(rlm -> { + dbModel.setAscending(filterModel.getAscending()); + dbModel.setGroupId(filterModel.getGroupId()); + }); + } else + mRealm.executeTransaction(rlm -> rlm.insertOrUpdate(filterModel)); + + } + } + + + /** + * @param mDataList company list to insert or update + */ + public void insertOrUpdateCompanyList(List mDataList) { + try (Realm mRealm = Realm.getDefaultInstance()) { + mRealm.executeTransaction(rlm -> + mRealm.insertOrUpdate(mDataList)); + } + } + + /** + * @return true if no company data in database + */ + public boolean isCompanyListEmpty() { + try (Realm mRealm = Realm.getDefaultInstance()) { + return mRealm.where(CompanyModel.class).findAll().isEmpty(); + } + } + + /** + * @return list of all companies + */ + @NotNull + public List getCompanyList() { + try (Realm mRealm = Realm.getDefaultInstance()) { + return mRealm.copyFromRealm(mRealm.where(CompanyModel.class).findAll()); // this will not empty in any case + } + } + + + /** + * @param filterModel filtering data for company + * @return list of filtered companies + */ + public List getFilteredList(@NotNull CompanyFilterModel filterModel) { + List modelList; + try (Realm mRealm = Realm.getDefaultInstance()) { + RealmResults mQuery = mRealm.where(CompanyModel.class).findAll(); // in memory objects + switch (filterModel.getGroupId()) { + case FILTER_BY_ID: + mQuery = mQuery.sort(FIELD_ID, filterModel.getAscending() ? Sort.ASCENDING : Sort.DESCENDING); // in memory query, faster than db + break; + case FILTER_BY_NAME: + mQuery = mQuery.sort(FIELD_NAME, filterModel.getAscending() ? Sort.ASCENDING : Sort.DESCENDING); + break; + default: + mQuery = mQuery.sort(FIELD_CLAPS, filterModel.getAscending() ? Sort.ASCENDING : Sort.DESCENDING); + break; + } + modelList = new ArrayList<>(mRealm.copyFromRealm(mQuery)); + } + return modelList; + } + + /** + * @param mCompany object to insert or update into realm + */ + public void insertOrUpdateCompanyObject(@NotNull CompanyModel mCompany) { + try (Realm mRealm = Realm.getDefaultInstance()) { + mRealm.executeTransaction(rlm -> rlm.insertOrUpdate(mCompany)); + } + } + + /** + * @param mCompanyId company id + * @return company data + */ + public CompanyModel getCompanyById(int mCompanyId) { + try (Realm mRealm = Realm.getDefaultInstance()) { + return mRealm.copyFromRealm(Objects.requireNonNull(mRealm.where(CompanyModel.class) + .equalTo(FIELD_ID, mCompanyId) + .findFirst())); + + } + } +} diff --git a/app/src/main/java/com/aks4125/gorealm/ui/EmployeeActivity.kt b/app/src/main/java/com/aks4125/gorealm/ui/EmployeeActivity.kt new file mode 100644 index 0000000..612bd1f --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/ui/EmployeeActivity.kt @@ -0,0 +1,53 @@ +package com.aks4125.gorealm.ui + +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.LinearLayoutManager +import com.aks4125.gorealm.R +import com.aks4125.gorealm.adapter.EmployeeAdapter +import com.aks4125.gorealm.model.CompanyFilterModel +import com.aks4125.gorealm.model.Employees +import com.aks4125.gorealm.repository.RealmRepository +import com.aks4125.gorealm.utils.GoConstants +import com.aks4125.gorealm.utils.GoConstants.Companion.FIELD_ID +import io.realm.RealmList +import kotlinx.android.synthetic.main.activity_employee.* +import org.koin.android.ext.android.inject + +class EmployeeActivity : AppCompatActivity() { + + private lateinit var mEmpList: RealmList + private lateinit var mAdapter: EmployeeAdapter + private lateinit var filterModel: CompanyFilterModel + private val mRepository: RealmRepository by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_employee) + initUI() + } + + private fun initUI() { + toolbar.setNavigationOnClickListener { onBackPressed() } + + val mCompanyId = intent!!.getIntExtra(FIELD_ID, 0) + val mCompany = mRepository.getCompanyById(mCompanyId) + mEmpList = mCompany.employees + if (mEmpList.isEmpty()) { + Toast.makeText(this, getString(R.string.no_record), Toast.LENGTH_SHORT).show() + + } + mAdapter = + EmployeeAdapter(mCompany.employees) + filterModel = CompanyFilterModel() + filterModel.groupId = GoConstants.FILTER_BY_ID + + rvEmployees.apply { + layoutManager = LinearLayoutManager(context) + adapter = mAdapter + + } + + } +} diff --git a/app/src/main/java/com/aks4125/gorealm/ui/company/AddCompanyActivity.kt b/app/src/main/java/com/aks4125/gorealm/ui/company/AddCompanyActivity.kt new file mode 100644 index 0000000..d35a877 --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/ui/company/AddCompanyActivity.kt @@ -0,0 +1,92 @@ +package com.aks4125.gorealm.ui.company + +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.aks4125.gorealm.R +import com.aks4125.gorealm.model.CompanyModel +import com.aks4125.gorealm.repository.RealmRepository +import kotlinx.android.synthetic.main.activity_add_company.* +import org.koin.android.ext.android.inject + +class AddCompanyActivity : AppCompatActivity() { + + // injecting repository + private val realmRepository: RealmRepository by inject() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_add_company) + toolbar.setNavigationOnClickListener { onBackPressed() } + + btnAdd.setOnClickListener { + + if (validate()) { + val mCompany = CompanyModel() + + mCompany.id = edtCompanyId.text.toString().toInt() + mCompany.name = edtCompanyName.text.toString() + mCompany.address = edtCompanyAddress.text.toString() + mCompany.claps = edtCompanyClaps.text.toString().toInt() + mCompany.empCount = edtCompanyEmpCount.text.toString().toInt() + + // considering replace scenario if user enters the same ID for a company. + // i will update here after unit testing if time permits + realmRepository.insertOrUpdateCompanyObject(mCompany) + showDialog() + } + + } + + + } + + /** + * upon success of adding company + */ + private fun showDialog() { + val builder = AlertDialog.Builder(this) + with(builder) { + setTitle("Success") + setMessage("${edtCompanyName.text.toString()} has been added Successfully.") + setPositiveButton("OK") { dialog, _ -> + dialog.dismiss() + finish() + } + show() + } + } + + /** + * company validations + */ + private fun validate(): Boolean { + inputId.error = null + inputName.error = null + inputAddress.error = null + inputClaps.error = null + when { + edtCompanyId.text.isNullOrEmpty() -> { + inputId.error = getString(R.string.valid_id) + return false + } + edtCompanyName.text.isNullOrEmpty() -> { + inputName.error = getString(R.string.valid_name) + return false + } + edtCompanyEmpCount.text.isNullOrEmpty() -> { + inputEmpCount.error = getString(R.string.valid_count) + return false + } + edtCompanyAddress.text.isNullOrEmpty() -> { + inputAddress.error = getString(R.string.valid_address) + return false + } + edtCompanyClaps.text.isNullOrEmpty() -> { + inputClaps.error = getString(R.string.valid_claps) + return false + } + else -> return true + } + } +} diff --git a/app/src/main/java/com/aks4125/gorealm/ui/main/MainActivity.kt b/app/src/main/java/com/aks4125/gorealm/ui/main/MainActivity.kt new file mode 100644 index 0000000..5ddf9d7 --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/ui/main/MainActivity.kt @@ -0,0 +1,145 @@ +package com.aks4125.gorealm.ui.main + +import android.app.Dialog +import android.content.Intent +import android.os.Bundle +import android.view.Window +import android.widget.Button +import android.widget.RadioGroup +import android.widget.SearchView.OnQueryTextListener +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import androidx.appcompat.widget.SwitchCompat +import androidx.recyclerview.widget.LinearLayoutManager +import com.aks4125.gorealm.R +import com.aks4125.gorealm.adapter.CompanyAdapter +import com.aks4125.gorealm.model.CompanyFilterModel +import com.aks4125.gorealm.model.CompanyModel +import com.aks4125.gorealm.ui.company.AddCompanyActivity +import com.aks4125.gorealm.utils.GoConstants.Companion.FILTER_BY_CLAPS +import com.aks4125.gorealm.utils.GoConstants.Companion.FILTER_BY_ID +import com.aks4125.gorealm.utils.GoConstants.Companion.FILTER_BY_NAME +import kotlinx.android.synthetic.main.activity_main.* +import org.koin.android.ext.android.inject +import org.koin.core.parameter.parametersOf + + +/** + * This class is only responsible to intract Presenter Layer & View Layer + * No Database related calls are done here + */ +class MainActivity : AppCompatActivity(), + MainContractor.IMainView { + + private lateinit var filterModel: CompanyFilterModel + private lateinit var mCompanyAdapter: CompanyAdapter + private var mCompanyList: MutableList = arrayListOf() + private val mainPresenter: MainContractor.IMainPresenter by inject { parametersOf(this) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + initUI() + mainPresenter.processJson(getString(R.string.sampleJson)) + + } + + /** + * initialize objects & UI + */ + private fun initUI() { + mCompanyAdapter = + CompanyAdapter(this@MainActivity,mCompanyList) + filterModel = CompanyFilterModel() + filterModel.groupId = FILTER_BY_ID + + rvCompany.apply { + layoutManager = LinearLayoutManager(context) + adapter = mCompanyAdapter + mCompanyAdapter.onItemClick = { mCompany: CompanyModel -> + mainPresenter.insertOrUpdateCompanyModel(mCompany) + } + } + + // listeners + btnShowFilter.setOnClickListener { + showDialog() + } + + mSearchView.setOnQueryTextListener(object : OnQueryTextListener, + SearchView.OnQueryTextListener { + + override fun onQueryTextChange(newText: String): Boolean { + mCompanyAdapter.filter.filter(newText) + return false + } + + override fun onQueryTextSubmit(query: String): Boolean { + return false + } + + }) + + btnAddCompany.setOnClickListener { + mainPresenter.setupRealmChangeListener(filterModel) + mSearchView.setQuery("", true) + startActivity(Intent(this@MainActivity, AddCompanyActivity::class.java)) + overridePendingTransition(0, 0) + } + } + + + override fun onResume() { + super.onResume() + mainPresenter.removeRealmListener() + } + + /** + * filter dialog + */ + private fun showDialog() { + filterModel = CompanyFilterModel() + val dialog = Dialog(this@MainActivity) + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) + dialog.setCancelable(false) + dialog.setContentView(R.layout.layout_filter) + val group = dialog.findViewById(R.id.filterGroup) as RadioGroup + val btnApply = dialog.findViewById(R.id.btnApplyFilter) as Button + val orderSwitch = dialog.findViewById(R.id.orderSwitch) as SwitchCompat + + btnApply.setOnClickListener { + val id = group.checkedRadioButtonId + var groupId = 0 + when (id) { + R.id.radioCompanyId -> { + groupId = + FILTER_BY_ID + } + R.id.radioClaps -> { + groupId = + FILTER_BY_CLAPS + } + R.id.radioCompanyName -> { + groupId = + FILTER_BY_NAME + } + } + filterModel.ascending = orderSwitch.isChecked + filterModel.groupId = groupId + mainPresenter.filterData(filterModel) + dialog.dismiss() + } + dialog.show() + + } + + /** + * updates from presenter layer to display list on view layer + */ + override fun updateList(mutableList: MutableList) { + mCompanyList.clear() + mCompanyList.addAll(mutableList) + mCompanyAdapter.updateList(mutableList) + } + +} diff --git a/app/src/main/java/com/aks4125/gorealm/ui/main/MainContractor.kt b/app/src/main/java/com/aks4125/gorealm/ui/main/MainContractor.kt new file mode 100644 index 0000000..a76f480 --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/ui/main/MainContractor.kt @@ -0,0 +1,19 @@ +package com.aks4125.gorealm.ui.main + +import com.aks4125.gorealm.model.CompanyFilterModel +import com.aks4125.gorealm.model.CompanyModel + +interface MainContractor { + interface IMainView { + fun updateList(mutableList: MutableList) + } + + interface IMainPresenter { + fun processJson(json: String) + fun filterData(filterModel: CompanyFilterModel) + fun insertOrUpdateCompanyModel(mCompany: CompanyModel) + fun removeRealmListener() + fun setupRealmChangeListener(filterModel: CompanyFilterModel) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/ui/main/MainPresenter.kt b/app/src/main/java/com/aks4125/gorealm/ui/main/MainPresenter.kt new file mode 100644 index 0000000..2775780 --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/ui/main/MainPresenter.kt @@ -0,0 +1,78 @@ +package com.aks4125.gorealm.ui.main + +import com.aks4125.gorealm.model.CompanyFilterModel +import com.aks4125.gorealm.model.CompanyModel +import com.aks4125.gorealm.repository.RealmRepository +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import io.realm.Realm +import io.realm.RealmResults + +class MainPresenter( + private val mainView: MainContractor.IMainView, + private val mRepository: RealmRepository +) : MainContractor.IMainPresenter { + + private lateinit var realmInstance: Realm + private lateinit var companyListener: Unit + private lateinit var companyResult: RealmResults + private var mCompanyList: MutableList = arrayListOf() + + /** + * insert json (companies) single time only + */ + override fun processJson(json: String) { + if (mRepository.isCompanyListEmpty) { + val turnsType = object : TypeToken>() {}.type + mCompanyList = + Gson().fromJson>( + json, + turnsType + ) + mRepository.insertOrUpdateCompanyList(mCompanyList) + + } else { + mCompanyList.addAll(mRepository.companyList) + } + mainView.updateList(mCompanyList) + } + + /** + * filter and update ui + */ + override fun filterData(filterModel: CompanyFilterModel) { + mRepository.insertOrUpdateCompanyFilter(filterModel) + mainView.updateList(mRepository.getFilteredList(filterModel)) + } + + /** + * insert or update company object in database + */ + override fun insertOrUpdateCompanyModel(mCompany: CompanyModel) { + mRepository.insertOrUpdateCompanyObject(mCompany) + } + + /** + * remove change listener as no need to listen anymore changes in database + */ + override fun removeRealmListener() { + if (::realmInstance.isInitialized) { + realmInstance.removeAllChangeListeners() + realmInstance.close() + } + } + + /** + * only register listener when user navigates to add new company + * listener removed @onResume when user has successfully added company, no need to listen further updates + */ + override fun setupRealmChangeListener(filterModel: CompanyFilterModel) { + realmInstance = Realm.getDefaultInstance() + companyResult = realmInstance.where(CompanyModel::class.java).findAll() + //updating list without resetting filter + companyListener = companyResult.addChangeListener { _, _ -> + filterData(filterModel) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/aks4125/gorealm/utils/GoConstants.kt b/app/src/main/java/com/aks4125/gorealm/utils/GoConstants.kt new file mode 100644 index 0000000..509e3a6 --- /dev/null +++ b/app/src/main/java/com/aks4125/gorealm/utils/GoConstants.kt @@ -0,0 +1,15 @@ +package com.aks4125.gorealm.utils + +class GoConstants { + + companion object { + const val COMPANY_FILTER_UUID = "c_id" + const val FILTER_BY_ID = 1 + const val FILTER_BY_CLAPS = 2 + const val FILTER_BY_NAME = 3 + const val FIELD_ID = "id" + const val FIELD_NAME = "name" + const val FIELD_CLAPS = "claps" + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/animator.xml b/app/src/main/res/drawable/animator.xml new file mode 100644 index 0000000..3228b51 --- /dev/null +++ b/app/src/main/res/drawable/animator.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml new file mode 100644 index 0000000..beafea3 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_favorite_black_24dp.xml b/app/src/main/res/drawable/ic_favorite_black_24dp.xml new file mode 100644 index 0000000..cfba5d8 --- /dev/null +++ b/app/src/main/res/drawable/ic_favorite_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_list_filter.xml b/app/src/main/res/drawable/ic_list_filter.xml new file mode 100644 index 0000000..4c2fb88 --- /dev/null +++ b/app/src/main/res/drawable/ic_list_filter.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-v23/list_item_company.xml b/app/src/main/res/layout-v23/list_item_company.xml new file mode 100644 index 0000000..4b9e952 --- /dev/null +++ b/app/src/main/res/layout-v23/list_item_company.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + +