From 592099e802aee546a145f8c9e96a3aecd149312b Mon Sep 17 00:00:00 2001 From: miPlodder Date: Sun, 16 Jun 2019 21:45:15 +0530 Subject: [PATCH] : Feat: Adds support for creating, viewing, editing group details, deleting, editing members and leaders name and tasks of group --- app/build.gradle | 13 + .../org/apache/fineract/FakeJsonName.java | 1 + .../apache/fineract/FakeRemoteDataSource.java | 6 + app/src/main/AndroidManifest.xml | 11 +- .../java/org/apache/fineract/data/Status.kt | 10 + .../data/datamanager/api/DataManagerGroups.kt | 45 +++ .../org/apache/fineract/data/models/Group.kt | 33 ++ .../fineract/data/remote/BaseApiManager.java | 7 + .../fineract/data/services/GroupsService.kt | 29 ++ .../component/ActivityComponent.java | 24 ++ .../fineract/ui/adapters/GroupsAdapter.kt | 79 ++++ .../fineract/ui/adapters/NameListAdapter.kt | 72 ++++ .../fineract/ui/online/DashboardActivity.java | 3 + .../FormCustomerDetailsFragment.java | 2 +- .../fineract/ui/online/groups/GroupAction.kt | 6 + .../creategroup/AddGroupLeaderStepFragment.kt | 181 +++++++++ .../creategroup/AddGroupMemberStepFragment.kt | 178 +++++++++ .../groups/creategroup/CreateGroupActivity.kt | 138 +++++++ .../groups/creategroup/CreateGroupAdapter.kt | 41 ++ .../creategroup/GroupAddressStepFragment.kt | 154 +++++++ .../creategroup/GroupDetailsStepFragment.kt | 124 ++++++ .../groups/creategroup/GroupModelHelper.kt | 25 ++ .../creategroup/GroupReviewStepFragment.kt | 84 ++++ .../groupdetails/GroupDetailsActivity.kt | 26 ++ .../groupdetails/GroupDetailsFragment.kt | 133 ++++++ .../groups/grouplist/GroupListFragment.kt | 133 ++++++ .../online/groups/grouplist/GroupViewModel.kt | 130 ++++++ .../groups/grouplist/GroupViewModelFactory.kt | 25 ++ .../GroupTasksBottomSheetFragment.kt | 144 +++++++ .../org/apache/fineract/utils/Constants.kt | 11 + .../org/apache/fineract/utils/DateUtils.java | 46 ++- .../apache/fineract/utils/StatusUtils.java | 35 +- .../java/org/apache/fineract/utils/Utils.java | 24 +- .../utils/ValidateIdentifierUtil.java | 2 +- app/src/main/res/drawable/border.xml | 9 + .../main/res/drawable/circular_background.xml | 7 + .../res/drawable/ic_teller_black_24dp.xml | 13 + .../main/res/layout/activity_create_group.xml | 28 ++ .../main/res/layout/fragment_group_list.xml | 23 ++ .../fragment_group_tasks_bottom_sheet.xml | 164 ++++++++ .../res/layout/fragment_groups_details.xml | 378 ++++++++++++++++++ .../layout/fragment_step_add_group_leader.xml | 125 ++++++ .../layout/fragment_step_add_group_member.xml | 128 ++++++ .../layout/fragment_step_group_address.xml | 91 +++++ .../layout/fragment_step_group_details.xml | 95 +++++ .../res/layout/fragment_step_group_review.xml | 277 +++++++++++++ app/src/main/res/layout/item_group.xml | 63 +++ app/src/main/res/layout/item_name_list.xml | 42 ++ app/src/main/res/menu/menu_group_detials.xml | 10 + app/src/main/res/menu/menu_group_search.xml | 11 + .../main/res/menu/menu_navigation_drawer.xml | 15 +- app/src/main/res/values-ml-rIN/strings.xml | 2 +- app/src/main/res/values/dimens.xml | 6 + app/src/main/res/values/strings.xml | 48 ++- app/src/main/res/values/styles_text.xml | 2 +- .../main/res/xml/network_security_config.xml | 8 + app/src/main/resources/groups.json | 122 ++++++ build.gradle | 3 + 58 files changed, 3618 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/org/apache/fineract/data/Status.kt create mode 100644 app/src/main/java/org/apache/fineract/data/datamanager/api/DataManagerGroups.kt create mode 100644 app/src/main/java/org/apache/fineract/data/models/Group.kt create mode 100644 app/src/main/java/org/apache/fineract/data/services/GroupsService.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/adapters/GroupsAdapter.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/adapters/NameListAdapter.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/GroupAction.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupLeaderStepFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupMemberStepFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupActivity.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupAdapter.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupAddressStepFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupDetailsStepFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupModelHelper.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupReviewStepFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsActivity.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupListFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModel.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModelFactory.kt create mode 100644 app/src/main/java/org/apache/fineract/ui/online/groups/grouptasks/GroupTasksBottomSheetFragment.kt create mode 100644 app/src/main/java/org/apache/fineract/utils/Constants.kt create mode 100644 app/src/main/res/drawable/border.xml create mode 100644 app/src/main/res/drawable/circular_background.xml create mode 100644 app/src/main/res/drawable/ic_teller_black_24dp.xml create mode 100644 app/src/main/res/layout/activity_create_group.xml create mode 100644 app/src/main/res/layout/fragment_group_list.xml create mode 100644 app/src/main/res/layout/fragment_group_tasks_bottom_sheet.xml create mode 100644 app/src/main/res/layout/fragment_groups_details.xml create mode 100644 app/src/main/res/layout/fragment_step_add_group_leader.xml create mode 100644 app/src/main/res/layout/fragment_step_add_group_member.xml create mode 100644 app/src/main/res/layout/fragment_step_group_address.xml create mode 100644 app/src/main/res/layout/fragment_step_group_details.xml create mode 100644 app/src/main/res/layout/fragment_step_group_review.xml create mode 100644 app/src/main/res/layout/item_group.xml create mode 100644 app/src/main/res/layout/item_name_list.xml create mode 100644 app/src/main/res/menu/menu_group_detials.xml create mode 100644 app/src/main/res/menu/menu_group_search.xml create mode 100644 app/src/main/res/xml/network_security_config.xml create mode 100644 app/src/main/resources/groups.json diff --git a/app/build.gradle b/app/build.gradle index 71788f4a..0c7f303f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,6 +17,7 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true + multiDexEnabled true } buildTypes { @@ -85,6 +86,10 @@ dependencies { implementation "androidx.cardview:cardview:$supportLibraryVersion" implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion" implementation "androidx.annotation:annotation:$supportLibraryVersion" + implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleExtension" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + kapt "androidx.lifecycle:lifecycle-compiler:$lifecycleExtension" + // Kotlin Dependencies implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" @@ -145,6 +150,14 @@ dependencies { implementation "com.mifos.mobile:mifos-passcode:$mifosPasscodeVersion" + //Easy Validation Library + implementation "com.wajahatkarim3.easyvalidation:easyvalidation-core:$easyValidationVersion" + + //Coroutines + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines" + + // Instrumentation test dependencies androidTestImplementation jUnit androidTestImplementation mockito diff --git a/app/src/commonTest/java/org/apache/fineract/FakeJsonName.java b/app/src/commonTest/java/org/apache/fineract/FakeJsonName.java index 9610ff0d..cac6ae2e 100644 --- a/app/src/commonTest/java/org/apache/fineract/FakeJsonName.java +++ b/app/src/commonTest/java/org/apache/fineract/FakeJsonName.java @@ -24,4 +24,5 @@ public class FakeJsonName { public static final String PRODUCT_PAGE = "productPage.json"; public static final String PRODUCT_DEFINITION = "productDefinition.json"; public static final String PAYROLL_CONFIG = "payrollConfiguration.json"; + public static final String GROUPS = "groups.json"; } \ No newline at end of file diff --git a/app/src/commonTest/java/org/apache/fineract/FakeRemoteDataSource.java b/app/src/commonTest/java/org/apache/fineract/FakeRemoteDataSource.java index e6b5a4ba..8ffc4c3c 100644 --- a/app/src/commonTest/java/org/apache/fineract/FakeRemoteDataSource.java +++ b/app/src/commonTest/java/org/apache/fineract/FakeRemoteDataSource.java @@ -3,6 +3,7 @@ import com.google.gson.reflect.TypeToken; import org.apache.fineract.data.models.Authentication; +import org.apache.fineract.data.models.Group; import org.apache.fineract.data.models.accounts.LedgerPage; import org.apache.fineract.data.models.accounts.AccountPage; import org.apache.fineract.data.models.customer.Command; @@ -113,4 +114,9 @@ public static PayrollConfiguration getPayrollConfig() { return testDataFactory.getObjectTypePojo(PayrollConfiguration.class, FakeJsonName.PAYROLL_CONFIG); } + + public static List getGroups() { + return testDataFactory.getListTypePojo(new TypeToken>() { + }, FakeJsonName.GROUPS); + } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d28cf451..08702c3d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -18,8 +19,10 @@ android:allowBackup="true" android:icon="@drawable/launcher_image" android:label="@string/app_name" + android:networkSecurityConfig="@xml/network_security_config" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + tools:targetApi="n"> + + + + diff --git a/app/src/main/java/org/apache/fineract/data/Status.kt b/app/src/main/java/org/apache/fineract/data/Status.kt new file mode 100644 index 00000000..a79e172c --- /dev/null +++ b/app/src/main/java/org/apache/fineract/data/Status.kt @@ -0,0 +1,10 @@ +package org.apache.fineract.data + +/** + * Created by Ahmad Jawid Muhammadi on 6/4/20 + */ +enum class Status { + LOADING, + ERROR, + DONE +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/data/datamanager/api/DataManagerGroups.kt b/app/src/main/java/org/apache/fineract/data/datamanager/api/DataManagerGroups.kt new file mode 100644 index 00000000..7de126f5 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/data/datamanager/api/DataManagerGroups.kt @@ -0,0 +1,45 @@ +package org.apache.fineract.data.datamanager.api + +import androidx.lifecycle.MutableLiveData +import io.reactivex.Observable +import io.reactivex.ObservableSource +import io.reactivex.functions.Function +import kotlinx.coroutines.Deferred +import okhttp3.ResponseBody +import org.apache.fineract.FakeRemoteDataSource +import org.apache.fineract.data.local.PreferencesHelper +import org.apache.fineract.data.models.Group +import org.apache.fineract.data.models.customer.Command +import org.apache.fineract.data.remote.BaseApiManager +import javax.inject.Inject + + +/* + * Created by saksham on 15/June/2019 +*/ + +class DataManagerGroups @Inject constructor(private val baseManagerApi: BaseApiManager, + val dataManagerAuth: DataManagerAuth, + val preferencesHelper: PreferencesHelper) + : FineractBaseDataManager(dataManagerAuth, preferencesHelper) { + + fun getGroups(): MutableLiveData> { + val groups = MutableLiveData>() + + groups.value = ArrayList(baseManagerApi.groupsService.getGroups() + .onErrorResumeNext(Function>> { + Observable.just(FakeRemoteDataSource.getGroups()) + }).blockingFirst()) + return groups + } + + fun createGroup(group: Group): Deferred = baseManagerApi.groupsService.createGroup(group) + + fun updateGroup(identifier: String, group: Group): Deferred { + return baseManagerApi.groupsService.updateGroup(identifier, group) + } + + fun changeGroupStatus(identifier: String, command: Command): Deferred { + return baseManagerApi.groupsService.changeGroupStatus(identifier, command) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/data/models/Group.kt b/app/src/main/java/org/apache/fineract/data/models/Group.kt new file mode 100644 index 00000000..9f8a35f5 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/data/models/Group.kt @@ -0,0 +1,33 @@ +package org.apache.fineract.data.models + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize +import org.apache.fineract.data.models.customer.Address + +/* + * Created by saksham on 16/June/2019 +*/ + +@Parcelize +data class Group( + var identifier: String? = null, + var groupDefinitionIdentifier: String? = null, + var name: String? = null, + var leaders: List? = null, + var members: List? = null, + var office: String? = null, + var assignedEmployee: String? = null, + var weekday: Int? = null, + var status: Status? = null, + var address: Address? = null, + var createdOn: String? = null, + var createdBy: String? = null, + var lastModifiedBy: String? = null, + var lastModifiedOn: String? = null) : Parcelable { + + enum class Status { + PENDING, + ACTIVE, + CLOSED + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/data/remote/BaseApiManager.java b/app/src/main/java/org/apache/fineract/data/remote/BaseApiManager.java index adb27346..bfef0a30 100644 --- a/app/src/main/java/org/apache/fineract/data/remote/BaseApiManager.java +++ b/app/src/main/java/org/apache/fineract/data/remote/BaseApiManager.java @@ -7,6 +7,7 @@ import org.apache.fineract.data.services.AuthService; import org.apache.fineract.data.services.CustomerService; import org.apache.fineract.data.services.DepositService; +import org.apache.fineract.data.services.GroupsService; import org.apache.fineract.data.services.IndividualLendingService; import org.apache.fineract.data.services.LoanService; import org.apache.fineract.data.services.ProductService; @@ -41,6 +42,7 @@ public class BaseApiManager { private static TellersService tellerService; private static ProductService productService; private static PayrollService payrollService; + private static GroupsService groupsService; public BaseApiManager(Context context) { createService(context); @@ -58,6 +60,7 @@ private static void init() { tellerService = createApi(TellersService.class); productService = createApi(ProductService.class); payrollService = createApi(PayrollService.class); + groupsService = createApi(GroupsService.class); } private static void initAnonymous() { @@ -142,4 +145,8 @@ public ProductService getProductService() { public PayrollService getPayrollService() { return payrollService; } + + public GroupsService getGroupsService() { + return groupsService; + } } diff --git a/app/src/main/java/org/apache/fineract/data/services/GroupsService.kt b/app/src/main/java/org/apache/fineract/data/services/GroupsService.kt new file mode 100644 index 00000000..3c0b702b --- /dev/null +++ b/app/src/main/java/org/apache/fineract/data/services/GroupsService.kt @@ -0,0 +1,29 @@ +package org.apache.fineract.data.services + +import io.reactivex.Observable +import kotlinx.coroutines.Deferred +import okhttp3.ResponseBody +import org.apache.fineract.data.models.Group +import org.apache.fineract.data.models.customer.Command +import retrofit2.http.* + +/* + * Created by saksham on 15/June/2019 +*/ + +interface GroupsService { + + @GET("/groups") + fun getGroups(): Observable> + + @POST("/groups") + fun createGroup(@Body group: Group): Deferred + + @PUT("/groups/{identifier}") + fun updateGroup(@Path("identifier") identifier: String, + @Body group: Group): Deferred + + @PUT("/groups/{identifier}/commands") + fun changeGroupStatus(@Path("identifier") identifier: String, + @Body command: Command): Deferred +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java b/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java index 6c250f80..7e8d2ec0 100644 --- a/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java +++ b/app/src/main/java/org/apache/fineract/injection/component/ActivityComponent.java @@ -30,6 +30,14 @@ import org.apache.fineract.ui.online.depositaccounts.depositaccountdetails .DepositAccountDetailsFragment; import org.apache.fineract.ui.online.depositaccounts.depositaccountslist.DepositAccountsFragment; +import org.apache.fineract.ui.online.groups.creategroup.AddGroupLeaderStepFragment; +import org.apache.fineract.ui.online.groups.creategroup.AddGroupMemberStepFragment; +import org.apache.fineract.ui.online.groups.creategroup.CreateGroupActivity; +import org.apache.fineract.ui.online.groups.creategroup.GroupAddressStepFragment; +import org.apache.fineract.ui.online.groups.creategroup.GroupReviewStepFragment; +import org.apache.fineract.ui.online.groups.groupdetails.GroupDetailsFragment; +import org.apache.fineract.ui.online.groups.grouplist.GroupListFragment; +import org.apache.fineract.ui.online.groups.grouptasks.GroupTasksBottomSheetFragment; import org.apache.fineract.ui.online.identification.createidentification.identificationactivity .CreateIdentificationActivity; import org.apache.fineract.ui.online.identification.identificationdetails @@ -135,5 +143,21 @@ public interface ActivityComponent { void inject(EditPayrollAllocationFragment editPayrollAllocationFragment); void inject(EditPayrollActivity editPayrollActivity); + + void inject(GroupListFragment groupListFragment); + + void inject(GroupAddressStepFragment groupAddressStepFragment); + + void inject(CreateGroupActivity createGroupActivity); + + void inject(AddGroupMemberStepFragment addGroupMemberStepFragment); + + void inject(AddGroupLeaderStepFragment addGroupLeaderStepFragment); + + void inject(GroupReviewStepFragment groupReviewStepFragment); + + void inject(GroupDetailsFragment groupDetailsFragment); + + void inject(GroupTasksBottomSheetFragment groupTasksBottomSheetFragment); } diff --git a/app/src/main/java/org/apache/fineract/ui/adapters/GroupsAdapter.kt b/app/src/main/java/org/apache/fineract/ui/adapters/GroupsAdapter.kt new file mode 100644 index 00000000..75e6ece5 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/adapters/GroupsAdapter.kt @@ -0,0 +1,79 @@ +package org.apache.fineract.ui.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.item_group.view.* +import org.apache.fineract.R +import org.apache.fineract.data.models.Group +import org.apache.fineract.injection.ApplicationContext +import org.apache.fineract.ui.base.OnItemClickListener +import org.apache.fineract.utils.DateUtils +import org.apache.fineract.utils.StatusUtils +import javax.inject.Inject + + +/* + * Created by saksham on 16/June/2019 +*/ + +class GroupsAdapter @Inject constructor(@ApplicationContext var context: Context?) + : RecyclerView.Adapter() { + + var groups = ArrayList() + lateinit var onItemClickListener: OnItemClickListener + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(LayoutInflater.from(context).inflate(R.layout.item_group, parent, false)) + } + + override fun getItemCount(): Int = groups.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + var group: Group = groups[position] + + StatusUtils.setGroupsStatusIcon(group.status, holder.ivTypeIndicator, context) + + holder.tvGroupIdentifier.text = group.identifier + + val modifiedBy = context?.getString(R.string.last_modified_by) + context?.getString(R.string.colon) + group.lastModifiedBy + holder.tvLastModifiedBy.text = modifiedBy + + val lastModifiedOn = context?.getString(R.string.last_modified_on) + context?.getString(R.string.colon) + DateUtils.getDate(group.lastModifiedOn, + DateUtils.INPUT_DATE_FORMAT, DateUtils.OUTPUT_DATE_FORMAT) + holder.tvLastModifiedOn.text = lastModifiedOn + } + + fun setGroupList(groups: ArrayList) { + this.groups = groups + notifyDataSetChanged() + } + + fun setItemClickListener(onItemClickListener: OnItemClickListener) { + this.onItemClickListener = onItemClickListener + } + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), + View.OnClickListener { + var llGroups: LinearLayout = itemView.ll_group + var ivTypeIndicator: ImageView = itemView.iv_type_indicator + var tvGroupIdentifier: TextView = itemView.tv_group_identifier + var tvLastModifiedBy: TextView = itemView.tv_last_modified_by + var tvLastModifiedOn: TextView = itemView.tv_last_modified_on + + init { + llGroups.setOnClickListener(this) + } + + override fun onClick(v: View?) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(v, adapterPosition) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/adapters/NameListAdapter.kt b/app/src/main/java/org/apache/fineract/ui/adapters/NameListAdapter.kt new file mode 100644 index 00000000..b261a60f --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/adapters/NameListAdapter.kt @@ -0,0 +1,72 @@ +package org.apache.fineract.ui.adapters + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.item_name_list.view.* +import org.apache.fineract.R +import javax.inject.Inject + +class NameListAdapter @Inject constructor() + : RecyclerView.Adapter() { + + private var nameList = ArrayList() + private var onItemClickListener: OnItemClickListener? = null + private var isReview = false + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder.from(parent) + } + + override fun getItemCount(): Int = nameList.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(nameList[position], onItemClickListener, isReview) + } + + fun setOnItemClickListener(onItemClickListener: OnItemClickListener?) { + this.onItemClickListener = onItemClickListener + } + + fun submitList(list: ArrayList) { + nameList = list + notifyDataSetChanged() + } + + fun setReview(isReview: Boolean) { + this.isReview = isReview + } + + class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun bind(name: String, onItemClickListener: OnItemClickListener?, isReview: Boolean) { + if (isReview) { + itemView.iv_edit.visibility = View.GONE + itemView.iv_delete.visibility = View.GONE + } else { + itemView.iv_edit.visibility = View.VISIBLE + itemView.iv_delete.visibility = View.VISIBLE + } + itemView.tv_name.text = name + itemView.iv_delete.setOnClickListener { + onItemClickListener?.onDeleteClicked(adapterPosition) + } + itemView.iv_edit.setOnClickListener { + onItemClickListener?.onEditClicked(adapterPosition) + } + } + + companion object { + fun from(parent: ViewGroup): ViewHolder { + return ViewHolder(LayoutInflater.from(parent.context) + .inflate(R.layout.item_name_list, parent, false)) + } + } + } + + interface OnItemClickListener { + fun onEditClicked(position: Int) + fun onDeleteClicked(position: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java b/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java index 15b4d61f..7f0460d9 100644 --- a/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java +++ b/app/src/main/java/org/apache/fineract/ui/online/DashboardActivity.java @@ -28,6 +28,7 @@ import org.apache.fineract.ui.online.accounting.accounts.AccountsFragment; import org.apache.fineract.ui.online.customers.customerlist.CustomersFragment; import org.apache.fineract.ui.online.dashboard.DashboardFragment; +import org.apache.fineract.ui.online.groups.grouplist.GroupListFragment; import org.apache.fineract.ui.online.launcher.LauncherActivity; import org.apache.fineract.ui.online.roles.roleslist.RolesFragment; import org.apache.fineract.ui.online.teller.TellerFragment; @@ -139,6 +140,8 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { case R.id.item_teller: replaceFragment(TellerFragment.Companion.newInstance(), true, R.id.container); break; + case R.id.item_groups: + replaceFragment(GroupListFragment.Companion.newInstance(), true, R.id.container); } drawerLayout.closeDrawer(GravityCompat.START); diff --git a/app/src/main/java/org/apache/fineract/ui/online/customers/createcustomer/FormCustomerDetailsFragment.java b/app/src/main/java/org/apache/fineract/ui/online/customers/createcustomer/FormCustomerDetailsFragment.java index 4222be02..6e779fc5 100644 --- a/app/src/main/java/org/apache/fineract/ui/online/customers/createcustomer/FormCustomerDetailsFragment.java +++ b/app/src/main/java/org/apache/fineract/ui/online/customers/createcustomer/FormCustomerDetailsFragment.java @@ -234,7 +234,7 @@ public boolean validateAccount() { return false; } else if (account.length() < 3) { showTextInputLayoutError(tilAccount, - getString(R.string.must_be_at_least_three_characters, 3)); + getString(R.string.must_be_at_least_n_characters, 3)); return false; } else if (account.length() > 32) { showTextInputLayoutError(tilAccount, diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/GroupAction.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/GroupAction.kt new file mode 100644 index 00000000..13d14d4c --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/GroupAction.kt @@ -0,0 +1,6 @@ +package org.apache.fineract.ui.online.groups + +enum class GroupAction { + EDIT, + CREATE +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupLeaderStepFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupLeaderStepFragment.kt new file mode 100644 index 00000000..55c2e4d6 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupLeaderStepFragment.kt @@ -0,0 +1,181 @@ +package org.apache.fineract.ui.online.groups.creategroup + +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import butterknife.ButterKnife +import butterknife.OnClick +import butterknife.Optional +import com.stepstone.stepper.Step +import com.stepstone.stepper.VerificationError +import com.wajahatkarim3.easyvalidation.core.view_ktx.validator +import kotlinx.android.synthetic.main.fragment_step_add_group_leader.* +import kotlinx.android.synthetic.main.fragment_step_add_group_leader.view.* +import kotlinx.android.synthetic.main.fragment_step_add_group_member.view.* +import kotlinx.android.synthetic.main.fragment_step_add_group_member.view.rv_name +import org.apache.fineract.R +import org.apache.fineract.ui.adapters.NameListAdapter +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseFragment +import org.apache.fineract.ui.online.groups.GroupAction +import org.apache.fineract.utils.Constants +import org.apache.fineract.utils.MaterialDialog +import org.apache.fineract.utils.Utils +import javax.inject.Inject + + +/* + * Created by saksham on 02/July/2019 +*/ + +class AddGroupLeaderStepFragment : FineractBaseFragment(), Step, NameListAdapter.OnItemClickListener { + + lateinit var rootView: View + var leaders: ArrayList = ArrayList() + private var currentAction = GroupAction.CREATE + private var editItemPosition = 0 + private lateinit var groupAction: GroupAction + + @Inject + lateinit var nameLisAdapter: NameListAdapter + + companion object { + fun newInstance(groupAction: GroupAction) = AddGroupLeaderStepFragment().apply { + val bundle = Bundle().apply { + putSerializable(Constants.GROUP_ACTION, groupAction) + } + arguments = bundle + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.getSerializable(Constants.GROUP_ACTION)?.let { + groupAction = it as GroupAction + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_step_add_group_leader, container, false) + ButterKnife.bind(this, rootView) + (activity as FineractBaseActivity).activityComponent.inject(this) + rootView.rv_name.adapter = nameLisAdapter + nameLisAdapter.setOnItemClickListener(this) + if (groupAction == GroupAction.EDIT) { + showDataOnViews() + } + return rootView + } + + private fun showDataOnViews() { + val group = (activity as CreateGroupActivity).getGroup() + leaders = group.leaders as ArrayList + if (leaders.size == 0) { + showRecyclerView(false) + } else { + showRecyclerView(true) + } + nameLisAdapter.submitList(leaders) + } + + @Optional + @OnClick(R.id.ibAddLeader) + fun showAddLeaderView() { + showAddLeaderView(GroupAction.CREATE, null) + } + + private fun showAddLeaderView(action: GroupAction, name: String?) { + currentAction = action + llAddLeader.visibility = View.VISIBLE + when (action) { + GroupAction.CREATE -> { + btnAddLeader.text = getString(R.string.add) + } + GroupAction.EDIT -> { + etNewLeader.setText(name) + btnAddLeader.text = getString(R.string.update) + } + } + } + + @Optional + @OnClick(R.id.btnAddLeader) + fun addLeader() { + if (etNewLeader.validator() + .nonEmpty() + .noNumbers() + .addErrorCallback { etNewLeader.error = it }.check()) { + if (currentAction == GroupAction.CREATE) { + leaders.add(etNewLeader.text.toString()) + } else { + leaders[editItemPosition] = etNewLeader.text.toString() + } + etNewLeader.text.clear() + llAddLeader.visibility = View.GONE + Utils.hideKeyboard(context, etNewLeader) + showRecyclerView(true) + nameLisAdapter.submitList(leaders) + } + } + + fun showRecyclerView(isShow: Boolean) { + if (isShow) { + rootView.rv_name.visibility = View.VISIBLE + rootView.tvAddLeader.visibility = View.GONE + } else { + rootView.rv_name.visibility = View.GONE + rootView.tvAddLeader.visibility = View.VISIBLE + } + + } + + @Optional + @OnClick(R.id.btnCancelAddLeader) + fun cancelLeaderAddition() { + etNewLeader.text.clear() + llAddLeader.visibility = View.GONE + } + + override fun onSelected() { + } + + override fun verifyStep(): VerificationError? { + if (leaders.size == 0) { + Toast.makeText(context, getString(R.string.error_group_atleast_1_member), Toast.LENGTH_SHORT).show() + return VerificationError("") + } + (activity as CreateGroupActivity).setLeaders(leaders) + return null + } + + override fun onError(error: VerificationError) { + + } + + override fun onEditClicked(position: Int) { + editItemPosition = position + showAddLeaderView(GroupAction.EDIT, leaders[position]) + } + + override fun onDeleteClicked(position: Int) { + MaterialDialog.Builder().init(context).apply { + setTitle(getString(R.string.dialog_title_confirm_deletion)) + setMessage(getString(R.string.dialog_message_confirm_name_deletion, leaders[position])) + setPositiveButton(getString(R.string.delete) + ) { dialog: DialogInterface?, _ -> + leaders.removeAt(position) + nameLisAdapter.submitList(leaders) + if (leaders.size == 0) { + showRecyclerView(false) + } + dialog?.dismiss() + } + setNegativeButton(getString(R.string.dialog_action_cancel)) + createMaterialDialog() + }.run { show() } + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupMemberStepFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupMemberStepFragment.kt new file mode 100644 index 00000000..b7fe562e --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/AddGroupMemberStepFragment.kt @@ -0,0 +1,178 @@ +package org.apache.fineract.ui.online.groups.creategroup + +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import butterknife.ButterKnife +import butterknife.OnClick +import butterknife.Optional +import com.stepstone.stepper.Step +import com.stepstone.stepper.VerificationError +import com.wajahatkarim3.easyvalidation.core.view_ktx.validator +import kotlinx.android.synthetic.main.fragment_step_add_group_member.* +import kotlinx.android.synthetic.main.fragment_step_add_group_member.view.* +import org.apache.fineract.R +import org.apache.fineract.ui.adapters.NameListAdapter +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseFragment +import org.apache.fineract.ui.online.groups.GroupAction +import org.apache.fineract.utils.Constants +import org.apache.fineract.utils.MaterialDialog +import org.apache.fineract.utils.Utils +import javax.inject.Inject + + +/* + * Created by saksham on 02/July/2019 +*/ + +class AddGroupMemberStepFragment : FineractBaseFragment(), Step, NameListAdapter.OnItemClickListener { + + lateinit var rootView: View + var members: ArrayList = ArrayList() + private var currentAction = GroupAction.CREATE + private var editItemPosition = 0 + private lateinit var groupAction: GroupAction + + @Inject + lateinit var nameLisAdapter: NameListAdapter + + companion object { + fun newInstance(groupAction: GroupAction) = AddGroupMemberStepFragment().apply { + val bundle = Bundle().apply { + putSerializable(Constants.GROUP_ACTION, groupAction) + } + arguments = bundle + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.getSerializable(Constants.GROUP_ACTION)?.let { + groupAction = it as GroupAction + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_step_add_group_member, container, false) + ButterKnife.bind(this, rootView) + (activity as FineractBaseActivity).activityComponent.inject(this) + rootView.rv_name.adapter = nameLisAdapter + nameLisAdapter.setOnItemClickListener(this) + if (groupAction == GroupAction.EDIT) { + showDataOnViews() + } + return rootView + } + + private fun showDataOnViews() { + val group = (activity as CreateGroupActivity).getGroup() + members = group.members as ArrayList + if (members.size == 0) { + showRecyclerView(false) + } else { + showRecyclerView(true) + } + nameLisAdapter.submitList(members) + } + + @Optional + @OnClick(R.id.ibAddMember) + fun showAddMemberView() { + showAddMemberView(GroupAction.CREATE, null) + } + + private fun showAddMemberView(action: GroupAction, name: String?) { + currentAction = action + llAddMember.visibility = View.VISIBLE + when (action) { + GroupAction.CREATE -> { + btnAddMember.text = getString(R.string.add) + } + GroupAction.EDIT -> { + etNewMember.setText(name) + btnAddMember.text = getString(R.string.update) + } + } + } + + @Optional + @OnClick(R.id.btnAddMember) + fun addMember() { + if (etNewMember.validator() + .nonEmpty() + .noNumbers() + .addErrorCallback { etNewMember.error = it }.check()) { + if (currentAction == GroupAction.CREATE) { + members.add(etNewMember.text.toString()) + } else { + members[editItemPosition] = etNewMember.text.toString() + } + etNewMember.text.clear() + llAddMember.visibility = View.GONE + Utils.hideKeyboard(context, etNewMember) + showRecyclerView(true) + nameLisAdapter.submitList(members) + } + } + + fun showRecyclerView(isShow: Boolean) { + if (isShow) { + rootView.rv_name.visibility = View.VISIBLE + rootView.tvAddedMember.visibility = View.GONE + } else { + rootView.rv_name.visibility = View.GONE + rootView.tvAddedMember.visibility = View.VISIBLE + } + + } + + @Optional + @OnClick(R.id.btnCancelAddMember) + fun cancelMemberAddition() { + etNewMember.text.clear() + llAddMember.visibility = View.GONE + } + + override fun onSelected() { + } + + override fun verifyStep(): VerificationError? { + if (members.size == 0) { + Toast.makeText(context, getString(R.string.error_group_atleast_1_member), Toast.LENGTH_SHORT).show() + return VerificationError("") + } + (activity as CreateGroupActivity).setMembers(members) + return null + } + + override fun onError(error: VerificationError) { + + } + + override fun onEditClicked(position: Int) { + editItemPosition = position + showAddMemberView(GroupAction.EDIT, members[position]) + } + + override fun onDeleteClicked(position: Int) { + MaterialDialog.Builder().init(context).apply { + setTitle(getString(R.string.dialog_title_confirm_deletion)) + setMessage(getString(R.string.dialog_message_confirm_name_deletion, members[position])) + setPositiveButton(getString(R.string.delete) + ) { dialog: DialogInterface?, _ -> + members.removeAt(position) + nameLisAdapter.submitList(members) + if (members.size == 0) { + showRecyclerView(false) + } + dialog?.dismiss() + } + setNegativeButton(getString(R.string.dialog_action_cancel)) + createMaterialDialog() + }.run { show() } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupActivity.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupActivity.kt new file mode 100644 index 00000000..00e5a8a7 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupActivity.kt @@ -0,0 +1,138 @@ +package org.apache.fineract.ui.online.groups.creategroup + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import com.stepstone.stepper.StepperLayout +import com.stepstone.stepper.VerificationError +import kotlinx.android.synthetic.main.activity_create_group.* +import org.apache.fineract.R +import org.apache.fineract.data.Status +import org.apache.fineract.data.models.Group +import org.apache.fineract.data.models.customer.Address +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.Toaster +import org.apache.fineract.ui.online.groups.GroupAction +import org.apache.fineract.ui.online.groups.grouplist.GroupViewModelFactory +import org.apache.fineract.ui.online.groups.grouplist.GroupViewModel +import org.apache.fineract.utils.Constants +import org.apache.fineract.utils.DateUtils +import javax.inject.Inject + +/* + * Created by saksham on 02/July/2019 +*/ + +class CreateGroupActivity : FineractBaseActivity(), StepperLayout.StepperListener { + + private var group = Group() + private var groupAction = GroupAction.CREATE + + @Inject + lateinit var groupViewModelFactory: GroupViewModelFactory + + lateinit var viewModel: GroupViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_create_group) + activityComponent.inject(this) + groupAction = intent.getSerializableExtra(Constants.GROUP_ACTION) as GroupAction + when (groupAction) { + GroupAction.CREATE -> { + setToolbarTitle(getString(R.string.create_group)) + } + GroupAction.EDIT -> { + setToolbarTitle(getString(R.string.edit_group)) + intent?.extras?.getParcelable(Constants.GROUP)?.let { + group = it + } + } + } + viewModel = ViewModelProviders.of(this, groupViewModelFactory).get(GroupViewModel::class.java) + subscribeUI() + showBackButton() + slCreateGroup.adapter = CreateGroupAdapter(supportFragmentManager, this, groupAction) + slCreateGroup.setOffscreenPageLimit(slCreateGroup.adapter.count) + slCreateGroup.setListener(this) + } + + override fun onStepSelected(newStepPosition: Int) {} + + override fun onError(verificationError: VerificationError?) {} + + override fun onReturn() {} + + private fun subscribeUI() { + viewModel.status.observe(this, Observer { status -> + when (status) { + Status.LOADING -> + if (groupAction == GroupAction.CREATE) { + showMifosProgressDialog(getString(R.string.create_group)) + } else { + showMifosProgressDialog(getString(R.string.updating_group_please_wait)) + } + Status.ERROR -> { + hideMifosProgressDialog() + if (groupAction == GroupAction.CREATE) { + Toaster.show(findViewById(android.R.id.content), R.string.error_while_creating_group, Toast.LENGTH_SHORT) + } else { + Toaster.show(findViewById(android.R.id.content), R.string.error_while_updating_group_status, Toast.LENGTH_SHORT) + } + } + Status.DONE -> { + hideMifosProgressDialog() + if (groupAction == GroupAction.CREATE) { + Toast.makeText(this, getString(R.string.group_identifier_updated_successfully, group.identifier), Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this, getString(R.string.group_identifier_updated_successfully, group.identifier), Toast.LENGTH_SHORT).show() + } + finish() + } + } + }) + } + + override fun onCompleted(completeButton: View?) { + when (groupAction) { + GroupAction.EDIT -> group.identifier?.let { + viewModel.updateGroup(it, group) + } + GroupAction.CREATE -> viewModel.createGroup(group) + } + } + + fun setGroupDetails(identifier: String, groupDefinitionIdentifier: String, name: String, office: String, assignedEmployee: String) { + group.identifier = identifier + group.groupDefinitionIdentifier = groupDefinitionIdentifier + group.name = name + group.office = office + group.assignedEmployee = assignedEmployee + group.weekday = DateUtils.getWeekDayIndex(DateUtils.getPresentDay()) + } + + fun setMembers(members: List) { + group.members = members + } + + fun setLeaders(leaders: List) { + group.leaders = leaders + } + + fun setGroupAddress(street: String, city: String, region: String, postalCode: String, country: String, countryCode: String?) { + val address = Address() + address.street = street + address.city = city + address.region = region + address.postalCode = postalCode + address.country = country + address.countryCode = countryCode + group.address = address + } + + fun getGroup(): Group { + return group + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupAdapter.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupAdapter.kt new file mode 100644 index 00000000..c90627a7 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/CreateGroupAdapter.kt @@ -0,0 +1,41 @@ +package org.apache.fineract.ui.online.groups.creategroup + +import android.content.Context +import androidx.fragment.app.FragmentManager +import com.stepstone.stepper.Step +import com.stepstone.stepper.adapter.AbstractFragmentStepAdapter +import com.stepstone.stepper.viewmodel.StepViewModel +import org.apache.fineract.R +import org.apache.fineract.ui.online.groups.GroupAction + + +/* + * Created by saksham on 02/July/2019 +*/ + +class CreateGroupAdapter constructor(fm: FragmentManager, + context: Context, + val groupAction: GroupAction) + : AbstractFragmentStepAdapter(fm, context) { + + private var createGroupSteps = context.resources.getStringArray(R.array.create_group_steps) + + override fun getCount(): Int { + return createGroupSteps.size + } + + override fun createStep(position: Int): Step? { + when (position) { + 0 -> return GroupDetailsStepFragment.newInstance(groupAction) + 1 -> return AddGroupMemberStepFragment.newInstance(groupAction) + 2 -> return AddGroupLeaderStepFragment.newInstance(groupAction) + 3 -> return GroupAddressStepFragment.newInstance(groupAction) + 4 -> return GroupReviewStepFragment.newInstance() + } + return null + } + + override fun getViewModel(position: Int): StepViewModel { + return StepViewModel.Builder(context).setTitle(createGroupSteps[position]).create() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupAddressStepFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupAddressStepFragment.kt new file mode 100644 index 00000000..533ffb55 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupAddressStepFragment.kt @@ -0,0 +1,154 @@ +package org.apache.fineract.ui.online.groups.creategroup + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import com.stepstone.stepper.Step +import com.stepstone.stepper.VerificationError +import com.wajahatkarim3.easyvalidation.core.rules.BaseRule +import com.wajahatkarim3.easyvalidation.core.view_ktx.validator +import kotlinx.android.synthetic.main.fragment_step_group_address.* +import org.apache.fineract.R +import org.apache.fineract.data.models.customer.Country +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseFragment +import org.apache.fineract.ui.online.groups.GroupAction +import org.apache.fineract.ui.online.groups.grouplist.GroupViewModelFactory +import org.apache.fineract.ui.online.groups.grouplist.GroupViewModel +import org.apache.fineract.utils.Constants +import javax.inject.Inject + + +/* + * Created by saksham on 03/July/2019 +*/ + +class GroupAddressStepFragment : FineractBaseFragment(), Step { + + lateinit var rootView: View + lateinit var countries: List + lateinit var viewModel: GroupViewModel + private lateinit var groupAction: GroupAction + + @Inject + lateinit var groupViewModelFactory: GroupViewModelFactory + + companion object { + fun newInstance(groupAction: GroupAction) = GroupAddressStepFragment().apply { + arguments = Bundle().apply { + putSerializable(Constants.GROUP_ACTION, groupAction) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.getSerializable(Constants.GROUP_ACTION)?.let { + groupAction = it as GroupAction + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_step_group_address, container, false) + (activity as FineractBaseActivity).activityComponent.inject(this) + viewModel = ViewModelProviders.of(this, + groupViewModelFactory).get(GroupViewModel::class.java) + return rootView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.getCountries().observe(this, Observer { + countries = it + etCountry.setAdapter(ArrayAdapter(context, android.R.layout.simple_list_item_1, viewModel.getCountryNames(it))) + }) + etCountry.threshold = 1 + + if (groupAction == GroupAction.EDIT) { + showDataOnViews() + } + } + + private fun showDataOnViews() { + val group = (activity as CreateGroupActivity).getGroup() + etStreet.setText(group.address?.street) + etCity.setText(group.address?.city) + etRegion.setText(group.address?.region) + etPostalCode.setText(group.address?.postalCode) + etCountry.setText(group.address?.country) + } + + override fun onSelected() { + + } + + override fun verifyStep(): VerificationError? { + if (!validateStreet() || !validateCity() || !validateRegion() || !validatePostalCode() || !validateCountry()) { + return VerificationError("") + } + (activity as CreateGroupActivity).setGroupAddress(etStreet.text.toString(), etCity.text.toString(), etRegion.text.toString(), + etPostalCode.text.toString(), etCountry.text.toString(), viewModel.getCountryCode(countries, etCountry.text.toString())) + return null + } + + override fun onError(error: VerificationError) { + + } + + private fun validateStreet(): Boolean { + return etStreet.validator() + .nonEmpty() + .addErrorCallback { + etStreet.error = it + } + .check() + } + + private fun validateCity(): Boolean { + return etCity.validator() + .nonEmpty() + .addErrorCallback { + etCity.error = it + }.check() + } + + private fun validateRegion(): Boolean { + return etRegion.validator() + .nonEmpty() + .addErrorCallback { + etRegion.error = it + }.check() + } + + private fun validatePostalCode(): Boolean { + return etPostalCode.validator() + .nonEmpty() + .onlyNumbers() + .addErrorCallback { + etPostalCode.error = it + }.check() + } + + private fun validateCountry(): Boolean { + return etCountry.validator() + .nonEmpty() + .noNumbers() + .addRule(object : BaseRule { + override fun validate(text: String): Boolean { + return viewModel.isCountryValid(countries, etCountry.text.toString()) + } + + override fun getErrorMessage(): String { + return getString(R.string.invalid_country) + } + }) + .addErrorCallback { + Toast.makeText(context, it, Toast.LENGTH_SHORT).show() + }.check() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupDetailsStepFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupDetailsStepFragment.kt new file mode 100644 index 00000000..ce57706c --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupDetailsStepFragment.kt @@ -0,0 +1,124 @@ +package org.apache.fineract.ui.online.groups.creategroup + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.stepstone.stepper.Step +import com.stepstone.stepper.VerificationError +import com.wajahatkarim3.easyvalidation.core.view_ktx.validator +import kotlinx.android.synthetic.main.fragment_step_group_details.* +import kotlinx.android.synthetic.main.fragment_step_group_details.view.* +import org.apache.fineract.R +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseFragment +import org.apache.fineract.ui.online.groups.GroupAction +import org.apache.fineract.utils.Constants + + +/* + * Created by saksham on 02/July/2019 +*/ + +class GroupDetailsStepFragment : FineractBaseFragment(), Step { + + lateinit var rootView: View + private lateinit var groupAction: GroupAction + + companion object { + fun newInstance(groupAction: GroupAction) = + GroupDetailsStepFragment().apply { + val bundle = Bundle().apply { + putSerializable(Constants.GROUP_ACTION, groupAction) + } + arguments = bundle + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.getSerializable(Constants.GROUP_ACTION)?.let { + groupAction = it as GroupAction + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_step_group_details, container, false) + if (groupAction == GroupAction.EDIT) { + showDataOnViews() + } + return rootView + } + + private fun showDataOnViews() { + val group = (activity as CreateGroupActivity).getGroup() + rootView.etIdentifier.setText(group.identifier) + rootView.etIdentifier.isEnabled = false + rootView.etGroupDefinitionIdentifier.setText(group.groupDefinitionIdentifier) + rootView.etName.setText(group.name) + rootView.etOffice.setText(group.office) + rootView.etAssignedEmployee.setText(group.assignedEmployee) + } + + override fun onSelected() {} + + override fun verifyStep(): VerificationError? { + if (!validateIdentifier() || !validateGroupDefinitionIdentifier() || !validateName() + || !validateOffice() || !validateAssignedEmployee()) { + return VerificationError(null) + } + (activity as CreateGroupActivity).setGroupDetails(etIdentifier.text.toString(), + etGroupDefinitionIdentifier.text.toString(), + etName.text.toString(), + etOffice.text.toString(), + etAssignedEmployee.text.toString()) + return null + } + + override fun onError(error: VerificationError) { + } + + private fun validateIdentifier(): Boolean { + return etIdentifier.validator() + .minLength(5) + .addErrorCallback { + etIdentifier.error = it + }.check() + } + + private fun validateGroupDefinitionIdentifier(): Boolean { + return etGroupDefinitionIdentifier.validator() + .minLength(5) + .addErrorCallback { + etGroupDefinitionIdentifier.error = it + }.check() + } + + private fun validateName(): Boolean { + return etName.validator() + .minLength(5) + .noNumbers() + .addErrorCallback { + etName.error = it + }.check() + } + + private fun validateOffice(): Boolean { + return etOffice.validator() + .minLength(5) + .noNumbers() + .addErrorCallback { + etOffice.error = it + }.check() + } + + private fun validateAssignedEmployee(): Boolean { + return etAssignedEmployee.validator() + .minLength(5) + .noNumbers() + .addErrorCallback { + etAssignedEmployee.error = it + } + .check() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupModelHelper.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupModelHelper.kt new file mode 100644 index 00000000..d3380f5a --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupModelHelper.kt @@ -0,0 +1,25 @@ +package org.apache.fineract.ui.online.groups.creategroup + + +/* + * Created by saksham on 04/July/2019 +*/ + +interface GroupModelHelper { + + interface GroupDetails { + + } + + interface GroupMemberDetails { + + } + + interface GroupLeaderDetails { + + } + + interface GroupAddress { + + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupReviewStepFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupReviewStepFragment.kt new file mode 100644 index 00000000..559f132b --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/creategroup/GroupReviewStepFragment.kt @@ -0,0 +1,84 @@ +package org.apache.fineract.ui.online.groups.creategroup + +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import androidx.recyclerview.widget.DividerItemDecoration +import com.stepstone.stepper.Step +import com.stepstone.stepper.VerificationError +import kotlinx.android.synthetic.main.fragment_step_add_group_member.view.* +import kotlinx.android.synthetic.main.fragment_step_group_review.* +import kotlinx.android.synthetic.main.fragment_step_group_review.view.* +import org.apache.fineract.R +import org.apache.fineract.data.models.Group +import org.apache.fineract.ui.adapters.NameListAdapter +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseFragment +import javax.inject.Inject + + +/* + * Created by saksham on 04/July/2019 +*/ + +class GroupReviewStepFragment : FineractBaseFragment(), Step { + + lateinit var rootView: View + + @Inject + lateinit var memberNameAdapter: NameListAdapter + + @Inject + lateinit var leaderNameAdapter: NameListAdapter + + companion object { + fun newInstance(): GroupReviewStepFragment { + return GroupReviewStepFragment() + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_step_group_review, container, false) + (activity as FineractBaseActivity).activityComponent.inject(this) + rootView.rv_leaders.adapter = leaderNameAdapter + leaderNameAdapter.setReview(true) + rootView.rv_members.adapter = memberNameAdapter + memberNameAdapter.setReview(true) + + return rootView + } + + private fun populateView(group: Group) { + tvIdentifier.text = group.identifier + tvGroupDefinitionIdentifier.text = group.groupDefinitionIdentifier + tvName.text = group.name + tvOffice.text = group.office + tvAssignedEmployee.text = group.assignedEmployee + group.members?.let { + memberNameAdapter.submitList(it as ArrayList) + } + group.leaders?.let { + leaderNameAdapter.submitList(it as ArrayList) + } + tvStreet.text = group.address?.street + tvCity.text = group.address?.city + tvRegion.text = group.address?.region + tvPostalCode.text = group.address?.postalCode + tvCountry.text = group.address?.country + } + + override fun onSelected() { + populateView((activity as CreateGroupActivity).getGroup()) + } + + override fun verifyStep(): VerificationError? { + return null + } + + override fun onError(error: VerificationError) {} +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsActivity.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsActivity.kt new file mode 100644 index 00000000..0300dd2e --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsActivity.kt @@ -0,0 +1,26 @@ +package org.apache.fineract.ui.online.groups.groupdetails + +import android.os.Bundle +import android.os.PersistableBundle +import android.util.Log +import android.widget.Toast +import org.apache.fineract.R +import org.apache.fineract.data.models.Group +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.utils.Constants + + +/* + * Created by saksham on 21/June/2019 +*/ + +class GroupDetailsActivity : FineractBaseActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_toolbar_container) + + replaceFragment(GroupDetailsFragment.newInstance(intent.getParcelableExtra(Constants.GROUP)), false, R.id.container) + showBackButton() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsFragment.kt new file mode 100644 index 00000000..7095ac0f --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/groupdetails/GroupDetailsFragment.kt @@ -0,0 +1,133 @@ +package org.apache.fineract.ui.online.groups.groupdetails + +import android.content.Intent +import android.os.Bundle +import android.view.* +import butterknife.ButterKnife +import butterknife.OnClick +import kotlinx.android.synthetic.main.fragment_groups_details.* +import kotlinx.android.synthetic.main.fragment_groups_details.view.* +import org.apache.fineract.R +import org.apache.fineract.data.models.Group +import org.apache.fineract.ui.adapters.NameListAdapter +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseFragment +import org.apache.fineract.ui.online.groups.GroupAction +import org.apache.fineract.ui.online.groups.creategroup.CreateGroupActivity +import org.apache.fineract.ui.online.groups.grouptasks.GroupTasksBottomSheetFragment +import org.apache.fineract.ui.views.CircularImageView +import org.apache.fineract.utils.Constants +import org.apache.fineract.utils.DateUtils +import org.apache.fineract.utils.Utils +import javax.inject.Inject + + +/* + * Created by saksham on 22/June/2019 +*/ + +class GroupDetailsFragment : FineractBaseFragment() { + + lateinit var rootView: View + lateinit var group: Group + + @Inject + lateinit var membersNameAdapter: NameListAdapter + + @Inject + lateinit var leadersNameAdapter: NameListAdapter + + companion object { + fun newInstance(group: Group): GroupDetailsFragment { + val fragment = GroupDetailsFragment() + val args = Bundle() + args.putParcelable(Constants.GROUP, group) + fragment.arguments = args + return fragment + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + group = arguments?.get(Constants.GROUP) as Group + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_groups_details, container, false) + (activity as FineractBaseActivity).activityComponent.inject(this) + ButterKnife.bind(this, rootView) + rootView.rv_members.adapter = membersNameAdapter + membersNameAdapter.setReview(true) + rootView.rv_leaders.adapter = leadersNameAdapter + leadersNameAdapter.setReview(true) + setToolbarTitle(group.identifier) + setHasOptionsMenu(true) + return rootView + } + + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + Utils.setToolbarIconColor(context, menu, R.color.white) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_group_detials, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_edit_group -> { + val intent = Intent(activity, CreateGroupActivity::class.java).apply { + putExtra(Constants.GROUP, group) + putExtra(Constants.GROUP_ACTION, GroupAction.EDIT) + } + startActivity(intent) + } + } + return super.onOptionsItemSelected(item) + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + tvIdentifier.text = group.identifier + tvGroupId.text = group.groupDefinitionIdentifier + tvName.text = group.name + tvStatus.text = group.status?.name + setGroupStatusCircularIcon(group.status, civStatus) + group.leaders?.let { + leadersNameAdapter.submitList(it as ArrayList) + } + group.members?.let { + membersNameAdapter.submitList(it as ArrayList) + } + tvOffice.text = group.office + tvAssignedEmployee.text = group.assignedEmployee + tvWeekday.text = DateUtils.getWeekDay(group.weekday!!) + tvStreet.text = group.address?.street + tvCity.text = group.address?.city + tvRegion.text = group.address?.region + tvPostalCode.text = group.address?.postalCode + tvCountry.text = group.address?.country + } + + @OnClick(R.id.cv_tasks) + fun onTasksCardViewClicked() { + val bottomSheet = GroupTasksBottomSheetFragment(group) + bottomSheet.show(childFragmentManager, getString(R.string.tasks)) + } + + private fun setGroupStatusCircularIcon(status: Group.Status?, civStatus: CircularImageView) { + when (status) { + Group.Status.PENDING -> { + civStatus.setImageDrawable(Utils.setCircularBackground(R.color.blue, context)) + } + Group.Status.ACTIVE -> { + civStatus.setImageDrawable(Utils.setCircularBackground(R.color.deposit_green, context)) + } + Group.Status.CLOSED -> { + civStatus.setImageDrawable(Utils.setCircularBackground(R.color.red_dark, context)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupListFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupListFragment.kt new file mode 100644 index 00000000..448554b6 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupListFragment.kt @@ -0,0 +1,133 @@ +package org.apache.fineract.ui.online.groups.grouplist + +import android.app.SearchManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.text.TextUtils +import android.view.* +import androidx.appcompat.widget.SearchView +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import butterknife.ButterKnife +import butterknife.OnClick +import kotlinx.android.synthetic.main.fragment_group_list.* +import org.apache.fineract.R +import org.apache.fineract.data.models.Group +import org.apache.fineract.ui.adapters.GroupsAdapter +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseFragment +import org.apache.fineract.ui.base.OnItemClickListener +import org.apache.fineract.ui.online.groups.GroupAction +import org.apache.fineract.ui.online.groups.creategroup.CreateGroupActivity +import org.apache.fineract.ui.online.groups.groupdetails.GroupDetailsActivity +import org.apache.fineract.utils.Constants +import javax.inject.Inject + + +/* + * Created by saksham on 15/June/2019 +*/ + +class GroupListFragment : FineractBaseFragment(), OnItemClickListener { + + lateinit var rootView: View + + lateinit var viewModel: GroupViewModel + + @Inject + lateinit var adapter: GroupsAdapter + + @Inject + lateinit var groupViewModelFactory: GroupViewModelFactory + + lateinit var groupList: ArrayList + + val searchedGroup: (ArrayList) -> Unit = { groups -> + adapter.setGroupList(groups) + } + + companion object { + fun newInstance(): GroupListFragment { + return GroupListFragment() + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_group_list, container, false) + (activity as FineractBaseActivity).activityComponent.inject(this) + viewModel = ViewModelProviders.of(this, + groupViewModelFactory).get(GroupViewModel::class.java) + return rootView + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + ButterKnife.bind(this, rootView) + adapter.setItemClickListener(this) + + rvGroups.adapter = adapter + rvGroups.layoutManager = LinearLayoutManager(context) + viewModel.getGroups().observe(this, + + Observer { + groupList = it + adapter.setGroupList(it) + }) + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_group_search, menu) + + val searchManager = activity?.getSystemService(Context.SEARCH_SERVICE) as SearchManager + val searchView = menu.findItem(R.id.group_search).actionView as SearchView + + searchView.setSearchableInfo(searchManager.getSearchableInfo(activity?.componentName)) + + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + viewModel.searchGroup(groupList, query, searchedGroup) + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + if (TextUtils.isEmpty(newText)) { + adapter.setGroupList(groupList) + } + viewModel.searchGroup(groupList, newText, searchedGroup) + return true + } + }) + } + + override fun onItemClick(childView: View?, position: Int) { + val intent = Intent(context, GroupDetailsActivity::class.java).apply { + putExtra(Constants.GROUP, groupList[position]) + } + startActivity(intent) + } + + override fun onItemLongPress(childView: View?, position: Int) { + } + + @OnClick(R.id.fabAddGroup) + fun addGroup() { + val intent = Intent(activity, CreateGroupActivity::class.java).apply { + putExtra(Constants.GROUP_ACTION, GroupAction.CREATE) + } + startActivity(intent) + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModel.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModel.kt new file mode 100644 index 00000000..6f9f159a --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModel.kt @@ -0,0 +1,130 @@ +package org.apache.fineract.ui.online.groups.grouplist + +import android.annotation.SuppressLint +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.functions.Predicate +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* +import org.apache.fineract.data.Status +import org.apache.fineract.data.datamanager.api.DataManagerAnonymous +import org.apache.fineract.data.datamanager.api.DataManagerGroups +import org.apache.fineract.data.models.Group +import org.apache.fineract.data.models.customer.Command +import org.apache.fineract.data.models.customer.Country + +/* + * Created by saksham on 15/June/2019 +*/ + +class GroupViewModel constructor(val dataManagerGroups: DataManagerGroups, val dataManagerAnonymous: DataManagerAnonymous) : ViewModel() { + + lateinit var groupsList: MutableLiveData> + private var viewModelJob = Job() + // Create a Coroutine scope using a job to be able to cancel when needed + private val uiScope = CoroutineScope(viewModelJob + Dispatchers.IO) + private var _status = MutableLiveData() + val status: LiveData + get() = _status + + fun getGroups(): MutableLiveData> { + groupsList = dataManagerGroups.getGroups() + return groupsList + } + + fun searchGroup(groups: ArrayList, query: String, searchedGroup: (ArrayList) -> Unit) { + searchedGroup(ArrayList(Observable.fromIterable(groups).filter(object : Predicate { + override fun test(group: Group): Boolean { + return group.identifier?.toLowerCase()?.contains(query.toLowerCase()).toString().toBoolean() + } + }).toList().blockingGet())) + } + + @SuppressLint("CheckResult") + fun getCountries(): MutableLiveData> { + val countries = MutableLiveData>() + dataManagerAnonymous.countries.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + countries.value = it + } + return countries + } + + fun createGroup(group: Group) { + uiScope.launch { + withContext(Dispatchers.Main) { + try { + _status.value = Status.LOADING + dataManagerGroups.createGroup(group).await() + _status.value = Status.DONE + } catch (e: Exception) { + _status.value = Status.ERROR + } + } + } + } + + fun updateGroup(identifier: String, group: Group) { + uiScope.launch { + withContext(Dispatchers.Main) { + try { + _status.value = Status.LOADING + dataManagerGroups.updateGroup(identifier, group).await() + _status.value = Status.DONE + } catch (e: Exception) { + _status.value = Status.ERROR + } + } + } + } + + fun changeGroupStatus(identifier: String, command: Command) { + uiScope.launch { + withContext(Dispatchers.Main) { + try { + _status.value = Status.LOADING + dataManagerGroups.changeGroupStatus(identifier, command).await() + _status.value = Status.DONE + } catch (e: Exception) { + _status.value = Status.ERROR + } + } + } + } + + fun getCountryNames(countries: List): List { + return Observable.fromIterable(countries).map { country: Country -> country.name }.toList().blockingGet() + } + + fun getCountryCode(countries: List, countryName: String): String? { + for (country in countries) { + if (country.name.equals(countryName)) { + return country.alphaCode + } + } + return null + } + + fun isCountryValid(countries: List, countryName: String): Boolean { + for (country in countries) { + if (country.name.equals(countryName)) { + return true + } + } + return false + } + + /** + * When the [ViewModel] is finished, we cancel our coroutine [viewModelJob], which tells the + * Retrofit service to stop. + */ + override fun onCleared() { + super.onCleared() + viewModelJob.cancel() + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModelFactory.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModelFactory.kt new file mode 100644 index 00000000..f541a374 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/grouplist/GroupViewModelFactory.kt @@ -0,0 +1,25 @@ +package org.apache.fineract.ui.online.groups.grouplist + +import android.content.Context +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import org.apache.fineract.data.datamanager.api.DataManagerAnonymous +import org.apache.fineract.data.datamanager.api.DataManagerGroups +import org.apache.fineract.injection.ApplicationContext +import javax.inject.Inject + + +/* + * Created by saksham on 16/June/2019 +*/ + + +class GroupViewModelFactory @Inject constructor(@ApplicationContext var context: Context, + private val dataManagerGroups: DataManagerGroups, + private val dataManagerAnonymous: DataManagerAnonymous) + : ViewModelProvider.NewInstanceFactory() { + + override fun create(modelClass: Class): T { + return GroupViewModel(dataManagerGroups, dataManagerAnonymous) as T + } +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/ui/online/groups/grouptasks/GroupTasksBottomSheetFragment.kt b/app/src/main/java/org/apache/fineract/ui/online/groups/grouptasks/GroupTasksBottomSheetFragment.kt new file mode 100644 index 00000000..76c601d1 --- /dev/null +++ b/app/src/main/java/org/apache/fineract/ui/online/groups/grouptasks/GroupTasksBottomSheetFragment.kt @@ -0,0 +1,144 @@ +package org.apache.fineract.ui.online.groups.grouptasks + +import android.app.Dialog +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import butterknife.ButterKnife +import butterknife.OnClick +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import kotlinx.android.synthetic.main.fragment_group_tasks_bottom_sheet.view.* +import org.apache.fineract.R +import org.apache.fineract.data.Status +import org.apache.fineract.data.models.Group +import org.apache.fineract.data.models.customer.Command +import org.apache.fineract.ui.base.FineractBaseActivity +import org.apache.fineract.ui.base.FineractBaseBottomSheetDialogFragment +import org.apache.fineract.ui.base.Toaster +import org.apache.fineract.ui.online.groups.grouplist.GroupViewModel +import org.apache.fineract.ui.online.groups.grouplist.GroupViewModelFactory +import javax.inject.Inject + +/** + * Created by Ahmad Jawid Muhammadi on 04/April/2020 + */ +class GroupTasksBottomSheetFragment(val group: Group) : FineractBaseBottomSheetDialogFragment() { + + lateinit var rootView: View + private var command = Command() + lateinit var behavior: BottomSheetBehavior<*> + private lateinit var viewModel: GroupViewModel + + @Inject + lateinit var viewModelFactory: GroupViewModelFactory + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog + rootView = View.inflate(context, R.layout.fragment_group_tasks_bottom_sheet, null) + dialog.setContentView(rootView) + behavior = BottomSheetBehavior.from(rootView.parent as View) + (activity as FineractBaseActivity).activityComponent.inject(this) + ButterKnife.bind(this, rootView) + viewModel = ViewModelProviders.of(this, viewModelFactory).get(GroupViewModel::class.java) + setDataOnViews() + + subscribeUI() + return dialog + } + + private fun subscribeUI() { + viewModel.status.observe(this, Observer { status -> + when (status) { + Status.LOADING -> { + showMifosProgressDialog(getString(R.string.please_wait_updating_group_status)) + } + Status.ERROR -> { + hideMifosProgressDialog() + Toaster.show(rootView, R.string.error_while_updating_group_status, Toast.LENGTH_SHORT) + } + Status.DONE -> { + Toaster.show(rootView, getString(R.string.group_identifier_updated_successfully, group.identifier), Toast.LENGTH_SHORT) + hideMifosProgressDialog() + dismiss() + } + } + }) + } + + private fun setDataOnViews() { + when (group.status) { + Group.Status.ACTIVE -> { + rootView.iv_task.setImageDrawable( + ContextCompat.getDrawable(activity!!, R.drawable.ic_close_black_24dp)) + rootView.iv_task.setColorFilter(ContextCompat.getColor(activity!!, R.color.red_dark)) + rootView.tv_task.text = getString(R.string.close) + } + Group.Status.PENDING -> { + rootView.iv_task.setImageDrawable(ContextCompat.getDrawable(activity!!, + R.drawable.ic_check_circle_black_24dp)) + rootView.iv_task.setColorFilter(ContextCompat.getColor(activity!!, R.color.status)) + rootView.tv_task.text = getString(R.string.activate) + } + Group.Status.CLOSED -> { + rootView.iv_task.setImageDrawable(ContextCompat.getDrawable(activity!!, + R.drawable.ic_check_circle_black_24dp)) + rootView.iv_task.setColorFilter(ContextCompat.getColor(activity!!, R.color.status)) + rootView.tv_task.text = getString(R.string.reopen) + } + } + } + + @OnClick(R.id.iv_task) + fun onTaskImageViewClicked() { + when (group.status) { + Group.Status.ACTIVE -> { + command.action = Command.Action.CLOSE + rootView.tv_header.text = getString(R.string.close) + rootView.tv_sub_header.text = getString(R.string.please_verify_following_group_task, getString(R.string.lock)) + rootView.btn_submit_task.text = getString(R.string.close) + } + Group.Status.PENDING -> { + command.action = Command.Action.ACTIVATE + rootView.tv_header.text = getString(R.string.activate) + rootView.tv_sub_header.text = getString(R.string.please_verify_following_group_task, getString(R.string.activate)) + rootView.btn_submit_task.text = getString(R.string.activate) + } + Group.Status.CLOSED -> { + command.action = Command.Action.REOPEN + rootView.tv_header.text = getString(R.string.reopen) + rootView.tv_sub_header.text = getString(R.string.please_verify_following_group_task, getString(R.string.reopen)) + rootView.btn_submit_task.text = getString(R.string.reopen) + } + } + rootView.ll_task_list.visibility = View.GONE + rootView.ll_task_form.visibility = View.VISIBLE + } + + @OnClick(R.id.btn_submit_task) + fun submitTask() { + command.comment = rootView.et_comment.text.toString().trim { it <= ' ' } + rootView.et_comment.isEnabled = false + group.identifier?.let { + viewModel.changeGroupStatus(it, command) + } + } + + @OnClick(R.id.btn_cancel) + fun onCancel() { + dismiss() + } + + override fun onStart() { + super.onStart() + behavior?.state = BottomSheetBehavior.STATE_EXPANDED + } + + override fun onDestroy() { + super.onDestroy() + hideMifosProgressBar() + } +} diff --git a/app/src/main/java/org/apache/fineract/utils/Constants.kt b/app/src/main/java/org/apache/fineract/utils/Constants.kt new file mode 100644 index 00000000..6d9dca5a --- /dev/null +++ b/app/src/main/java/org/apache/fineract/utils/Constants.kt @@ -0,0 +1,11 @@ +package org.apache.fineract.utils + + +/* + * Created by saksham on 23/June/2019 +*/ + +object Constants { + const val GROUP = "group" + const val GROUP_ACTION = "group_action" +} \ No newline at end of file diff --git a/app/src/main/java/org/apache/fineract/utils/DateUtils.java b/app/src/main/java/org/apache/fineract/utils/DateUtils.java index 172bfef0..a5826537 100644 --- a/app/src/main/java/org/apache/fineract/utils/DateUtils.java +++ b/app/src/main/java/org/apache/fineract/utils/DateUtils.java @@ -11,7 +11,7 @@ /** * @author Rajan Maurya - * On 06/07/17. + * On 06/07/17. */ public class DateUtils { @@ -90,4 +90,48 @@ public static String convertServerDate(Calendar calendar) { Locale.ENGLISH); return simpleDateFormat.format(calendar.getTime()); } + + public static String getWeekDay(int index) { + switch (index) { + case 1: + return "Monday"; + case 2: + return "Tuesday"; + case 3: + return "Wednesday"; + case 4: + return "Thursday"; + case 5: + return "Friday"; + case 6: + return "Saturday"; + case 7: + return "Sunday"; + } + return null; + } + + public static int getWeekDayIndex(String weekday) { + switch (weekday) { + case "Monday": + return 1; + case "Tuesday": + return 2; + case "Wednesday": + return 3; + case "Thursday": + return 4; + case "Friday": + return 5; + case "Saturday": + return 6; + case "Sunday": + return 7; + } + return 0; + } + + public static String getPresentDay() { + return new SimpleDateFormat("EEEE").format(new Date()); + } } diff --git a/app/src/main/java/org/apache/fineract/utils/StatusUtils.java b/app/src/main/java/org/apache/fineract/utils/StatusUtils.java index 3cd120fa..bbfe894f 100644 --- a/app/src/main/java/org/apache/fineract/utils/StatusUtils.java +++ b/app/src/main/java/org/apache/fineract/utils/StatusUtils.java @@ -1,13 +1,16 @@ package org.apache.fineract.utils; import android.content.Context; + import androidx.core.content.ContextCompat; import androidx.appcompat.widget.AppCompatImageView; + import android.widget.ImageView; import com.google.gson.annotations.SerializedName; import org.apache.fineract.R; +import org.apache.fineract.data.models.Group; import org.apache.fineract.data.models.accounts.AccountType; import org.apache.fineract.data.models.customer.Command; import org.apache.fineract.data.models.customer.Customer; @@ -22,7 +25,7 @@ public class StatusUtils { public static void setCustomerStatus(Customer.State state, AppCompatImageView imageView, - Context context) { + Context context) { switch (state) { case ACTIVE: imageView.setColorFilter( @@ -44,7 +47,7 @@ public static void setCustomerStatus(Customer.State state, AppCompatImageView im } public static void setTellerStatus(Teller.State state, AppCompatImageView imageView, - Context context) { + Context context) { switch (state) { case ACTIVE: imageView.setColorFilter( @@ -66,7 +69,7 @@ public static void setTellerStatus(Teller.State state, AppCompatImageView imageV } public static void setCustomerStatusIcon(Customer.State state, ImageView imageView, - Context context) { + Context context) { switch (state) { case ACTIVE: imageView.setImageDrawable(ContextCompat.getDrawable(context, @@ -96,7 +99,7 @@ public static void setCustomerStatusIcon(Customer.State state, ImageView imageVi } public static void setLoanAccountStatus(LoanAccount.State state, AppCompatImageView imageView, - Context context) { + Context context) { switch (state) { case CREATED: imageView.setColorFilter( @@ -125,8 +128,24 @@ public static void setLoanAccountStatus(LoanAccount.State state, AppCompatImageV } } + public static void setGroupsStatusIcon(Group.Status state, ImageView imageView, + Context context) { + switch (state) { + + case PENDING: + imageView.setColorFilter(ContextCompat.getColor(context, R.color.blue)); + break; + case ACTIVE: + imageView.setColorFilter(ContextCompat.getColor(context, R.color.deposit_green)); + break; + case CLOSED: + imageView.setColorFilter(ContextCompat.getColor(context, R.color.red_dark)); + break; + } + } + public static void setLoanAccountStatusIcon(LoanAccount.State state, ImageView imageView, - Context context) { + Context context) { switch (state) { case ACTIVE: imageView.setImageDrawable(ContextCompat.getDrawable(context, @@ -162,7 +181,7 @@ public static void setLoanAccountStatusIcon(LoanAccount.State state, ImageView i } public static void setDepositAccountStatusIcon(DepositAccount.State state, ImageView imageView, - Context context) { + Context context) { switch (state) { case ACTIVE: imageView.setImageDrawable(ContextCompat.getDrawable(context, @@ -222,7 +241,7 @@ public enum Action { } public static void setCustomerActivitiesStatusIcon(Command.Action action, ImageView imageView, - Context context) { + Context context) { switch (action) { case ACTIVATE: imageView.setImageDrawable(ContextCompat.getDrawable(context, @@ -259,7 +278,7 @@ public static void setCustomerActivitiesStatusIcon(Command.Action action, ImageV } public static void setAccountType(AccountType action, ImageView imageView, - Context context) { + Context context) { switch (action) { case ASSET: imageView.setColorFilter( diff --git a/app/src/main/java/org/apache/fineract/utils/Utils.java b/app/src/main/java/org/apache/fineract/utils/Utils.java index b0b8326a..035959fe 100644 --- a/app/src/main/java/org/apache/fineract/utils/Utils.java +++ b/app/src/main/java/org/apache/fineract/utils/Utils.java @@ -1,19 +1,28 @@ package org.apache.fineract.utils; +import android.app.Activity; import android.content.Context; import android.graphics.PorterDuff; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; + import androidx.core.content.ContextCompat; + +import android.graphics.drawable.LayerDrawable; import android.view.Menu; +import android.view.View; +import android.view.inputmethod.InputMethodManager; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import org.apache.fineract.R; + import java.util.Locale; /** * @author Rajan Maurya - * On 30/07/17. + * On 30/07/17. */ public class Utils { @@ -36,4 +45,17 @@ public static void setToolbarIconColor(Context context, Menu menu, int color) { } } } + + public static LayerDrawable setCircularBackground(int colorId, Context context) { + Drawable color = new ColorDrawable(ContextCompat.getColor(context, colorId)); + Drawable image = ContextCompat.getDrawable(context, R.drawable.circular_background); + LayerDrawable ld = new LayerDrawable(new Drawable[]{image, color}); + return ld; + } + + public static void hideKeyboard(Context context, View view) { + InputMethodManager imm = (InputMethodManager) context.getSystemService( + Activity.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } } diff --git a/app/src/main/java/org/apache/fineract/utils/ValidateIdentifierUtil.java b/app/src/main/java/org/apache/fineract/utils/ValidateIdentifierUtil.java index ae71bfe2..be0e216c 100644 --- a/app/src/main/java/org/apache/fineract/utils/ValidateIdentifierUtil.java +++ b/app/src/main/java/org/apache/fineract/utils/ValidateIdentifierUtil.java @@ -27,7 +27,7 @@ private static boolean validate(Context context, String string, TextInputLayout textInputLayout) { if (string.length() < 3) { showTextInputLayoutError(textInputLayout, - context.getString(R.string.must_be_at_least_three_characters, 3)); + context.getString(R.string.must_be_at_least_n_characters, 3)); return false; } diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 00000000..3a178915 --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circular_background.xml b/app/src/main/res/drawable/circular_background.xml new file mode 100644 index 00000000..c11f653c --- /dev/null +++ b/app/src/main/res/drawable/circular_background.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_teller_black_24dp.xml b/app/src/main/res/drawable/ic_teller_black_24dp.xml new file mode 100644 index 00000000..bd452ec2 --- /dev/null +++ b/app/src/main/res/drawable/ic_teller_black_24dp.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create_group.xml b/app/src/main/res/layout/activity_create_group.xml new file mode 100644 index 00000000..92bbd872 --- /dev/null +++ b/app/src/main/res/layout/activity_create_group.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_group_list.xml b/app/src/main/res/layout/fragment_group_list.xml new file mode 100644 index 00000000..b4313606 --- /dev/null +++ b/app/src/main/res/layout/fragment_group_list.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_group_tasks_bottom_sheet.xml b/app/src/main/res/layout/fragment_group_tasks_bottom_sheet.xml new file mode 100644 index 00000000..e2f4442d --- /dev/null +++ b/app/src/main/res/layout/fragment_group_tasks_bottom_sheet.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +