mirror of
https://github.com/openMF/mobile-wallet.git
synced 2026-02-06 14:16:54 +00:00
Moved ReadQr and home to feature module (#1674)
This commit is contained in:
parent
776923b698
commit
9b6ddd19cf
@ -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?>?
|
||||
}
|
||||
}
|
||||
@ -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
1
feature/home/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
16
feature/home/build.gradle.kts
Normal file
16
feature/home/build.gradle.kts
Normal 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)
|
||||
}
|
||||
0
feature/home/consumer-rules.pro
Normal file
0
feature/home/consumer-rules.pro
Normal file
21
feature/home/proguard-rules.pro
vendored
Normal file
21
feature/home/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.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)
|
||||
}
|
||||
}
|
||||
4
feature/home/src/main/AndroidManifest.xml
Normal file
4
feature/home/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,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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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) { }
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
1
feature/qr/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
18
feature/qr/build.gradle.kts
Normal file
18
feature/qr/build.gradle.kts
Normal 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"))
|
||||
}
|
||||
0
feature/qr/consumer-rules.pro
Normal file
0
feature/qr/consumer-rules.pro
Normal file
21
feature/qr/proguard-rules.pro
vendored
Normal file
21
feature/qr/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.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)
|
||||
}
|
||||
}
|
||||
4
feature/qr/src/main/AndroidManifest.xml
Normal file
4
feature/qr/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,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
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -53,3 +53,5 @@ include(":feature:merchants")
|
||||
include(":feature:accounts")
|
||||
include(":feature:standing-instruction")
|
||||
include(":feature:payments")
|
||||
include(":feature:qr")
|
||||
include(":feature:home")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user