mirror of
https://github.com/openMF/mobile-wallet.git
synced 2026-02-06 11:07:02 +00:00
Move Merchants to feature module (#1669)
Co-authored-by: Rajan Maurya <therajanmaurya@users.noreply.github.com>
This commit is contained in:
parent
5133f20c80
commit
b07241d2cb
1
feature/merchants/.gitignore
vendored
Normal file
1
feature/merchants/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
19
feature/merchants/build.gradle.kts
Normal file
19
feature/merchants/build.gradle.kts
Normal file
@ -0,0 +1,19 @@
|
||||
plugins {
|
||||
alias(libs.plugins.mifospay.android.feature)
|
||||
alias(libs.plugins.mifospay.android.library.compose)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "org.mifospay.feature.merchants"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.data)
|
||||
implementation(libs.compose.material)
|
||||
|
||||
//Todo: Remove these after migration of MerchantTransferActivity
|
||||
implementation("com.jakewharton:butterknife-annotations:10.2.3")
|
||||
implementation("com.jakewharton:butterknife:10.2.3@aar")
|
||||
implementation("com.mifos.mobile:mifos-passcode:0.3.0@aar")
|
||||
implementation(project(":mifospay"))
|
||||
}
|
||||
0
feature/merchants/consumer-rules.pro
Normal file
0
feature/merchants/consumer-rules.pro
Normal file
21
feature/merchants/proguard-rules.pro
vendored
Normal file
21
feature/merchants/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@ -0,0 +1,24 @@
|
||||
package org.mifospay.feature.merchants
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("org.mifospay.feature.merchants.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
4
feature/merchants/src/main/AndroidManifest.xml
Normal file
4
feature/merchants/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,284 @@
|
||||
package org.mifospay.feature.merchants
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.mifospay.core.model.entity.accounts.savings.SavingsWithAssociations
|
||||
import org.mifospay.R
|
||||
import org.mifospay.common.Constants
|
||||
import org.mifospay.core.designsystem.component.MfLoadingWheel
|
||||
import org.mifospay.core.ui.EmptyContentScreen
|
||||
import org.mifospay.theme.MifosTheme
|
||||
|
||||
@Composable
|
||||
fun MerchantScreen(
|
||||
viewModel: MerchantViewModel = hiltViewModel()
|
||||
) {
|
||||
val merchantUiState by viewModel.merchantUiState.collectAsStateWithLifecycle()
|
||||
val merchantsListUiState by viewModel.merchantsListUiState.collectAsStateWithLifecycle()
|
||||
val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle()
|
||||
|
||||
MerchantScreen(
|
||||
merchantUiState = merchantUiState,
|
||||
merchantListUiState = merchantsListUiState,
|
||||
updateQuery = { viewModel.updateSearchQuery(it) },
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = { viewModel.refresh() },
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun MerchantScreen(
|
||||
merchantUiState: MerchantUiState,
|
||||
merchantListUiState: MerchantUiState,
|
||||
updateQuery: (String) -> Unit,
|
||||
isRefreshing: Boolean,
|
||||
onRefresh: () -> Unit,
|
||||
) {
|
||||
val pullRefreshState = rememberPullRefreshState(isRefreshing, onRefresh)
|
||||
Box(Modifier.pullRefresh(pullRefreshState)) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
when (merchantUiState) {
|
||||
MerchantUiState.Empty -> {
|
||||
EmptyContentScreen(
|
||||
modifier = Modifier,
|
||||
title = stringResource(id = R.string.empty_no_merchants_title),
|
||||
subTitle = stringResource(id = R.string.empty_no_merchants_subtitle),
|
||||
iconTint = Color.Black,
|
||||
iconImageVector = Icons.Rounded.Info
|
||||
)
|
||||
}
|
||||
|
||||
is MerchantUiState.Error -> {
|
||||
EmptyContentScreen(
|
||||
modifier = Modifier,
|
||||
title = stringResource(id = R.string.error_oops),
|
||||
subTitle = stringResource(id = R.string.unexpected_error_subtitle),
|
||||
iconTint = Color.Black,
|
||||
iconImageVector = Icons.Rounded.Info
|
||||
)
|
||||
}
|
||||
|
||||
MerchantUiState.Loading -> {
|
||||
MfLoadingWheel(
|
||||
contentDesc = stringResource(R.string.loading),
|
||||
backgroundColor = Color.White
|
||||
)
|
||||
}
|
||||
|
||||
is MerchantUiState.ShowMerchants -> {
|
||||
MerchantScreenContent(
|
||||
merchantList = (merchantListUiState as MerchantUiState.ShowMerchants).merchants,
|
||||
updateQuery = updateQuery
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
PullRefreshIndicator(
|
||||
refreshing = isRefreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MerchantScreenContent(
|
||||
merchantList: List<SavingsWithAssociations>,
|
||||
updateQuery: (String) -> Unit
|
||||
) {
|
||||
val query by rememberSaveable { mutableStateOf("") }
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Column {
|
||||
SearchBarScreen(
|
||||
query = query,
|
||||
onQueryChange = { q ->
|
||||
updateQuery(q)
|
||||
},
|
||||
onSearch = {},
|
||||
onClearQuery = { updateQuery("") }
|
||||
)
|
||||
MerchantList(merchantList = merchantList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MerchantList(
|
||||
merchantList: List<SavingsWithAssociations>
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
items(merchantList.size) { index ->
|
||||
MerchantsItem(savingsWithAssociations = merchantList[index],
|
||||
onMerchantClicked = {
|
||||
val intent = Intent(context, MerchantTransferActivity::class.java)
|
||||
intent.putExtra(Constants.MERCHANT_NAME, merchantList[index].clientName)
|
||||
intent.putExtra(Constants.MERCHANT_VPA, merchantList[index].externalId)
|
||||
intent.putExtra(Constants.MERCHANT_ACCOUNT_NO, merchantList[index].accountNo)
|
||||
context.startActivity(intent)
|
||||
},
|
||||
onMerchantLongPressed = {
|
||||
clipboardManager.setText(AnnotatedString(it ?: ""))
|
||||
Toast.makeText(context, R.string.vpa_copy_success, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SearchBarScreen(
|
||||
query: String,
|
||||
onQueryChange: (String) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onClearQuery: () -> Unit
|
||||
) {
|
||||
SearchBar(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp, horizontal = 16.dp),
|
||||
query = query,
|
||||
onQueryChange = onQueryChange,
|
||||
onSearch = onSearch,
|
||||
active = false,
|
||||
onActiveChange = { },
|
||||
placeholder = {
|
||||
Text(text = stringResource(R.string.search))
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Search,
|
||||
contentDescription = stringResource(R.string.search)
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(
|
||||
onClick = onClearQuery
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Close,
|
||||
contentDescription = stringResource(R.string.close)
|
||||
)
|
||||
}
|
||||
}
|
||||
) {}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MerchantLoadingPreview() {
|
||||
MifosTheme {
|
||||
MerchantScreen(merchantUiState = MerchantUiState.Loading,
|
||||
merchantListUiState = MerchantUiState.ShowMerchants(sampleMerchantList),
|
||||
updateQuery = {},
|
||||
false, {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MerchantListPreview() {
|
||||
MifosTheme {
|
||||
MerchantScreen(
|
||||
merchantUiState = MerchantUiState.ShowMerchants(sampleMerchantList),
|
||||
merchantListUiState = MerchantUiState.ShowMerchants(sampleMerchantList),
|
||||
updateQuery = {}, false, {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MerchantErrorPreview() {
|
||||
MifosTheme {
|
||||
MerchantScreen(
|
||||
merchantUiState = MerchantUiState.Error("Error Screen"),
|
||||
merchantListUiState = MerchantUiState.ShowMerchants(sampleMerchantList),
|
||||
updateQuery = {}, true, {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MerchantEmptyPreview() {
|
||||
MifosTheme {
|
||||
MerchantScreen(
|
||||
merchantUiState = MerchantUiState.Empty,
|
||||
merchantListUiState = MerchantUiState.ShowMerchants(sampleMerchantList),
|
||||
updateQuery = {}, false, {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val sampleMerchantList = List(10) {
|
||||
SavingsWithAssociations(
|
||||
id = 1L,
|
||||
accountNo = "123456789",
|
||||
depositType = null,
|
||||
externalId = "EXT987654",
|
||||
clientId = 101,
|
||||
clientName = "Alice Bob",
|
||||
savingsProductId = 2001,
|
||||
savingsProductName = "Premium Savings Account",
|
||||
fieldOfficerId = 501,
|
||||
status = null,
|
||||
timeline = null,
|
||||
currency = null,
|
||||
nominalAnnualInterestRate = 3.5,
|
||||
minRequiredOpeningBalance = 500.0,
|
||||
lockinPeriodFrequency = 12.0,
|
||||
withdrawalFeeForTransfers = true,
|
||||
allowOverdraft = false,
|
||||
enforceMinRequiredBalance = false,
|
||||
withHoldTax = true,
|
||||
lastActiveTransactionDate = listOf(2024, 3, 24),
|
||||
dormancyTrackingActive = true,
|
||||
summary = null,
|
||||
transactions = listOf()
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
package org.mifospay.feature.merchants
|
||||
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import butterknife.OnClick
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import com.mifospay.core.model.domain.Transaction
|
||||
import org.mifospay.MifosPayApp
|
||||
import org.mifospay.R
|
||||
import org.mifospay.base.BaseActivity
|
||||
import org.mifospay.common.ui.MakeTransferFragment
|
||||
import org.mifospay.history.ui.adapter.SpecificTransactionsAdapter
|
||||
import org.mifospay.merchants.presenter.MerchantTransferPresenter
|
||||
import org.mifospay.common.Constants
|
||||
import org.mifospay.home.BaseHomeContract
|
||||
import org.mifospay.utils.TextDrawable
|
||||
import org.mifospay.utils.Toaster
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created by Shivansh Tiwari on 06/07/19.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MerchantTransferActivity : BaseActivity(), BaseHomeContract.MerchantTransferView {
|
||||
private var mBottomSheetBehavior: BottomSheetBehavior<*>? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.nsv_merchant_bottom_sheet_dialog)
|
||||
var vMerchantBottomSheetDialog: View? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.iv_merchant_image)
|
||||
var ivMerchantImage: ImageView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.tv_pay_to_name)
|
||||
var tvMerchantName: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.tv_pay_to_vpa)
|
||||
var tvMerchantVPA: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.et_merchant_amount)
|
||||
var etAmount: TextInputEditText? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.btn_submit)
|
||||
var btnSubmit: Button? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.rv_merchant_history)
|
||||
var rvMerchantHistory: RecyclerView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.inc_empty_transactions_state_view)
|
||||
var vEmptyState: View? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.iv_empty_no_transaction_history)
|
||||
var ivTransactionsStateIcon: ImageView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.tv_empty_no_transaction_history_title)
|
||||
var tvTransactionsStateTitle: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@BindView(R.id.tv_empty_no_transaction_history_subtitle)
|
||||
var tvTransactionsStateSubtitle: TextView? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var mPresenter: MerchantTransferPresenter? = null
|
||||
private var mTransferPresenter: BaseHomeContract.MerchantTransferPresenter? = null
|
||||
private var merchantAccountNumber: String? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var mMerchantHistoryAdapter: SpecificTransactionsAdapter? = null
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_merchant_transaction)
|
||||
ButterKnife.bind(this)
|
||||
setToolbarTitle("Merchant Transaction")
|
||||
showColoredBackButton(R.drawable.ic_arrow_back_black_24dp)
|
||||
setupUI()
|
||||
mPresenter?.attachView(this)
|
||||
mPresenter?.fetchMerchantTransfers(merchantAccountNumber)
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
setupBottomSheet()
|
||||
merchantAccountNumber = intent.getStringExtra(Constants.MERCHANT_ACCOUNT_NO)
|
||||
tvMerchantName?.text =
|
||||
intent.getStringExtra(Constants.MERCHANT_NAME)
|
||||
tvMerchantVPA?.text = intent.getStringExtra(Constants.MERCHANT_VPA)
|
||||
val drawable = intent.getStringExtra(Constants.MERCHANT_NAME)
|
||||
?.substring(0, 1)?.let {
|
||||
TextDrawable.builder().beginConfig()
|
||||
.width(resources.getDimension(R.dimen.user_profile_image_size).toInt())
|
||||
.height(resources.getDimension(R.dimen.user_profile_image_size).toInt())
|
||||
.endConfig().buildRound(
|
||||
it, R.color.colorPrimary
|
||||
)
|
||||
}
|
||||
ivMerchantImage?.setImageDrawable(drawable)
|
||||
showTransactionFetching()
|
||||
setUpRecycleView()
|
||||
}
|
||||
|
||||
private fun setUpRecycleView() {
|
||||
mMerchantHistoryAdapter?.setContext(this)
|
||||
rvMerchantHistory?.layoutManager =
|
||||
LinearLayoutManager(MifosPayApp.context)
|
||||
rvMerchantHistory?.adapter = mMerchantHistoryAdapter
|
||||
}
|
||||
|
||||
override fun setPresenter(presenter: BaseHomeContract.MerchantTransferPresenter?) {
|
||||
mTransferPresenter = presenter
|
||||
}
|
||||
|
||||
private fun setupBottomSheet() {
|
||||
mBottomSheetBehavior = BottomSheetBehavior.from(vMerchantBottomSheetDialog!!)
|
||||
mBottomSheetBehavior?.setBottomSheetCallback(object : BottomSheetCallback() {
|
||||
override fun onStateChanged(view: View, newState: Int) {
|
||||
when (newState) {
|
||||
BottomSheetBehavior.STATE_COLLAPSED -> {}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(view: View, v: Float) {}
|
||||
})
|
||||
}
|
||||
|
||||
@OnClick(R.id.btn_submit)
|
||||
fun makeTransaction() {
|
||||
val externalId = tvMerchantVPA?.text.toString().trim { it <= ' ' }
|
||||
val amount = etAmount?.text.toString().trim { it <= ' ' }
|
||||
if (amount.isEmpty()) {
|
||||
showToast(Constants.PLEASE_ENTER_ALL_THE_FIELDS)
|
||||
return
|
||||
} else if (amount.toDouble() <= 0) {
|
||||
showToast(Constants.PLEASE_ENTER_VALID_AMOUNT)
|
||||
return
|
||||
}
|
||||
mTransferPresenter?.checkBalanceAvailability(externalId, amount.toDouble())
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (mBottomSheetBehavior?.state != BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
mBottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
return
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override fun showToast(message: String?) {
|
||||
Toaster.showToast(MifosPayApp.context, message)
|
||||
}
|
||||
|
||||
override fun showPaymentDetails(externalId: String?, amount: Double) {
|
||||
val fragment = MakeTransferFragment.newInstance(externalId, amount)
|
||||
fragment.show(supportFragmentManager, "tag")
|
||||
}
|
||||
|
||||
override fun showTransactionFetching() {
|
||||
rvMerchantHistory?.visibility = View.GONE
|
||||
tvTransactionsStateTitle?.text = resources.getString(R.string.fetching)
|
||||
tvTransactionsStateSubtitle?.visibility = View.GONE
|
||||
ivTransactionsStateIcon?.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun showTransactions(transactions: List<Transaction?>?) {
|
||||
vEmptyState?.visibility = View.GONE
|
||||
rvMerchantHistory?.visibility = View.VISIBLE
|
||||
mMerchantHistoryAdapter?.setData(transactions as List<Transaction>)
|
||||
}
|
||||
|
||||
override fun showSpecificView(drawable: Int, title: Int, subtitle: Int) {
|
||||
rvMerchantHistory?.visibility = View.GONE
|
||||
tvTransactionsStateSubtitle?.visibility = View.VISIBLE
|
||||
ivTransactionsStateIcon?.visibility = View.VISIBLE
|
||||
tvTransactionsStateTitle?.setText(title)
|
||||
tvTransactionsStateSubtitle?.setText(subtitle)
|
||||
ivTransactionsStateIcon?.setImageDrawable(resources.getDrawable(drawable))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
package org.mifospay.feature.merchants
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.mifospay.core.model.entity.accounts.savings.SavingsWithAssociations
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.mifospay.core.data.base.TaskLooper
|
||||
import org.mifospay.core.data.base.UseCase
|
||||
import org.mifospay.core.data.base.UseCaseFactory
|
||||
import org.mifospay.core.data.base.UseCaseHandler
|
||||
import org.mifospay.core.data.domain.usecase.account.FetchMerchants
|
||||
import org.mifospay.core.data.domain.usecase.client.FetchClientDetails
|
||||
import org.mifospay.core.data.util.Constants
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class MerchantViewModel @Inject constructor(
|
||||
private val mUseCaseHandler: UseCaseHandler,
|
||||
private val mFetchMerchantsUseCase: FetchMerchants,
|
||||
private val mUseCaseFactory: UseCaseFactory
|
||||
) : ViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var mTaskLooper: TaskLooper
|
||||
|
||||
private val _searchQuery = MutableStateFlow("")
|
||||
private val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
|
||||
|
||||
private val _merchantUiState = MutableStateFlow<MerchantUiState>(MerchantUiState.Loading)
|
||||
val merchantUiState: StateFlow<MerchantUiState> = _merchantUiState
|
||||
|
||||
init {
|
||||
fetchMerchants()
|
||||
}
|
||||
|
||||
private val _isRefreshing = MutableStateFlow(false)
|
||||
val isRefreshing: StateFlow<Boolean> get() = _isRefreshing.asStateFlow()
|
||||
|
||||
fun refresh() {
|
||||
viewModelScope.launch {
|
||||
_isRefreshing.emit(true)
|
||||
fetchMerchants()
|
||||
_isRefreshing.emit(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val merchantsListUiState: StateFlow<MerchantUiState> = searchQuery
|
||||
.map { q ->
|
||||
when (_merchantUiState.value) {
|
||||
is MerchantUiState.ShowMerchants -> {
|
||||
val merchantList =
|
||||
(merchantUiState.value as MerchantUiState.ShowMerchants).merchants
|
||||
val filterCards = merchantList.filter {
|
||||
it.externalId.lowercase().contains(q.lowercase())
|
||||
it.savingsProductName?.lowercase()?.contains(q.lowercase())
|
||||
it.accountNo?.lowercase()?.contains(q.lowercase())
|
||||
it.clientName.lowercase().contains(q.lowercase())
|
||||
}
|
||||
MerchantUiState.ShowMerchants(filterCards)
|
||||
}
|
||||
|
||||
else -> MerchantUiState.ShowMerchants(arrayListOf())
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue = MerchantUiState.ShowMerchants(arrayListOf())
|
||||
)
|
||||
|
||||
fun updateSearchQuery(query: String) {
|
||||
_searchQuery.update { query }
|
||||
}
|
||||
|
||||
|
||||
private fun fetchMerchants() {
|
||||
_merchantUiState.value = MerchantUiState.Loading
|
||||
mUseCaseHandler.execute(mFetchMerchantsUseCase,
|
||||
FetchMerchants.RequestValues(),
|
||||
object : UseCase.UseCaseCallback<FetchMerchants.ResponseValue> {
|
||||
override fun onSuccess(response: FetchMerchants.ResponseValue) {
|
||||
retrieveMerchantsData(response.savingsWithAssociationsList)
|
||||
}
|
||||
|
||||
override fun onError(message: String) {
|
||||
_merchantUiState.value = MerchantUiState.Error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun retrieveMerchantsData(
|
||||
savingsWithAssociationsList: List<SavingsWithAssociations>
|
||||
) {
|
||||
for (i in savingsWithAssociationsList.indices) {
|
||||
mTaskLooper.addTask(
|
||||
useCase = mUseCaseFactory.getUseCase(Constants.FETCH_CLIENT_DETAILS_USE_CASE)
|
||||
as UseCase<FetchClientDetails.RequestValues, FetchClientDetails.ResponseValue>,
|
||||
values = FetchClientDetails.RequestValues(
|
||||
savingsWithAssociationsList[i].clientId.toLong()
|
||||
),
|
||||
taskData = TaskLooper.TaskData("Client data", i)
|
||||
)
|
||||
}
|
||||
mTaskLooper.listen(object : TaskLooper.Listener {
|
||||
override fun <R : UseCase.ResponseValue?> onTaskSuccess(
|
||||
taskData: TaskLooper.TaskData,
|
||||
response: R
|
||||
) {
|
||||
val responseValue = response as FetchClientDetails.ResponseValue
|
||||
savingsWithAssociationsList[taskData.taskId].externalId =
|
||||
responseValue.client.externalId
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
if (savingsWithAssociationsList.isEmpty()) {
|
||||
_merchantUiState.value = MerchantUiState.Empty
|
||||
} else {
|
||||
_merchantUiState.value =
|
||||
MerchantUiState.ShowMerchants(savingsWithAssociationsList)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(message: String?) {
|
||||
_merchantUiState.value = MerchantUiState.Error(message.toString())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MerchantUiState {
|
||||
data object Loading : MerchantUiState()
|
||||
data object Empty : MerchantUiState()
|
||||
data class Error(val message: String) : MerchantUiState()
|
||||
data class ShowMerchants(val merchants: List<SavingsWithAssociations>) : MerchantUiState()
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
package org.mifospay.feature.merchants
|
||||
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.mifospay.core.model.entity.accounts.savings.SavingsWithAssociations
|
||||
import org.mifospay.R
|
||||
import org.mifospay.core.designsystem.component.MifosCard
|
||||
import org.mifospay.core.designsystem.theme.mifosText
|
||||
import org.mifospay.core.designsystem.theme.styleMedium16sp
|
||||
|
||||
@Composable
|
||||
fun MerchantsItem(
|
||||
savingsWithAssociations: SavingsWithAssociations,
|
||||
onMerchantClicked: () -> Unit,
|
||||
onMerchantLongPressed: (String?) -> Unit
|
||||
) {
|
||||
MifosCard(
|
||||
modifier = Modifier.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
onMerchantLongPressed(savingsWithAssociations.externalId)
|
||||
}
|
||||
)
|
||||
},
|
||||
onClick = { onMerchantClicked.invoke() },
|
||||
colors = CardDefaults.cardColors(Color.White)
|
||||
) {
|
||||
Column {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp),
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_bank),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.padding(start = 16.dp, end = 16.dp)
|
||||
.size(39.dp)
|
||||
)
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = savingsWithAssociations.clientName,
|
||||
color = mifosText,
|
||||
)
|
||||
Text(
|
||||
text = savingsWithAssociations.externalId,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
style = styleMedium16sp.copy(mifosText)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.padding(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AccountsItemPreview() {
|
||||
MerchantsItem(
|
||||
savingsWithAssociations = SavingsWithAssociations(),
|
||||
onMerchantClicked = {},
|
||||
onMerchantLongPressed = {}
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.mifospay.feature.merchants
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@ -48,6 +48,7 @@ include(":feature:invoices")
|
||||
include(":feature:invoices")
|
||||
include(":feature:settings")
|
||||
include(":feature:profile")
|
||||
include(":feature:merchants")
|
||||
include(":feature:accounts")
|
||||
include(":feature:standing-instruction")
|
||||
include(":feature:payments")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user