Moved ReadQr and home to feature module (#1674)

This commit is contained in:
Aditya Kumdale 2024-06-22 09:54:54 +05:30 committed by GitHub
parent 776923b698
commit 9b6ddd19cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1128 additions and 2 deletions

View File

@ -0,0 +1,57 @@
package org.mifospay.feature
import com.mifospay.core.model.entity.accounts.savings.TransferDetail
import com.mifospay.core.model.domain.Transaction
import com.mifospay.core.model.domain.TransactionType
import org.mifospay.base.BasePresenter
import org.mifospay.base.BaseView
/**
* Created by naman on 17/8/17.
*/
interface HistoryContract {
interface TransactionsHistoryAsync {
fun onTransactionsFetchCompleted(transactions: List<Transaction>?)
}
interface HistoryView : BaseView<TransactionsHistoryPresenter?> {
fun showRecyclerView()
fun showStateView(drawable: Int, title: Int, subtitle: Int)
fun showTransactions(transactions: List<Transaction>?)
fun showEmptyTransactionTypeStateView(drawable: Int, title: String?, subtitle: String?)
fun showTransactionDetailDialog(transactionIndex: Int, accountNumber: String?)
fun showHistoryFetchingProgress()
fun refreshTransactions(transactions: List<Transaction>?)
}
interface TransactionsHistoryPresenter : BasePresenter {
fun fetchTransactions()
fun filterTransactionType(type: TransactionType)
fun handleTransactionClick(transactionIndex: Int)
}
interface TransactionDetailView : BaseView<TransactionDetailPresenter?> {
fun showTransferDetail(transferDetail: TransferDetail?)
fun showProgressBar()
fun hideProgressBar()
fun showToast(message: String?)
}
interface TransactionDetailPresenter : BasePresenter {
fun getTransferDetail(transferId: Long)
}
interface SpecificTransactionsView : BaseView<SpecificTransactionsPresenter?> {
fun showSpecificTransactions(specificTransactions: ArrayList<Transaction?>)
fun showProgress()
fun hideProgress()
fun showStateView(drawable: Int, title: Int, subtitle: Int)
}
interface SpecificTransactionsPresenter : BasePresenter {
fun getSpecificTransactions(
transactions: ArrayList<Transaction?>?,
secondAccountNumber: String?
): ArrayList<Transaction?>?
}
}

View File

@ -0,0 +1,46 @@
package org.mifospay.feature
import org.mifospay.core.data.base.TaskLooper
import org.mifospay.core.data.base.UseCase.UseCaseCallback
import org.mifospay.core.data.base.UseCaseFactory
import com.mifospay.core.model.domain.Transaction
import org.mifospay.core.data.base.UseCaseHandler
import org.mifospay.core.data.domain.usecase.account.FetchAccount
import org.mifospay.core.data.domain.usecase.account.FetchAccountTransactions
import javax.inject.Inject
class TransactionsHistory @Inject constructor(
private val mUsecaseHandler: UseCaseHandler,
private val fetchAccountTransactionsUseCase: FetchAccountTransactions,
private val mFetchAccountUseCase: FetchAccount
) {
var delegate: HistoryContract.TransactionsHistoryAsync? = null
@JvmField
@Inject
var mTaskLooper: TaskLooper? = null
@JvmField
@Inject
var mUseCaseFactory: UseCaseFactory? = null
private var transactions: List<Transaction>?
init {
transactions = ArrayList()
}
fun fetchTransactionsHistory(accountId: Long) {
mUsecaseHandler.execute(fetchAccountTransactionsUseCase,
FetchAccountTransactions.RequestValues(accountId),
object : UseCaseCallback<FetchAccountTransactions.ResponseValue?> {
override fun onSuccess(response: FetchAccountTransactions.ResponseValue?) {
transactions = response?.transactions
delegate!!.onTransactionsFetchCompleted(transactions)
}
override fun onError(message: String) {
transactions = null
}
})
}
}

1
feature/home/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,16 @@
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)
}
android {
namespace = "org.mifospay.feature.home"
}
dependencies {
implementation(projects.feature.history)
implementation(projects.core.data)
//remove this after optimizing HistoryContract
implementation(projects.mifospay)
}

View File

21
feature/home/proguard-rules.pro vendored Normal file
View 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

View File

@ -0,0 +1,24 @@
package org.mifospay.feature.home
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.home.test", appContext.packageName)
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,22 @@
package org.mifospay.feature.home
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
const val HOME_ROUTE = "home_route"
fun NavController.navigateToHome(navOptions: NavOptions) = navigate(HOME_ROUTE, navOptions)
fun NavGraphBuilder.homeScreen(
onRequest: (String) -> Unit,
onPay: () -> Unit
) {
composable(route = HOME_ROUTE) {
HomeRoute(
onRequest = onRequest,
onPay = onPay
)
}
}

View File

@ -0,0 +1,297 @@
package org.mifospay.feature.home
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.mifospay.core.model.domain.Account
import com.mifospay.core.model.domain.Currency
import com.mifospay.core.model.domain.Transaction
import com.mifospay.core.model.domain.TransactionType
import org.mifospay.R
import org.mifospay.common.Utils
import org.mifospay.core.designsystem.component.MfLoadingWheel
import org.mifospay.core.designsystem.theme.border
import org.mifospay.core.designsystem.theme.lightGrey
import org.mifospay.core.designsystem.theme.styleMedium16sp
import org.mifospay.core.ui.ErrorScreenContent
import org.mifospay.core.ui.TransactionItemScreen
@Composable
fun HomeRoute(
homeViewModel: HomeViewModel = hiltViewModel(),
onRequest: (String) -> Unit,
onPay: () -> Unit
) {
val homeUIState by homeViewModel
.homeUIState
.collectAsStateWithLifecycle()
when (homeUIState) {
is HomeUiState.Loading -> {
MfLoadingWheel(
contentDesc = stringResource(R.string.loading),
backgroundColor = Color.White
)
}
is HomeUiState.Success -> {
val successState = homeUIState as HomeUiState.Success
HomeScreen(
successState.account,
successState.transactions,
onRequest = {
onRequest.invoke(successState.vpa ?: "")
},
onPay = onPay
)
}
is HomeUiState.Error -> {
ErrorScreenContent(
onClickRetry = {
homeViewModel.fetchAccountDetails()
}
)
}
}
}
@Composable
fun HomeScreen(
account: Account?,
transactions: List<Transaction>,
onRequest: () -> Unit,
onPay: () -> Unit
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.background(color = Color.White)
.padding(start = 32.dp, end = 32.dp),
) {
item {
MifosWalletCardScreen(account = account)
}
item {
PayRequestScreen(
onRequest = onRequest,
onPay = onPay
)
}
if (transactions.isNotEmpty()) {
item {
Text(
modifier = Modifier.padding(top = 32.dp),
text = "Recent Transactions",
style = styleMedium16sp
)
Spacer(modifier = Modifier.height(16.dp))
}
}
items(transactions) { transaction ->
TransactionItemScreen(transaction = transaction)
}
item {
Spacer(modifier = Modifier.height(24.dp))
}
}
}
@Composable
fun MifosWalletCardScreen(account: Account?) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(225.dp)
.padding(top = 20.dp, bottom = 32.dp),
colors = CardDefaults.cardColors(containerColor = Color.Black)
) {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(start = 36.dp),
verticalArrangement = Arrangement.Center
) {
val walletBalanceLabel =
if (account != null) "(${account.currency.displayLabel})" else ""
Text(
text = "Wallet Balance $walletBalanceLabel",
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight.W400,
color = lightGrey
)
)
Spacer(modifier = Modifier.height(10.dp))
val accountBalance =
if (account != null) Utils.getFormattedAccountBalance(
account.balance, account.currency.code
) else "0"
Text(
text = accountBalance,
style = TextStyle(
fontSize = 42.sp,
fontWeight = FontWeight(600),
color = Color.White
)
)
Spacer(modifier = Modifier.height(10.dp))
val currencyEqual = if (account != null) {
"${account.currency.code}1 ${account.currency.displayLabel}"
} else ""
Text(
text = currencyEqual,
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight(500),
color = lightGrey
)
)
}
}
}
@Composable
fun PayRequestScreen(
onRequest: () -> Unit,
onPay: () -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
PayCard(
modifier = Modifier.weight(1f),
title = "Request",
icon = R.drawable.money_in
) {
onRequest.invoke()
}
Spacer(modifier = Modifier.width(16.dp))
PayCard(
modifier = Modifier.weight(1f),
title = "Pay",
icon = R.drawable.money_out
) {
onPay.invoke()
}
}
}
@Composable
fun PayCard(
modifier: Modifier,
title: String,
icon: Int,
onClickCard: () -> Unit
) {
Card(
modifier = modifier
.height(144.dp)
.clickable { onClickCard.invoke() },
border = BorderStroke(1.dp, border),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(top = 20.dp, bottom = 20.dp, start = 20.dp),
verticalArrangement = Arrangement.SpaceBetween
) {
Box(
modifier = Modifier
.size(40.dp)
.background(Color.Black, shape = RoundedCornerShape(4.dp)),
contentAlignment = Alignment.Center
) {
Image(
modifier = Modifier.size(20.dp),
painter = painterResource(id = icon),
contentDescription = null,
colorFilter = ColorFilter.tint(Color.White)
)
}
Text(text = title)
}
}
}
@Preview(showSystemUi = true, device = "id:pixel_5")
@Composable
fun HomeScreenPreview() {
HomeScreen(
account = Account(
image = "",
name = "Mifos",
number = "1234567890",
balance = 10000.0,
id = 1L,
currency = Currency(
code = "USD",
displayLabel = "$",
displaySymbol = "$"
),
productId = 1223
),
transactions = List(25) { index ->
Transaction(
transactionId = index.toString(),
amount = 23004.0,
currency = Currency(
code = "USD",
displayLabel = "$",
displaySymbol = "$"
),
transactionType = TransactionType.CREDIT
)
},
onPay = {},
onRequest = {}
)
}
@Preview
@Composable
fun PayRequestScreenPreview() {
PayRequestScreen({}, {})
}
@Preview
@Composable
fun PayCardPreview() {
PayCard(Modifier.width(150.dp), "Request", R.drawable.ic_arrow_back_black_24dp) { }
}

View File

@ -0,0 +1,79 @@
package org.mifospay.feature.home
import androidx.lifecycle.ViewModel
import com.mifospay.core.model.domain.Account
import com.mifospay.core.model.domain.Transaction
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import org.mifospay.core.data.base.UseCase.UseCaseCallback
import org.mifospay.core.data.base.UseCaseHandler
import org.mifospay.core.data.domain.usecase.account.FetchAccount
import org.mifospay.core.data.repository.local.LocalRepository
import org.mifospay.core.datastore.PreferencesHelper
import org.mifospay.feature.HistoryContract
import org.mifospay.feature.TransactionsHistory
import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val useCaseHandler: UseCaseHandler,
private val localRepository: LocalRepository,
private val preferencesHelper: PreferencesHelper,
private val fetchAccountUseCase: FetchAccount,
private val transactionsHistory: TransactionsHistory
) : ViewModel(), HistoryContract.TransactionsHistoryAsync,
org.mifospay.history.HistoryContract.TransactionsHistoryAsync {
// Expose screen UI state
private val _homeUIState: MutableStateFlow<HomeUiState> = MutableStateFlow(HomeUiState.Loading)
val homeUIState: StateFlow<HomeUiState> = _homeUIState.asStateFlow()
init {
transactionsHistory.delegate = this
fetchAccountDetails()
}
fun fetchAccountDetails() {
useCaseHandler.execute(fetchAccountUseCase,
FetchAccount.RequestValues(localRepository.clientDetails.clientId),
object : UseCaseCallback<FetchAccount.ResponseValue> {
override fun onSuccess(response: FetchAccount.ResponseValue) {
preferencesHelper.accountId = response.account.id
_homeUIState.update {
HomeUiState.Success(
account = response.account,
vpa = localRepository.clientDetails.externalId
)
}
response.account.id.let {
transactionsHistory.fetchTransactionsHistory(it)
}
}
override fun onError(message: String) {
_homeUIState.update { HomeUiState.Error }
}
})
}
override fun onTransactionsFetchCompleted(transactions: List<Transaction>?) {
_homeUIState.update { currentState ->
(currentState as HomeUiState.Success)
.copy(transactions = transactions ?: emptyList())
}
}
}
sealed interface HomeUiState {
data object Loading : HomeUiState
data class Success(
val account: Account? = null,
val transactions: List<Transaction> = emptyList(),
val vpa: String? = null
) : HomeUiState
data object Error : HomeUiState
}

View File

@ -0,0 +1,17 @@
package org.mifospay.feature.home
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)
}
}

View File

@ -19,8 +19,6 @@ 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

View File

@ -0,0 +1,139 @@
package org.mifospay.feature.merchants
import org.mifospay.core.data.base.TaskLooper
import org.mifospay.core.data.base.TaskLooper.TaskData
import org.mifospay.core.data.base.UseCase
import org.mifospay.core.data.base.UseCase.UseCaseCallback
import org.mifospay.core.data.base.UseCaseFactory
import com.mifospay.core.model.domain.Transaction
import org.mifospay.core.data.base.UseCaseHandler
import org.mifospay.core.data.domain.usecase.account.FetchAccount
import org.mifospay.core.data.domain.usecase.account.FetchAccountTransfer
import org.mifospay.core.data.util.Constants.FETCH_ACCOUNT_TRANSFER_USECASE
import org.mifospay.R
import org.mifospay.base.BaseView
import org.mifospay.data.local.LocalRepository
import org.mifospay.core.datastore.PreferencesHelper
import org.mifospay.history.HistoryContract.TransactionsHistoryAsync
import org.mifospay.history.TransactionsHistory
import org.mifospay.home.BaseHomeContract
import org.mifospay.home.BaseHomeContract.MerchantTransferView
import org.mifospay.common.Constants
import javax.inject.Inject
/**
* Created by Shivansh Tiwari on 06/07/19.
*/
class MerchantTransferPresenter @Inject constructor(
private val mUsecaseHandler: UseCaseHandler,
private val localRepository: LocalRepository,
private val preferencesHelper: PreferencesHelper,
private val transactionsHistory: TransactionsHistory,
private val mUseCaseFactory: UseCaseFactory,
private val mFetchAccount: FetchAccount
) : BaseHomeContract.MerchantTransferPresenter, TransactionsHistoryAsync {
@Inject
lateinit var mTaskLooper: TaskLooper
private var mMerchantTransferView: MerchantTransferView? = null
private var merchantAccountNumber: String? = null
override fun attachView(baseView: BaseView<*>?) {
mMerchantTransferView = baseView as MerchantTransferView?
mMerchantTransferView!!.setPresenter(this)
transactionsHistory!!.delegate = this
}
override fun checkBalanceAvailability(externalId: String?, transferAmount: Double) {
mUsecaseHandler.execute(mFetchAccount,
FetchAccount.RequestValues(localRepository.clientDetails.clientId),
object : UseCaseCallback<FetchAccount.ResponseValue> {
override fun onSuccess(response: FetchAccount.ResponseValue) {
mMerchantTransferView!!.hideSwipeProgress()
if (transferAmount > response.account.balance) {
mMerchantTransferView!!.showToast(Constants.INSUFFICIENT_BALANCE)
} else {
mMerchantTransferView!!.showPaymentDetails(externalId, transferAmount)
}
}
override fun onError(message: String) {
mMerchantTransferView!!.hideSwipeProgress()
mMerchantTransferView!!.showToast(Constants.ERROR_FETCHING_BALANCE)
}
})
}
override fun fetchMerchantTransfers(merchantAccountNumber: String?) {
transactionsHistory!!.fetchTransactionsHistory(preferencesHelper.accountId)
this.merchantAccountNumber = merchantAccountNumber
}
private fun showErrorStateView() {
mMerchantTransferView!!.showSpecificView(
R.drawable.ic_error_state, R.string.error_oops,
R.string.error_no_transaction_history_subtitle
)
}
private fun showEmptyStateView() {
mMerchantTransferView!!.showSpecificView(
R.drawable.ic_history,
R.string.empty_no_transaction_history_title,
R.string.empty_no_transaction_history_subtitle
)
}
override fun onTransactionsFetchCompleted(transactions: List<Transaction>?) {
val specificTransactions = ArrayList<Transaction?>()
if (!transactions.isNullOrEmpty()) {
for (i in transactions.indices) {
val transaction = transactions[i]
if (transaction.transferDetail == null
&& transaction.transferId != 0L
) {
val transferId = transaction.transferId
mTaskLooper.addTask(
useCase = mUseCaseFactory.getUseCase(FETCH_ACCOUNT_TRANSFER_USECASE)
as UseCase<FetchAccountTransfer.RequestValues, FetchAccountTransfer.ResponseValue>,
values = transferId.let { FetchAccountTransfer.RequestValues(it) },
taskData = TaskData(Constants.TRANSFER_DETAILS, i)
)
}
}
mTaskLooper.listen(object : TaskLooper.Listener {
override fun <R : UseCase.ResponseValue?> onTaskSuccess(
taskData: TaskData, response: R
) {
when (taskData.taskName) {
Constants.TRANSFER_DETAILS -> {
val responseValue = response as FetchAccountTransfer.ResponseValue
val index = taskData.taskId
transactions[index].transferDetail = responseValue.transferDetail
}
}
}
override fun onComplete() {
for (transaction in transactions) {
if (transaction.transferDetail != null && transaction.transferDetail.toAccount
.accountNo == merchantAccountNumber
) {
specificTransactions.add(transaction)
}
}
if (specificTransactions.size == 0) {
showEmptyStateView()
} else {
//mMerchantTransferView.showToast("History Fetched Successfully");
mMerchantTransferView!!.showTransactions(specificTransactions)
}
}
override fun onFailure(message: String?) {
showErrorStateView()
}
})
}
}
}

View File

@ -0,0 +1,153 @@
package org.mifospay.feature.merchants
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import butterknife.BindView
import butterknife.ButterKnife
import com.mifospay.core.model.domain.Transaction
import com.mifospay.core.model.domain.TransactionType
import org.mifospay.R
import org.mifospay.common.Constants
import org.mifospay.common.Utils.getFormattedAccountBalance
import javax.inject.Inject
/**
* Created by ankur on 18/June/2018
*/
class SpecificTransactionsAdapter @Inject constructor() :
RecyclerView.Adapter<SpecificTransactionsAdapter.ViewHolder>() {
private var context: Context? = null
private var transactions: List<Transaction>? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(
R.layout.item_specific_transaction, parent, false
)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val transaction = transactions!![position]
holder.mTvTransactionId!!.text =
Constants.TRANSACTION_ID + ": " + transaction.transactionId
holder.mTvTransactionDate!!.text =
Constants.DATE + ": " + transaction.date
holder.mTvTransactionAmount!!.text = getFormattedAccountBalance(
transaction.amount, transaction.currency.code
)
holder.mTvFromClientName!!.text = transaction.transferDetail.fromClient.displayName
holder.mTvFromAccountNo!!.text = transaction.transferDetail.fromAccount.accountNo
holder.mTvToClientName!!.text = transaction.transferDetail.toClient.displayName
holder.mTvToAccountNo!!.text = transaction.transferDetail.toAccount.accountNo
when (transaction.transactionType) {
TransactionType.DEBIT -> {
holder.mTvTransactionStatus!!.text = Constants.DEBIT
holder.mTvTransactionAmount!!.setTextColor(Color.RED)
}
TransactionType.CREDIT -> {
holder.mTvTransactionStatus!!.text = Constants.CREDIT
holder.mTvTransactionAmount!!.setTextColor(Color.parseColor("#009688"))
}
TransactionType.OTHER -> {
holder.mTvTransactionStatus!!.text = Constants.OTHER
holder.mTvTransactionAmount!!.setTextColor(Color.YELLOW)
}
}
}
override fun getItemCount(): Int {
return if (transactions != null) {
transactions!!.size
} else {
0
}
}
fun setContext(context: Context?) {
this.context = context
}
fun setData(transactions: List<Transaction>?) {
this.transactions = transactions
notifyDataSetChanged()
}
fun getTransactions(): ArrayList<Transaction>? {
return transactions as ArrayList<Transaction>?
}
fun getTransaction(position: Int): Transaction {
return transactions!![position]
}
inner class ViewHolder(v: View?) : RecyclerView.ViewHolder(v!!) {
@JvmField
@BindView(R.id.iv_transaction_status)
var mIvTransactionStatus: ImageView? = null
@JvmField
@BindView(R.id.tv_transaction_id)
var mTvTransactionId: TextView? = null
@JvmField
@BindView(R.id.tv_transaction_date)
var mTvTransactionDate: TextView? = null
@JvmField
@BindView(R.id.tv_transaction_status)
var mTvTransactionStatus: TextView? = null
@JvmField
@BindView(R.id.tv_transaction_amount)
var mTvTransactionAmount: TextView? = null
@JvmField
@BindView(R.id.iv_fromImage)
var mIvFromImage: ImageView? = null
@JvmField
@BindView(R.id.tv_fromClientName)
var mTvFromClientName: TextView? = null
@JvmField
@BindView(R.id.tv_fromAccountNo)
var mTvFromAccountNo: TextView? = null
@JvmField
@BindView(R.id.ll_from)
var mLlFrom: LinearLayout? = null
@JvmField
@BindView(R.id.iv_toImage)
var mIvToImage: ImageView? = null
@JvmField
@BindView(R.id.tv_toClientName)
var mTvToClientName: TextView? = null
@JvmField
@BindView(R.id.tv_toAccountNo)
var mTvToAccountNo: TextView? = null
@JvmField
@BindView(R.id.ll_to)
var mLlTo: LinearLayout? = null
@JvmField
@BindView(R.id.rl_from_to)
var mRlFromTo: RelativeLayout? = null
init {
ButterKnife.bind(this, v!!)
}
}
}

1
feature/qr/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,18 @@
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)
}
android {
namespace = "org.mifospay.feature.qr"
}
dependencies {
//Todo: Remove these after migration
implementation("com.jakewharton:butterknife-annotations:10.2.3")
implementation("com.jakewharton:butterknife:10.2.3@aar")
implementation(project(":mifospay"))
implementation("me.dm7.barcodescanner:zxing:1.9.13")
implementation("com.journeyapps:zxing-android-embedded:4.2.0")
implementation(project(":core:data"))
}

View File

21
feature/qr/proguard-rules.pro vendored Normal file
View 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

View File

@ -0,0 +1,24 @@
package org.mifospay.feature.qr
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.qr.test", appContext.packageName)
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,146 @@
package org.mifospay.feature.read.qr
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import android.widget.Toast
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.google.zxing.BinaryBitmap
import com.google.zxing.LuminanceSource
import com.google.zxing.MultiFormatReader
import com.google.zxing.RGBLuminanceSource
import com.google.zxing.Reader
import com.google.zxing.Result
import com.google.zxing.common.HybridBinarizer
import com.journeyapps.barcodescanner.CaptureActivity
import me.dm7.barcodescanner.zxing.ZXingScannerView
import me.dm7.barcodescanner.zxing.ZXingScannerView.ResultHandler
import org.mifospay.R
import org.mifospay.common.Constants
import org.mifospay.qr.QrContract
import org.mifospay.qr.QrContract.ReadQrView
import java.io.FileNotFoundException
/**
* Created by naman on 7/9/17.
*/
class ReadQrActivity : CaptureActivity(), ReadQrView, ResultHandler {
var mReadQrPresenter: QrContract.ReadQrPresenter? = null
@JvmField
@BindView(R.id.scannerView)
var mScannerView: ZXingScannerView? = null
@JvmField
@BindView(R.id.btn_flash_on)
var mFlashOn: ImageButton? = null
@JvmField
@BindView(R.id.btn_flash_off)
var mFlashOff: ImageButton? = null
@JvmField
@BindView(R.id.btn_open_gallery)
var mOpenGallery: ImageButton? = null
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_read_qr)
ButterKnife.bind(this@ReadQrActivity)
mScannerView!!.setAutoFocus(true)
}
@OnClick(R.id.btn_flash_on)
fun turnOnFlash() {
mScannerView!!.flash = true
mFlashOn!!.visibility = View.GONE
mFlashOff!!.visibility = View.VISIBLE
}
@OnClick(R.id.btn_flash_off)
fun turnOffFlash() {
mScannerView!!.flash = false
mFlashOn!!.visibility = View.VISIBLE
mFlashOff!!.visibility = View.GONE
}
@OnClick(R.id.btn_open_gallery)
fun openGallery() {
val photoPic = Intent(Intent.ACTION_PICK)
photoPic.type = "image/*"
startActivityForResult(photoPic, SELECT_PHOTO)
}
public override fun onResume() {
super.onResume()
mScannerView!!.setResultHandler(this)
mScannerView!!.startCamera()
}
public override fun onPause() {
super.onPause()
mScannerView!!.stopCamera()
}
override fun handleResult(result: Result) {
val qrData = result.text
val returnIntent = Intent()
returnIntent.putExtra(Constants.QR_DATA, qrData)
setResult(RESULT_OK, returnIntent)
finish()
}
override fun setPresenter(presenter: QrContract.ReadQrPresenter?) {
mReadQrPresenter = presenter
}
fun scanQRImage(bMap: Bitmap): String? {
var contents: String? = null
val intArray = IntArray(bMap.width * bMap.height)
bMap.getPixels(intArray, 0, bMap.width, 0, 0, bMap.width, bMap.height)
val source: LuminanceSource = RGBLuminanceSource(bMap.width, bMap.height, intArray)
val bitmap = BinaryBitmap(HybridBinarizer(source))
val reader: Reader = MultiFormatReader()
try {
val result = reader.decode(bitmap)
contents = result.text
val returnIntent = Intent()
returnIntent.putExtra(Constants.QR_DATA, contents)
setResult(RESULT_OK, returnIntent)
finish()
} catch (e: Exception) {
Toast.makeText(
applicationContext,
"Error! $e", Toast.LENGTH_SHORT
).show()
}
return contents
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SELECT_PHOTO && resultCode == RESULT_OK) {
try {
val selectedImage = data!!.data
if (selectedImage != null) {
val imageStream = contentResolver.openInputStream(selectedImage)
val bMap = BitmapFactory.decodeStream(imageStream)
scanQRImage(bMap)
}
} catch (e: FileNotFoundException) {
Toast.makeText(applicationContext, "File not found", Toast.LENGTH_SHORT)
.show()
}
}
}
companion object {
private const val SELECT_PHOTO = 100
}
}

View File

@ -0,0 +1,19 @@
package org.mifospay.feature.read.qr
import org.mifospay.core.data.base.UseCaseHandler
import org.mifospay.base.BaseView
import org.mifospay.qr.QrContract
import org.mifospay.qr.QrContract.ReadQrView
import javax.inject.Inject
/**
* Created by naman on 7/9/17.
*/
class ReadQrPresenter @Inject constructor(private val mUsecaseHandler: UseCaseHandler) :
QrContract.ReadQrPresenter {
private var mReadQrView: ReadQrView? = null
override fun attachView(baseView: BaseView<*>?) {
mReadQrView = baseView as ReadQrView?
mReadQrView!!.setPresenter(this)
}
}

View File

@ -0,0 +1,17 @@
package org.mifospay.feature.qr
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)
}
}

View File

@ -53,3 +53,5 @@ include(":feature:merchants")
include(":feature:accounts")
include(":feature:standing-instruction")
include(":feature:payments")
include(":feature:qr")
include(":feature:home")