Refactor - [:feature:make-transfer] Applied Spotless & Detekt Compose Rules (#1724)

* Refactor: Applied Spotless & Detekt Compose Rules

- :feature:accounts
- :feature:auth
- :feature:editpassword
- :feature:finance

* Refactor - [:feature:history] Applied Spotless & Detekt Compose Rules

* Refactor - [:feature:home] Applied Spotless & Detekt Compose Rules

* Refactor - [:feature:invoices] Applied Spotless & Detekt Compose Rules

* Refactor - [:feature:kyc] Applied Spotless & Detekt Compose Rules

* Refactor - [:feature:make-transfer] Applied Spotless & Detekt Compose Rules
This commit is contained in:
Sk Niyaj Ali 2024-08-11 23:06:53 +05:30 committed by GitHub
parent 6f87da5869
commit e45c9b6f59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
151 changed files with 2981 additions and 1922 deletions

7
.editorconfig Normal file
View File

@ -0,0 +1,7 @@
# https://editorconfig.org/
# This configuration is used by ktlint when spotless invokes it
[*.{kt,kts}]
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true
ktlint_function_naming_ignore_when_annotated_with=Composable, Test, Preview

View File

@ -52,20 +52,21 @@ jobs:
name: mobile-wallet
path: mifospay/build/outputs/apk/debug/
lintCheck:
name: Static Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Setup JDK 17
- name: Setup JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Detekt For All Modules
run: ./gradlew detekt
# Turing off detekt check until migration finished
# lintCheck:
# name: Static Analysis
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
#
# # Setup JDK 17
# - name: Setup JDK 17
# uses: actions/setup-java@v1
# with:
# java-version: 17
#
# - name: Detekt For All Modules
# run: ./gradlew detekt
pmd:
name: PMD

View File

@ -1,11 +1,10 @@
import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
dependencies {
classpath(libs.google.oss.licenses.plugin) {
exclude(group = "com.google.protobuf")
}
classpath(libs.spotless.gradle)
}
}
@ -28,11 +27,14 @@ plugins {
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.detekt)
alias(libs.plugins.detekt.compiler)
alias(libs.plugins.module.graph) apply true // Plugin applied to allow module graph generation
// Plugin applied to allow module graph generation
alias(libs.plugins.module.graph) apply true
alias(libs.plugins.spotless)
}
val detektFormatting = libs.detekt.formatting
val twitterComposeRules = libs.twitter.detekt.compose
val ktlintVersion = "1.0.1"
val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) {
output.set(rootProject.layout.buildDirectory.file("reports/detekt/merge.html")) // or "reports/detekt/merge.sarif"
@ -41,17 +43,40 @@ val reportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMe
subprojects {
apply {
plugin("io.gitlab.arturbosch.detekt")
}
detekt {
config.from(rootProject.files("config/detekt/detekt.yml"))
reports.xml.required.set(true)
plugin("com.diffplug.spotless")
}
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
config.from(rootProject.files("config/detekt/detekt.yml"))
reports.xml.required.set(true)
finalizedBy(reportMerge)
}
extensions.configure<com.diffplug.gradle.spotless.SpotlessExtension> {
kotlin {
target("**/*.kt")
targetExclude("**/build/**/*.kt")
ktlint(ktlintVersion).editorConfigOverride(
mapOf(
"android" to "true",
),
)
licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
}
format("kts") {
target("**/*.kts")
targetExclude("**/build/**/*.kts")
// Look for the first line that doesn't have a block comment (assumed to be the license)
licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
}
format("xml") {
target("**/*.xml")
targetExclude("**/build/**/*.xml")
// Look for the first XML tag that isn't a comment (<!--) or the xml declaration (<?xml)
licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[^!?])")
}
}
reportMerge {
input.from(tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().map {
it.htmlReportFile }

View File

@ -805,7 +805,7 @@ style:
NestedClassesVisibility:
active: true
NewLineAtEndOfFile:
active: false # Turning off for current implementation
active: true
NoTabs:
active: false
NullableBooleanCheck:
@ -937,3 +937,49 @@ style:
active: true
excludeImports:
- "java.util.*"
TwitterCompose:
CompositionLocalAllowlist:
active: true
# You can optionally define a list of CompositionLocals that are allowed here
# allowedCompositionLocals: LocalSomething,LocalSomethingElse
CompositionLocalNaming:
active: true
ContentEmitterReturningValues:
active: true
# You can optionally add your own composables here
# contentEmitters: MyComposable,MyOtherComposable
ModifierComposable:
active: true
ModifierMissing:
active: true
ModifierReused:
active: true
ModifierWithoutDefault:
active: true
MultipleEmitters:
active: true
# You can optionally add your own composables here
# contentEmitters: MyComposable,MyOtherComposable
MutableParams:
active: true
ComposableNaming:
active: true
# You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters)
# allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter
ComposableParamOrder:
active: true
PreviewNaming:
active: true
PreviewPublic:
active: true
# You can optionally disable that only previews with @PreviewParameter are flagged
# previewPublicOnlyIfParams: false
RememberMissing:
active: true
UnstableCollections:
active: false
ViewModelForwarding:
active: true
ViewModelInjection:
active: true

View File

@ -5,7 +5,7 @@ import android.content.Context
import android.content.res.Resources
import android.view.inputmethod.InputMethodManager
import java.text.NumberFormat
import java.util.*
import java.util.Currency
object Utils {
@ -31,4 +31,10 @@ object Utils {
return accountBalanceFormatter.format(balance)
}
fun <T> List<T>.toArrayList(): ArrayList<T> {
val array: ArrayList<T> = ArrayList()
for (index in this) array.add(index)
return array
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.domain.usecase.history
import com.mifospay.core.model.domain.Transaction
interface HistoryContract {
interface TransactionsHistoryAsync {
fun onTransactionsFetchCompleted(transactions: List<Transaction>?)
}
}

View File

@ -1,19 +1,25 @@
package org.mifospay.feature
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.data.domain.usecase.history
import com.mifospay.core.model.domain.Transaction
import org.mifospay.core.data.base.TaskLooper
import org.mifospay.core.data.base.UseCase.UseCaseCallback
import org.mifospay.core.data.base.UseCaseFactory
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
@Suppress("UnusedPrivateProperty")
class TransactionsHistory @Inject constructor(
private val mUsecaseHandler: UseCaseHandler,
private val mUseCaseHandler: UseCaseHandler,
private val fetchAccountTransactionsUseCase: FetchAccountTransactions,
private val mFetchAccountUseCase: FetchAccount
) {
var delegate: HistoryContract.TransactionsHistoryAsync? = null
@ -31,7 +37,8 @@ class TransactionsHistory @Inject constructor(
}
fun fetchTransactionsHistory(accountId: Long) {
mUsecaseHandler.execute(fetchAccountTransactionsUseCase,
mUseCaseHandler.execute(
fetchAccountTransactionsUseCase,
FetchAccountTransactions.RequestValues(accountId),
object : UseCaseCallback<FetchAccountTransactions.ResponseValue?> {
override fun onSuccess(response: FetchAccountTransactions.ResponseValue?) {
@ -42,6 +49,7 @@ class TransactionsHistory @Inject constructor(
override fun onError(message: String) {
transactions = null
}
})
},
)
}
}

View File

@ -31,7 +31,6 @@ class FetchInvoice @Inject constructor(private val mFineractRepository: Fineract
val clientId = params?.get(0) // "clientId"
val invoiceId = params?.get(1) // "invoiceId"
if (clientId != null && invoiceId != null) {
mFineractRepository.fetchInvoice(clientId, invoiceId)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())

View File

@ -1,5 +1,6 @@
package org.mifospay.core.data.domain.usecase.invoice
import android.util.Log
import org.mifospay.core.data.base.UseCase
import com.mifospay.core.model.entity.Invoice
import org.mifospay.core.data.fineract.repository.FineractRepository
@ -26,6 +27,7 @@ class FetchInvoices @Inject constructor(private val mFineractRepository: Finerac
.subscribe(object : Subscriber<List<Invoice>>() {
override fun onCompleted() {}
override fun onError(e: Throwable) {
Log.e("Invoices", e.message.toString())
useCaseCallback.onError(e.toString())
}

View File

@ -5,16 +5,18 @@ import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@Composable
fun MifosScaffold(
topBarTitle: Int? = null,
backPress: () -> Unit,
modifier: Modifier = Modifier,
topBarTitle: Int? = null,
floatingActionButtonContent: FloatingActionButtonContent? = null,
snackbarHost: @Composable () -> Unit = {},
scaffoldContent: @Composable (PaddingValues) -> Unit,
actions: @Composable RowScope.() -> Unit = {}
actions: @Composable RowScope.() -> Unit = {},
) {
Scaffold(
topBar = {
@ -22,7 +24,7 @@ fun MifosScaffold(
MifosTopBar(
topBarTitle = topBarTitle,
backPress = backPress,
actions = actions
actions = actions,
)
}
},
@ -31,17 +33,18 @@ fun MifosScaffold(
FloatingActionButton(
onClick = content.onClick,
contentColor = content.contentColor,
content = content.content
content = content.content,
)
}
},
snackbarHost = snackbarHost,
content = scaffoldContent,
modifier = modifier,
)
}
data class FloatingActionButtonContent(
val onClick: (() -> Unit),
val contentColor: Color,
val content: (@Composable () -> Unit)
val content: (@Composable () -> Unit),
)

View File

@ -16,11 +16,8 @@ dependencies {
api(projects.core.designsystem)
api(projects.core.model)
api(projects.core.common)
api(libs.androidx.metrics)
api(projects.core.analytics)
api(projects.core.designsystem)
api(projects.core.model)
implementation(libs.accompanist.pager)
implementation(libs.androidx.browser)

View File

@ -16,11 +16,6 @@ import com.google.accompanist.pager.PagerState
import kotlinx.coroutines.launch
import org.mifospay.core.ui.utility.TabContent
/**
* @author pratyush
* @since 23/3/24
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MifosScrollableTabRow(
@ -35,11 +30,10 @@ fun MifosScrollableTabRow(
val scope = rememberCoroutineScope()
ScrollableTabRow(
modifier = modifier,
containerColor = containerColor,
selectedTabIndex = pagerState.currentPage,
edgePadding = edgePadding,
indicator = {},
divider = {},
) {
tabContents.forEachIndexed { index, currentTab ->
Tab(
@ -59,9 +53,8 @@ fun MifosScrollableTabRow(
HorizontalPager(
count = tabContents.size,
state = pagerState,
modifier = modifier
) { page ->
tabContents.getOrNull(page)?.content?.invoke() ?: Text("Page $page")
state = pagerState
) {
tabContents[it].content.invoke()
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)

View File

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

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts
import androidx.lifecycle.ViewModel
@ -55,33 +64,54 @@ class AccountViewModel @Inject constructor() : ViewModel() {
private fun fetchSampleLinkedAccounts(): List<BankAccountDetails> {
return listOf(
BankAccountDetails(
"SBI", "Ankur Sharma", "New Delhi",
mRandom.nextInt().toString() + " ", "Savings"
"SBI",
"Ankur Sharma",
"New Delhi",
mRandom.nextInt().toString() + " ",
"Savings",
),
BankAccountDetails(
"HDFC", "Mandeep Singh", "Uttar Pradesh",
mRandom.nextInt().toString() + " ", "Savings"
"HDFC",
"Mandeep Singh",
"Uttar Pradesh",
mRandom.nextInt().toString() + " ",
"Savings",
),
BankAccountDetails(
"ANDHRA", "Rakesh anna", "Telegana",
mRandom.nextInt().toString() + " ", "Savings"
"ANDHRA",
"Rakesh anna",
"Telegana",
mRandom.nextInt().toString() + " ",
"Savings",
),
BankAccountDetails(
"PNB", "luv Pro", "Gujrat",
mRandom.nextInt().toString() + " ", "Savings"
"PNB",
"luv Pro",
"Gujrat",
mRandom.nextInt().toString() + " ",
"Savings",
),
BankAccountDetails(
"HDF", "Harry potter", "Hogwarts",
mRandom.nextInt().toString() + " ", "Savings"
"HDF",
"Harry potter",
"Hogwarts",
mRandom.nextInt().toString() + " ",
"Savings",
),
BankAccountDetails(
"GCI", "JIGME", "JAMMU",
mRandom.nextInt().toString() + " ", "Savings"
"GCI",
"JIGME",
"JAMMU",
mRandom.nextInt().toString() + " ",
"Savings",
),
BankAccountDetails(
"FCI", "NISHU BOII", "ASSAM",
mRandom.nextInt().toString() + " ", "Savings"
)
"FCI",
"NISHU BOII",
"ASSAM",
mRandom.nextInt().toString() + " ",
"Savings",
),
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts
import androidx.compose.foundation.layout.Column
@ -20,19 +29,21 @@ import com.mifospay.core.model.domain.BankAccountDetails
import org.mifospay.core.designsystem.component.MifosCard
@Composable
fun AccountsItem(
internal fun AccountsItem(
bankAccountDetails: BankAccountDetails,
onAccountClicked: () -> Unit
onAccountClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
MifosCard(
modifier = modifier,
onClick = { onAccountClicked.invoke() },
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface)
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface),
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
.padding(top = 16.dp),
) {
Icon(
painter = painterResource(id = R.drawable.feature_accounts_ic_bank),
@ -40,7 +51,7 @@ fun AccountsItem(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 16.dp, end = 16.dp)
.size(39.dp)
.size(39.dp),
)
Column {
@ -52,18 +63,18 @@ fun AccountsItem(
text = bankAccountDetails.bankName.toString(),
modifier = Modifier.padding(top = 4.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
}
Column(
horizontalAlignment = Alignment.End,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = bankAccountDetails.branch.toString(),
modifier = Modifier.padding(16.dp),
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
}
}
@ -76,6 +87,6 @@ fun AccountsItem(
private fun AccountsItemPreview() {
AccountsItem(
bankAccountDetails = BankAccountDetails("A", "B", "C"),
onAccountClicked = {}
onAccountClicked = {},
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts
import androidx.compose.foundation.background
@ -34,15 +43,17 @@ import org.mifospay.core.ui.utility.AddCardChip
@Composable
fun AccountsScreen(
navigateToBankAccountDetailScreen: (BankAccountDetails, Int) -> Unit,
navigateToLinkBankAccountScreen: () -> Unit,
modifier: Modifier = Modifier,
viewModel: AccountViewModel = hiltViewModel(),
navigateToBankAccountDetailScreen: (BankAccountDetails,Int) -> Unit,
navigateToLinkBankAccountScreen: () -> Unit
) {
val accountsUiState by viewModel.accountsUiState.collectAsStateWithLifecycle()
val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle()
val bankAccountDetailsList by viewModel.bankAccountDetailsList.collectAsStateWithLifecycle()
AccountScreen(
modifier = modifier,
accountsUiState = accountsUiState,
onAddAccount = {
navigateToLinkBankAccountScreen.invoke()
@ -54,27 +65,30 @@ fun AccountsScreen(
},
onUpdateAccount = { bankAccountDetails, index ->
viewModel.updateBankAccount(index, bankAccountDetails)
navigateToBankAccountDetailScreen.invoke(bankAccountDetails,index)
}
navigateToBankAccountDetailScreen.invoke(bankAccountDetails, index)
},
)
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AccountScreen(
private fun AccountScreen(
accountsUiState: AccountsUiState,
onAddAccount: () -> Unit,
bankAccountDetailsList: List<BankAccountDetails>,
isRefreshing: Boolean,
onRefresh: () -> Unit,
onUpdateAccount: (BankAccountDetails, Int) -> Unit
onUpdateAccount: (BankAccountDetails, Int) -> Unit,
modifier: Modifier = Modifier,
) {
val pullRefreshState = rememberPullRefreshState(isRefreshing, onRefresh)
Box(Modifier.pullRefresh(pullRefreshState)) {
Box(modifier.pullRefresh(pullRefreshState)) {
Column(modifier = Modifier.fillMaxSize()) {
when (accountsUiState) {
AccountsUiState.Empty -> {
NoLinkedAccountsScreen { onAddAccount.invoke() }
NoLinkedAccountsScreen(
onAddBtn = onAddAccount,
)
}
AccountsUiState.Error -> {
@ -83,7 +97,7 @@ fun AccountScreen(
title = stringResource(id = R.string.feature_accounts_error_oops),
subTitle = stringResource(id = R.string.feature_accounts_unexpected_error_subtitle),
iconTint = MaterialTheme.colorScheme.onSurface,
iconImageVector = Icons.Rounded.Info
iconImageVector = Icons.Rounded.Info,
)
}
@ -91,14 +105,14 @@ fun AccountScreen(
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxSize()
.fillMaxSize(),
) {
item {
Text(
text = stringResource(id = R.string.feature_accounts_linked_bank_account),
fontSize = 16.sp,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(top = 48.dp, start = 24.dp)
modifier = Modifier.padding(top = 48.dp, start = 24.dp),
)
}
items(bankAccountDetailsList) { bankAccountDetails ->
@ -107,10 +121,10 @@ fun AccountScreen(
bankAccountDetails = bankAccountDetails,
onAccountClicked = {
onUpdateAccount(bankAccountDetails, index)
}
},
)
HorizontalDivider(
modifier = Modifier.padding(8.dp)
modifier = Modifier.padding(8.dp),
)
}
item {
@ -118,13 +132,13 @@ fun AccountScreen(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.background(MaterialTheme.colorScheme.surface)
.background(MaterialTheme.colorScheme.surface),
) {
AddCardChip(
modifier = Modifier.align(Alignment.Center),
onAddBtn = onAddAccount,
text = R.string.feature_accounts_add_account,
btnText = R.string.feature_accounts_add_cards
btnText = R.string.feature_accounts_add_cards,
)
}
}
@ -134,7 +148,7 @@ fun AccountScreen(
AccountsUiState.Loading -> {
MfLoadingWheel(
contentDesc = stringResource(R.string.feature_accounts_loading),
backgroundColor = MaterialTheme.colorScheme.surface
backgroundColor = MaterialTheme.colorScheme.surface,
)
}
}
@ -142,26 +156,29 @@ fun AccountScreen(
PullRefreshIndicator(
refreshing = isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter)
modifier = Modifier.align(Alignment.TopCenter),
)
}
}
@Composable
fun NoLinkedAccountsScreen(onAddBtn: () -> Unit) {
private fun NoLinkedAccountsScreen(
onAddBtn: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = stringResource(R.string.feature_accounts_no_linked_bank_accounts))
AddCardChip(
modifier = Modifier,
onAddBtn = onAddBtn,
text = R.string.feature_accounts_add_account,
btnText = R.string.feature_accounts_add_cards
btnText = R.string.feature_accounts_add_cards,
)
}
}
@ -170,7 +187,14 @@ fun NoLinkedAccountsScreen(onAddBtn: () -> Unit) {
@Preview(showBackground = true)
@Composable
private fun AccountScreenLoadingPreview() {
AccountScreen(accountsUiState = AccountsUiState.Loading, {}, emptyList(), false, {}, { _, _ -> })
AccountScreen(
accountsUiState = AccountsUiState.Loading,
{},
emptyList(),
false,
{},
{ _, _ -> },
)
}
@Preview(showBackground = true)
@ -188,7 +212,7 @@ private fun AccountListScreenPreview() {
sampleLinkedAccount,
false,
{},
{ _, _ -> }
{ _, _ -> },
)
}
@ -200,7 +224,10 @@ private fun AccountErrorScreenPreview() {
val sampleLinkedAccount = List(10) {
BankAccountDetails(
"SBI", "Ankur Sharma", "New Delhi",
"XXXXXXXX9990XXX " + " ", "Savings"
"SBI",
"Ankur Sharma",
"New Delhi",
"XXXXXXXX9990XXX " + " ",
"Savings",
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts.choose.sim
import androidx.compose.animation.AnimatedVisibility
@ -40,14 +49,14 @@ import org.mifospay.core.designsystem.theme.MifosTheme
import org.mifospay.feature.bank.accounts.R
@Composable
fun ChooseSimDialogSheet(
internal fun ChooseSimDialogSheet(
onSimSelected: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
MifosBottomSheet(
content = {
ChooseSimDialogSheetContent(
onSimSelected = onSimSelected
onSimSelected = onSimSelected,
)
},
onDismiss = {
@ -63,7 +72,7 @@ fun ChooseSimDialogSheet(
*/
@Composable
@Suppress("LongMethod")
fun ChooseSimDialogSheetContent(
private fun ChooseSimDialogSheetContent(
onSimSelected: (Int) -> Unit,
modifier: Modifier = Modifier,
) {
@ -82,67 +91,67 @@ fun ChooseSimDialogSheetContent(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
.fillMaxSize()
.padding(8.dp)
.padding(8.dp),
) {
Text(
text = stringResource(id = R.string.feature_accounts_verify_mobile_number),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = R.string.feature_accounts_confirm_mobile_number_message),
style = MaterialTheme.typography.bodySmall.copy(
textAlign = TextAlign.Center
textAlign = TextAlign.Center,
),
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.height(16.dp))
Text(
modifier = Modifier.padding(horizontal = 24.dp),
text = stringResource(id = R.string.feature_accounts_bank_account_mobile_verification_conditions),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.height(20.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
SimCard(
simNumber = 1,
isSelected = selectedSim == 1,
onSimSelected = { selectedSim = 1 }
onSimSelected = { selectedSim = 1 },
)
Spacer(modifier = Modifier.width(24.dp))
Text(
text = stringResource(id = R.string.feature_accounts_or),
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.width(24.dp))
SimCard(
simNumber = 2,
isSelected = selectedSim == 2,
onSimSelected = { selectedSim = 2 }
onSimSelected = { selectedSim = 2 },
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = R.string.feature_accounts_regular_charges_will_apply),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodySmall
style = MaterialTheme.typography.bodySmall,
)
AnimatedVisibility(
visible = showMessage
visible = showMessage,
) {
Text(
text = message,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(vertical = 4.dp)
modifier = Modifier.padding(vertical = 4.dp),
)
}
@ -156,7 +165,7 @@ fun ChooseSimDialogSheetContent(
} else {
onSimSelected(selectedSim)
}
}
},
) {
Text(text = stringResource(id = R.string.feature_accounts_confirm))
}
@ -165,7 +174,7 @@ fun ChooseSimDialogSheetContent(
}
@Composable
fun SimCard(
private fun SimCard(
simNumber: Int,
isSelected: Boolean,
onSimSelected: () -> Unit,
@ -174,7 +183,9 @@ fun SimCard(
val drawable: Painter = painterResource(
id = if (isSelected) {
R.drawable.feature_accounts_sim_card_selected
} else R.drawable.feature_accounts_sim_card_unselected
} else {
R.drawable.feature_accounts_sim_card_unselected
},
)
Image(
painter = drawable,
@ -182,17 +193,17 @@ fun SimCard(
contentScale = ContentScale.Fit,
modifier = modifier
.size(50.dp)
.clickable { onSimSelected() }
.clickable { onSimSelected() },
)
}
@Preview
@Composable
fun SimSelectionPreview() {
private fun SimSelectionPreview() {
MifosTheme {
Surface {
ChooseSimDialogSheetContent(
onSimSelected = {}
onSimSelected = {},
)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts.details
import androidx.compose.foundation.border
@ -26,12 +35,12 @@ import org.mifospay.core.designsystem.component.MifosTopBar
import org.mifospay.feature.bank.accounts.R
@Composable
fun BankAccountDetailScreen(
internal fun BankAccountDetailScreen(
bankAccountDetails: BankAccountDetails,
onSetupUpiPin: () -> Unit,
onChangeUpiPin: () -> Unit,
onForgotUpiPin: () -> Unit,
navigateBack: () -> Unit
navigateBack: () -> Unit,
) {
BankAccountDetailScreen(
bankName = bankAccountDetails.bankName.toString(),
@ -43,12 +52,12 @@ fun BankAccountDetailScreen(
onSetupUpiPin = onSetupUpiPin,
onChangeUpiPin = onChangeUpiPin,
onForgotUpiPin = onForgotUpiPin,
navigateBack = navigateBack
navigateBack = navigateBack,
)
}
@Composable
fun BankAccountDetailScreen(
private fun BankAccountDetailScreen(
bankName: String,
accountHolderName: String,
branchName: String,
@ -58,109 +67,131 @@ fun BankAccountDetailScreen(
onSetupUpiPin: () -> Unit,
onChangeUpiPin: () -> Unit,
onForgotUpiPin: () -> Unit,
navigateBack: () -> Unit
navigateBack: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier = Modifier.fillMaxSize()) {
MifosTopBar(topBarTitle = R.string.feature_accounts_bank_account_details) { navigateBack.invoke() }
Column(
modifier = modifier
.fillMaxSize(),
) {
MifosTopBar(
topBarTitle = R.string.feature_accounts_bank_account_details,
backPress = navigateBack,
)
Column(
modifier = Modifier
.padding(20.dp)
.border(2.dp, MaterialTheme.colorScheme.onSurface)
.padding(20.dp)
.padding(20.dp),
) {
BankAccountDetailRows(
modifier = Modifier.fillMaxWidth(),
detail = R.string.feature_accounts_bank_name,
detailValue = bankName
detailValue = bankName,
)
BankAccountDetailRows(
modifier = Modifier.fillMaxWidth().padding(top = 10.dp),
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
detail = R.string.feature_accounts_ac_holder_name,
detailValue = accountHolderName
detailValue = accountHolderName,
)
BankAccountDetailRows(
modifier = Modifier.fillMaxWidth().padding(top = 10.dp),
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
detail = R.string.feature_accounts_branch_name,
detailValue = branchName
detailValue = branchName,
)
BankAccountDetailRows(
modifier = Modifier.fillMaxWidth().padding(top = 10.dp),
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
detail = R.string.feature_accounts_ifsc,
detailValue = ifsc
detailValue = ifsc,
)
BankAccountDetailRows(
modifier = Modifier.fillMaxWidth().padding(top = 10.dp),
modifier = Modifier
.fillMaxWidth()
.padding(top = 10.dp),
detail = R.string.feature_accounts_type,
detailValue = type
detailValue = type,
)
}
Row(
modifier = Modifier.fillMaxWidth().padding(20.dp),
horizontalArrangement = Arrangement.SpaceBetween
modifier = Modifier
.fillMaxWidth()
.padding(20.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
BankAccountDetailButton(
btnText = R.string.feature_accounts_setup_upi,
onClick = { onSetupUpiPin.invoke() },
isUpiEnabled = !isUpiEnabled,
hasTrailingIcon = false
hasTrailingIcon = false,
)
BankAccountDetailButton(
btnText = R.string.feature_accounts_delete_bank,
onClick = {},
isUpiEnabled = !isUpiEnabled
isUpiEnabled = !isUpiEnabled,
)
}
Column(
modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(20.dp)
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
BankAccountDetailButton(
btnText = R.string.feature_accounts_change_upi_pin,
onClick = { onChangeUpiPin.invoke() },
isUpiEnabled = isUpiEnabled,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
)
BankAccountDetailButton(
btnText = R.string.feature_accounts_forgot_upi_pin,
onClick = { onForgotUpiPin.invoke() },
isUpiEnabled = isUpiEnabled,
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
)
}
}
}
@Composable
fun BankAccountDetailRows(
modifier: Modifier, detail: Int, detailValue: String
private fun BankAccountDetailRows(
detail: Int,
detailValue: String,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = detail),
modifier = Modifier.padding(end = 10.dp),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Text(text = detailValue,
Text(
text = detailValue,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface)
color = MaterialTheme.colorScheme.onSurface,
)
}
}
@Composable
fun BankAccountDetailButton(
modifier: Modifier = Modifier,
private fun BankAccountDetailButton(
btnText: Int,
onClick: () -> Unit,
isUpiEnabled: Boolean,
hasTrailingIcon: Boolean = false
modifier: Modifier = Modifier,
hasTrailingIcon: Boolean = false,
) {
if (isUpiEnabled) {
Button(
@ -168,23 +199,23 @@ fun BankAccountDetailButton(
colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.primary),
modifier = modifier
.padding(start = 20.dp, end = 20.dp),
contentPadding = PaddingValues(20.dp)
contentPadding = PaddingValues(20.dp),
) {
Row(
modifier = modifier,
modifier = Modifier,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = btnText),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onPrimary
color = MaterialTheme.colorScheme.onPrimary,
)
if (hasTrailingIcon) {
Icon(
imageVector = Icons.Filled.ChevronRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimary
tint = MaterialTheme.colorScheme.onPrimary,
)
}
}
@ -192,29 +223,30 @@ fun BankAccountDetailButton(
}
}
@Preview(showBackground = true)
@Composable
private fun BankAccountDetailUpiDisabledPreview() {
BankAccountDetailScreen("Mifos Bank",
BankAccountDetailScreen(
"Mifos Bank",
"Mifos Account Holder",
"Mifos Branch",
"IFSC",
"type",
false,
{}, {}, {}, {}
{}, {}, {}, {},
)
}
@Preview(showBackground = true)
@Composable
private fun BankAccountDetailUpiEnabledPreview() {
BankAccountDetailScreen("Mifos Bank",
BankAccountDetailScreen(
"Mifos Bank",
"Mifos Account Holder",
"Mifos Branch",
"IFSC",
"type",
true,
{}, {}, {}, {}
{}, {}, {}, {},
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts.link
import androidx.compose.foundation.Image
@ -58,11 +67,10 @@ import org.mifospay.core.ui.DevicePreviews
import org.mifospay.feature.bank.accounts.R
import org.mifospay.feature.bank.accounts.choose.sim.ChooseSimDialogSheet
@Composable
fun LinkBankAccountRoute(
internal fun LinkBankAccountRoute(
viewModel: LinkBankAccountViewModel = hiltViewModel(),
onBackClick: () -> Unit
onBackClick: () -> Unit,
) {
val bankUiState by viewModel.bankListUiState.collectAsStateWithLifecycle()
var showSimBottomSheet by rememberSaveable { mutableStateOf(false) }
@ -79,7 +87,7 @@ fun LinkBankAccountRoute(
onBackClick()
}
}
}
},
)
}
@ -93,22 +101,23 @@ fun LinkBankAccountRoute(
viewModel.updateSelectedBank(it)
showSimBottomSheet = true
},
onBackClick = onBackClick
onBackClick = onBackClick,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LinkBankAccountScreen(
private fun LinkBankAccountScreen(
bankUiState: BankUiState,
showOverlyProgressBar: Boolean,
onBankSearch: (String) -> Unit,
onBankSelected: (Bank) -> Unit,
onBackClick: () -> Unit
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(
modifier = Modifier.background(color = MaterialTheme.colorScheme.surface),
modifier = modifier
.background(color = MaterialTheme.colorScheme.surface),
topBar = {
MifosTopAppBar(
titleRes = R.string.feature_accounts_link_bank_account,
@ -119,15 +128,17 @@ fun LinkBankAccountScreen(
containerColor = MaterialTheme.colorScheme.surface,
),
)
}) { paddingValues ->
},
) { paddingValues ->
Box(
modifier = Modifier.padding(paddingValues)
modifier = Modifier
.padding(paddingValues),
) {
when (bankUiState) {
is BankUiState.Loading -> {
MfLoadingWheel(
contentDesc = stringResource(R.string.feature_accounts_loading),
backgroundColor = MaterialTheme.colorScheme.surface
backgroundColor = MaterialTheme.colorScheme.surface,
)
}
@ -135,7 +146,7 @@ fun LinkBankAccountScreen(
BankListScreenContent(
banks = bankUiState.banks,
onBankSearch = onBankSearch,
onBankSelected = onBankSelected
onBankSelected = onBankSelected,
)
}
}
@ -148,19 +159,21 @@ fun LinkBankAccountScreen(
}
@Composable
fun BankListScreenContent(
private fun BankListScreenContent(
banks: List<Bank>,
onBankSearch: (String) -> Unit,
onBankSelected: (Bank) -> Unit
onBankSelected: (Bank) -> Unit,
modifier: Modifier = Modifier,
) {
var searchQuery by rememberSaveable { mutableStateOf("") }
Column(
modifier = Modifier
modifier = modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState())
.verticalScroll(rememberScrollState()),
) {
MifosOutlinedTextField(modifier = Modifier
MifosOutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
value = searchQuery,
@ -171,7 +184,8 @@ fun BankListScreenContent(
label = R.string.feature_accounts_search,
trailingIcon = {
Icon(imageVector = Icons.Filled.Search, contentDescription = null)
})
},
)
if (searchQuery.isBlank()) {
Spacer(modifier = Modifier.height(24.dp))
@ -179,23 +193,23 @@ fun BankListScreenContent(
text = stringResource(id = R.string.feature_accounts_popular_banks),
style = TextStyle(
MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium
fontWeight = FontWeight.Medium,
),
modifier = Modifier.padding(start = 16.dp)
modifier = Modifier.padding(start = 16.dp),
)
Spacer(modifier = Modifier.height(12.dp))
PopularBankGridBody(
banks = banks.filter { it.bankType == BankType.POPULAR },
onBankSelected = onBankSelected
onBankSelected = onBankSelected,
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(id = R.string.feature_accounts_other_banks),
style = TextStyle(
MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium
fontWeight = FontWeight.Medium,
),
modifier = Modifier.padding(start = 16.dp)
modifier = Modifier.padding(start = 16.dp),
)
Spacer(modifier = Modifier.height(12.dp))
}
@ -203,34 +217,37 @@ fun BankListScreenContent(
BankListBody(
banks = if (searchQuery.isBlank()) {
banks.filter { it.bankType == BankType.OTHER }
} else banks,
onBankSelected = onBankSelected
} else {
banks
},
onBankSelected = onBankSelected,
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun PopularBankGridBody(
private fun PopularBankGridBody(
banks: List<Bank>,
onBankSelected: (Bank) -> Unit
onBankSelected: (Bank) -> Unit,
modifier: Modifier = Modifier,
) {
MifosCard(
modifier = Modifier,
modifier = modifier,
shape = RoundedCornerShape(0.dp),
elevation = 2.dp,
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface)
colors = CardDefaults.cardColors(MaterialTheme.colorScheme.surface),
) {
FlowRow(
modifier = Modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp),
maxItemsInEachRow = 3
maxItemsInEachRow = 3,
) {
banks.forEach {
PopularBankItemBody(
modifier = Modifier.weight(1f),
bank = it,
onBankSelected = onBankSelected
onBankSelected = onBankSelected,
)
}
}
@ -238,10 +255,10 @@ fun PopularBankGridBody(
}
@Composable
fun PopularBankItemBody(
modifier: Modifier,
private fun PopularBankItemBody(
bank: Bank,
onBankSelected: (Bank) -> Unit
onBankSelected: (Bank) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@ -262,18 +279,19 @@ fun PopularBankItemBody(
Text(
text = bank.name,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp)
modifier = Modifier.padding(top = 4.dp, bottom = 16.dp),
)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun BankListBody(
private fun BankListBody(
banks: List<Bank>,
onBankSelected: (Bank) -> Unit
onBankSelected: (Bank) -> Unit,
modifier: Modifier = Modifier,
) {
FlowColumn {
FlowColumn(modifier) {
banks.forEach { bank ->
BankListItemBody(bank = bank, onBankSelected = onBankSelected)
}
@ -281,14 +299,15 @@ fun BankListBody(
}
@Composable
fun BankListItemBody(
private fun BankListItemBody(
bank: Bank,
onBankSelected: (Bank) -> Unit
onBankSelected: (Bank) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = Modifier
modifier = modifier
.fillMaxSize()
.clickable { onBankSelected(bank) }
.clickable { onBankSelected(bank) },
) {
HorizontalDivider()
Row(
@ -296,7 +315,7 @@ fun BankListItemBody(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, top = 8.dp, bottom = 8.dp)
.padding(start = 24.dp, top = 8.dp, bottom = 8.dp),
) {
Image(
modifier = Modifier.size(32.dp),
@ -305,7 +324,8 @@ fun BankListItemBody(
)
Text(
modifier = Modifier.padding(start = 16.dp, end = 16.dp),
text = bank.name, style = TextStyle(fontSize = 14.sp)
text = bank.name,
style = TextStyle(fontSize = 14.sp),
)
}
}
@ -323,7 +343,7 @@ private fun LinkBankAccountScreenPreview(
showOverlyProgressBar = false,
onBankSelected = { },
onBankSearch = { },
onBackClick = { }
onBackClick = { },
)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts.link
import androidx.compose.runtime.getValue
@ -24,17 +33,17 @@ import javax.inject.Inject
@HiltViewModel
class LinkBankAccountViewModel @Inject constructor(
localAssetRepository: MifosLocalAssetRepository
localAssetRepository: MifosLocalAssetRepository,
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
private val searchQuery = MutableStateFlow("")
private var selectedBank by mutableStateOf<Bank?>(null)
private val _bankAccountDetails: MutableStateFlow<BankAccountDetails?> = MutableStateFlow(null)
val bankAccountDetails: StateFlow<BankAccountDetails?> = _bankAccountDetails.asStateFlow()
private val accountDetails: MutableStateFlow<BankAccountDetails?> = MutableStateFlow(null)
val bankAccountDetails: StateFlow<BankAccountDetails?> = accountDetails.asStateFlow()
fun updateSearchQuery(query: String) {
_searchQuery.update { query }
searchQuery.update { query }
}
fun updateSelectedBank(bank: Bank) {
@ -42,9 +51,9 @@ class LinkBankAccountViewModel @Inject constructor(
}
val bankListUiState: StateFlow<BankUiState> = combine(
_searchQuery,
searchQuery,
localAssetRepository.getBanks(),
::Pair
::Pair,
).map { searchQueryAndBanks ->
val searchQuery = searchQueryAndBanks.first
val localBanks = searchQueryAndBanks.second.map {
@ -55,7 +64,7 @@ class LinkBankAccountViewModel @Inject constructor(
addAll(localBanks)
}.distinctBy { it.name }
BankUiState.Success(
banks.filter { it.name.contains(searchQuery.lowercase(), ignoreCase = true) }
banks.filter { it.name.contains(searchQuery.lowercase(), ignoreCase = true) },
)
}.stateIn(
scope = viewModelScope,
@ -70,17 +79,20 @@ class LinkBankAccountViewModel @Inject constructor(
Bank("PNB Bank", R.drawable.feature_accounts_logo_pnb, BankType.POPULAR),
Bank("HDFC Bank", R.drawable.feature_accounts_logo_hdfc, BankType.POPULAR),
Bank("ICICI Bank", R.drawable.feature_accounts_logo_icici, BankType.POPULAR),
Bank("AXIS Bank", R.drawable.feature_accounts_logo_axis, BankType.POPULAR)
Bank("AXIS Bank", R.drawable.feature_accounts_logo_axis, BankType.POPULAR),
)
}
fun fetchBankAccountDetails(onBankDetailsSuccess: () -> Unit) {
// TODO:: UPI API implement, Implement with real API,
// It revert back to Account Screen after successful BankAccount Add
_bankAccountDetails.update {
accountDetails.update {
BankAccountDetails(
selectedBank?.name, "Ankur Sharma", "New Delhi",
mRandom.nextInt().toString() + " ", "Savings"
selectedBank?.name,
"Ankur Sharma",
"New Delhi",
mRandom.nextInt().toString() + " ",
"Savings",
)
}
onBankDetailsSuccess.invoke()

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts.link
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -20,6 +29,6 @@ class LinkBankUiStatePreviewParameterProvider : PreviewParameterProvider<BankUiS
}
override val values: Sequence<BankUiState> = sequenceOf(
BankUiState.Success(banks = banks)
BankUiState.Success(banks = banks),
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts.navigation
import androidx.navigation.NavController
@ -16,14 +25,14 @@ fun NavGraphBuilder.bankAccountDetailScreen(
onSetupUpiPin: (BankAccountDetails, Int) -> Unit,
onChangeUpiPin: (BankAccountDetails, Int) -> Unit,
onForgotUpiPin: (BankAccountDetails, Int) -> Unit,
onBackClick: (BankAccountDetails, Int) -> Unit
onBackClick: (BankAccountDetails, Int) -> Unit,
) {
composable(
route = "$BANK_ACCOUNT_DETAIL_ROUTE/{${Constants.BANK_ACCOUNT_DETAILS}}/{${Constants.INDEX}}",
arguments = listOf(
navArgument(Constants.BANK_ACCOUNT_DETAILS) { type = NavType.StringType },
navArgument(Constants.INDEX) { type = NavType.IntType }
)
navArgument(Constants.INDEX) { type = NavType.IntType },
),
) { backStackEntry ->
val bankAccountDetails =
backStackEntry.arguments?.getParcelable(Constants.BANK_ACCOUNT_DETAILS)
@ -47,7 +56,7 @@ fun NavGraphBuilder.bankAccountDetailScreen(
// TODO: Use global snackbar
}
},
navigateBack = { onBackClick(bankAccountDetails, index) }
navigateBack = { onBackClick(bankAccountDetails, index) },
)
}
}
@ -55,7 +64,7 @@ fun NavGraphBuilder.bankAccountDetailScreen(
fun NavController.navigateToBankAccountDetail(
bankAccountDetails: BankAccountDetails,
index: Int,
navOptions: NavOptions? = null
navOptions: NavOptions? = null,
) {
this.navigate("$BANK_ACCOUNT_DETAIL_ROUTE/$bankAccountDetails/$index", navOptions)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.bank.accounts.navigation
import androidx.navigation.NavController
@ -12,11 +21,11 @@ fun NavController.navigateToLinkBankAccount(navOptions: NavOptions? = null) =
navigate(LINK_BANK_ACCOUNT_ROUTE, navOptions)
fun NavGraphBuilder.linkBankAccountScreen(
onBackClick: () -> Unit
onBackClick: () -> Unit,
) {
composable(route = LINK_BANK_ACCOUNT_ROUTE) {
LinkBankAccountRoute(
onBackClick = onBackClick
onBackClick = onBackClick,
)
}
}

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="60dp"
android:height="60dp"

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<color name="feature_accounts_colorBlack87">#DE000000</color>
<color name="feature_accounts_colorTextPrimary">@color/feature_accounts_colorBlack87</color>

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<string name="feature_accounts_bank_account_details">Bank Account Details</string>
<string name="feature_accounts_bank_name">Bank Name</string>

View File

@ -1,16 +0,0 @@
package org.mifospay.feature.bank.accounts
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* 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

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)

View File

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

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.login
import android.os.Bundle

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.login
import android.content.Context
@ -53,8 +62,9 @@ import org.mifospay.feature.auth.socialSignup.SocialSignupMethodContentScreen
import org.mifospay.feature.passcode.PassCodeActivity
@Composable
fun LoginScreen(
viewModel: LoginViewModel = hiltViewModel()
internal fun LoginScreen(
modifier: Modifier = Modifier,
viewModel: LoginViewModel = hiltViewModel(),
) {
val context = LocalContext.current
val showProgress by viewModel.showProgress.collectAsStateWithLifecycle()
@ -65,6 +75,7 @@ fun LoginScreen(
}
LoginScreenContent(
modifier = modifier,
showProgress = showProgress,
login = { username, password ->
viewModel.loginUser(
@ -72,9 +83,9 @@ fun LoginScreen(
password = password,
onLoginFailed = { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
},
)
}
},
)
if (isLoginSuccess) {
@ -84,20 +95,21 @@ fun LoginScreen(
@Composable
@Suppress("LongMethod")
fun LoginScreenContent(
private fun LoginScreenContent(
showProgress: Boolean,
login: (username: String, password: String) -> Unit,
modifier: Modifier = Modifier,
) {
var showSignUpScreen by rememberSaveable { mutableStateOf(false) }
var userName by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(
TextFieldValue("")
TextFieldValue(""),
)
}
var password by rememberSaveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(
TextFieldValue("")
TextFieldValue(""),
)
}
var passwordVisibility: Boolean by remember { mutableStateOf(false) }
@ -108,25 +120,25 @@ fun LoginScreenContent(
}
}
Box {
Box(modifier) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState())
.padding(top = 100.dp, start = 48.dp, end = 48.dp),
horizontalAlignment = Alignment.Start
horizontalAlignment = Alignment.Start,
) {
Text(
text = stringResource(id = R.string.feature_auth_login),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary
color = MaterialTheme.colorScheme.primary,
)
Text(
modifier = Modifier
.padding(top = 32.dp),
text = stringResource(id = R.string.feature_auth_welcome_back),
style = styleNormal18sp.copy(color = grey)
style = styleNormal18sp.copy(color = grey),
)
Spacer(modifier = Modifier.padding(top = 32.dp))
MifosOutlinedTextField(
@ -135,7 +147,7 @@ fun LoginScreenContent(
onValueChange = {
userName = it
},
label = R.string.feature_auth_username
label = R.string.feature_auth_username,
)
Spacer(modifier = Modifier.padding(top = 16.dp))
MifosOutlinedTextField(
@ -147,15 +159,19 @@ fun LoginScreenContent(
label = R.string.feature_auth_password,
visualTransformation = if (passwordVisibility) {
VisualTransformation.None
} else PasswordVisualTransformation(),
} else {
PasswordVisualTransformation()
},
trailingIcon = {
val image = if (passwordVisibility)
val image = if (passwordVisibility) {
Icons.Filled.Visibility
else Icons.Filled.VisibilityOff
} else {
Icons.Filled.VisibilityOff
}
IconButton(onClick = { passwordVisibility = !passwordVisibility }) {
Icon(imageVector = image, null)
}
}
},
)
Button(
modifier = Modifier
@ -166,12 +182,12 @@ fun LoginScreenContent(
onClick = {
login.invoke(userName.text, password.text)
},
contentPadding = PaddingValues(12.dp)
contentPadding = PaddingValues(12.dp),
) {
Text(
text = stringResource(id = R.string.feature_auth_login).uppercase(),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onPrimary
color = MaterialTheme.colorScheme.onPrimary,
)
}
// Hide reset password for now
@ -197,12 +213,12 @@ fun LoginScreenContent(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp),
horizontalArrangement = Arrangement.Center
horizontalArrangement = Arrangement.Center,
) {
Text(
text = "Dont have an account yet? ",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Text(
modifier = Modifier.clickable {
@ -218,11 +234,10 @@ fun LoginScreenContent(
if (showProgress) {
MfOverlayLoadingWheel(
contentDesc = stringResource(id = R.string.feature_auth_logging_in)
contentDesc = stringResource(id = R.string.feature_auth_logging_in),
)
}
}
}
/**
@ -237,11 +252,11 @@ private fun startPassCodeActivity(context: Context) {
@Preview(showSystemUi = true, device = "id:pixel_5")
@Composable
fun LoanScreenPreview() {
private fun LoanScreenPreview() {
MifosTheme {
LoginScreenContent(
showProgress = false,
login = { _, _ -> }
login = { _, _ -> },
)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.login
import android.util.Log
@ -43,7 +52,6 @@ class LoginViewModel @Inject constructor(
_isLoginSuccess.update { isLoginSuccess }
}
/**
* Authenticate User with username and password
* @param username
@ -53,14 +61,16 @@ class LoginViewModel @Inject constructor(
fun loginUser(
username: String,
password: String,
onLoginFailed: (String) -> Unit
onLoginFailed: (String) -> Unit,
) {
updateProgressState(true)
authenticateUserUseCase.walletRequestValues =
AuthenticateUser.RequestValues(username, password)
val requestValue = authenticateUserUseCase.walletRequestValues
mUsecaseHandler.execute(authenticateUserUseCase, requestValue,
mUsecaseHandler.execute(
authenticateUserUseCase,
requestValue,
object : UseCaseCallback<AuthenticateUser.ResponseValue> {
override fun onSuccess(response: AuthenticateUser.ResponseValue) {
saveAuthTokenInPref(response.user)
@ -72,16 +82,17 @@ class LoginViewModel @Inject constructor(
updateProgressState(false)
onLoginFailed(message)
}
})
},
)
}
/**
* Fetch user details return by authenticated user
* @param user
*/
private fun fetchUserDetails(user: User) {
mUsecaseHandler.execute(fetchUserDetailsUseCase,
mUsecaseHandler.execute(
fetchUserDetailsUseCase,
FetchUserDetails.RequestValues(user.userId),
object : UseCaseCallback<FetchUserDetails.ResponseValue> {
override fun onSuccess(response: FetchUserDetails.ResponseValue) {
@ -92,7 +103,8 @@ class LoginViewModel @Inject constructor(
updateProgressState(false)
Log.d("Login User Detailed: ", message)
}
})
},
)
}
/**
@ -116,7 +128,8 @@ class LoginViewModel @Inject constructor(
override fun onError(message: String) {
updateProgressState(false)
}
})
},
)
}
private fun saveAuthTokenInPref(user: User) {
@ -128,7 +141,7 @@ class LoginViewModel @Inject constructor(
*/
private fun saveUserDetails(
user: User,
userWithRole: UserWithRole
userWithRole: UserWithRole,
) {
val userName = user.username
val userID = user.userId

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.mobileVerify
import android.widget.Toast
@ -41,16 +50,17 @@ import org.mifospay.core.designsystem.component.MifosOutlinedTextField
import org.mifospay.core.designsystem.theme.MifosTheme
import org.mifospay.feature.auth.R
@Composable
fun MobileVerificationScreen(
internal fun MobileVerificationScreen(
onOtpVerificationSuccess: (String) -> Unit,
modifier: Modifier = Modifier,
viewModel: MobileVerificationViewModel = hiltViewModel(),
onOtpVerificationSuccess: (String) -> Unit
) {
val context = LocalContext.current
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
MobileVerificationScreen(uiState = uiState,
MobileVerificationScreen(
uiState = uiState,
showProgressState = viewModel.showProgress,
verifyMobileAndRequestOtp = { phone, fullPhone ->
viewModel.verifyMobileAndRequestOtp(fullPhone, phone) {
@ -63,18 +73,19 @@ fun MobileVerificationScreen(
viewModel.verifyOTP(validatedOtp) {
onOtpVerificationSuccess(fullNumber)
}
}
},
modifier = modifier,
)
}
@Composable
fun MobileVerificationScreen(
private fun MobileVerificationScreen(
uiState: MobileVerificationUiState,
showProgressState: Boolean = false,
verifyMobileAndRequestOtp: (String, String) -> Unit,
verifyOtp: (String, String) -> Unit
verifyOtp: (String, String) -> Unit,
modifier: Modifier = Modifier,
showProgressState: Boolean = false,
) {
var phoneNumber by rememberSaveable { mutableStateOf("") }
var fullPhoneNumber by rememberSaveable { mutableStateOf("") }
var isNumberValid: Boolean by rememberSaveable { mutableStateOf(false) }
@ -90,19 +101,18 @@ fun MobileVerificationScreen(
}
}
Box {
Box(modifier) {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = Color.White)
.focusable(!showProgressState),
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = MaterialTheme.colorScheme.primary),
verticalArrangement = Arrangement.Top
verticalArrangement = Arrangement.Top,
) {
Text(
modifier = Modifier.padding(top = 48.dp, start = 24.dp, end = 24.dp),
@ -111,11 +121,14 @@ fun MobileVerificationScreen(
} else {
stringResource(id = R.string.feature_auth_enter_otp)
},
style = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onPrimary)
style = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onPrimary),
)
Text(
modifier = Modifier.padding(
top = 4.dp, bottom = 32.dp, start = 24.dp, end = 24.dp
top = 4.dp,
bottom = 32.dp,
start = 24.dp,
end = 24.dp,
),
text = if (uiState == MobileVerificationUiState.VerifyPhone) {
stringResource(id = R.string.feature_auth_enter_mobile_number_description)
@ -123,7 +136,7 @@ fun MobileVerificationScreen(
stringResource(id = R.string.feature_auth_enter_otp_received_on_your_registered_device)
},
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onPrimary
color = MaterialTheme.colorScheme.onPrimary,
)
}
@ -137,7 +150,7 @@ fun MobileVerificationScreen(
phoneNumber = phone
fullPhoneNumber = fullPhone
isNumberValid = valid
}
},
)
}
@ -145,11 +158,12 @@ fun MobileVerificationScreen(
EnterOtpScreen(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 48.dp, vertical = 24.dp)
) { isValidated, otp ->
.padding(horizontal = 48.dp, vertical = 24.dp),
onOtpValidated = { isValidated, otp ->
isOtpValidated = isValidated
validatedOtp = otp
}
},
)
}
}
@ -171,8 +185,9 @@ fun MobileVerificationScreen(
stringResource(id = R.string.feature_auth_verify_phone).uppercase()
} else {
stringResource(id = R.string.feature_auth_verify_otp).uppercase()
}, style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onPrimary
},
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onPrimary,
)
}
}
@ -184,29 +199,29 @@ fun MobileVerificationScreen(
}
@Composable
fun EnterPhoneScreen(
modifier: Modifier,
onNumberUpdated: (String, String, Boolean) -> Unit
private fun EnterPhoneScreen(
onNumberUpdated: (String, String, Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
val keyboardController = LocalSoftwareKeyboardController.current
TogiCountryCodePicker(
modifier = modifier,
shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = MaterialTheme.colorScheme.primary
focusedBorderColor = MaterialTheme.colorScheme.primary,
),
onValueChange = { (code, phone), isValid ->
onNumberUpdated(phone, code + phone, isValid)
},
label = { Text(stringResource(id = R.string.feature_auth_phone_number)) },
keyboardActions = KeyboardActions { keyboardController?.hide() }
keyboardActions = KeyboardActions { keyboardController?.hide() },
)
}
@Composable
fun EnterOtpScreen(
modifier: Modifier,
onOtpValidated: (Boolean, String) -> Unit
private fun EnterOtpScreen(
onOtpValidated: (Boolean, String) -> Unit,
modifier: Modifier = Modifier,
) {
val keyboardController = LocalSoftwareKeyboardController.current
var otp by rememberSaveable(stateSaver = TextFieldValue.Saver) {
@ -221,20 +236,21 @@ fun EnterOtpScreen(
onOtpValidated(otp.text.length == 6, otp.text)
},
label = R.string.feature_auth_enter_otp,
keyboardActions = KeyboardActions { keyboardController?.hide() }
keyboardActions = KeyboardActions { keyboardController?.hide() },
)
}
@Composable
fun ShowProgressScreen(
private fun ShowProgressScreen(
uiState: MobileVerificationUiState,
modifier: Modifier = Modifier,
) {
Box(
modifier = Modifier
modifier = modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f))
.focusable(),
contentAlignment = Alignment.Center
contentAlignment = Alignment.Center,
) {
MifosLoadingWheel(
modifier = Modifier.wrapContentSize(),
@ -242,31 +258,33 @@ fun ShowProgressScreen(
Constants.SENDING_OTP_TO_YOUR_MOBILE_NUMBER
} else {
Constants.VERIFYING_OTP
}
},
)
}
}
@Preview
@Composable
fun MobileVerificationScreenVerifyPhonePreview() {
private fun MobileVerificationScreenVerifyPhonePreview() {
MifosTheme {
MobileVerificationScreen(uiState = MobileVerificationUiState.VerifyPhone,
MobileVerificationScreen(
uiState = MobileVerificationUiState.VerifyPhone,
showProgressState = false,
verifyMobileAndRequestOtp = { _, _ -> },
verifyOtp = { _, _ -> }
verifyOtp = { _, _ -> },
)
}
}
@Preview
@Composable
fun MobileVerificationScreenVerifyOtpPreview() {
private fun MobileVerificationScreenVerifyOtpPreview() {
MifosTheme {
MobileVerificationScreen(uiState = MobileVerificationUiState.VerifyOtp,
MobileVerificationScreen(
uiState = MobileVerificationUiState.VerifyOtp,
showProgressState = false,
verifyMobileAndRequestOtp = { _, _ -> },
verifyOtp = { _, _ -> }
verifyOtp = { _, _ -> },
)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.mobileVerify
import androidx.compose.runtime.getValue
@ -20,7 +29,7 @@ import javax.inject.Inject
@Suppress("UnusedParameter")
class MobileVerificationViewModel @Inject constructor(
private val mUseCaseHandler: UseCaseHandler,
private val searchClientUseCase: SearchClient
private val searchClientUseCase: SearchClient,
) : ViewModel() {
private val _uiState =
@ -35,10 +44,11 @@ class MobileVerificationViewModel @Inject constructor(
fun verifyMobileAndRequestOtp(
fullNumber: String,
mobileNo: String,
onError: (String?) -> Unit
onError: (String?) -> Unit,
) {
showProgress = true
mUseCaseHandler.execute(searchClientUseCase,
mUseCaseHandler.execute(
searchClientUseCase,
fullNumber.let { SearchClient.RequestValues(it) },
object : UseCase.UseCaseCallback<SearchClient.ResponseValue> {
override fun onSuccess(response: SearchClient.ResponseValue) {
@ -49,7 +59,8 @@ class MobileVerificationViewModel @Inject constructor(
override fun onError(message: String) {
requestOtp(fullNumber)
}
})
},
)
}
/**

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.navigation
import androidx.navigation.NavController
@ -10,7 +19,7 @@ const val LOGIN_ROUTE = "login_route"
@Suppress("UnusedParameter")
fun NavGraphBuilder.loginScreen(
onDismissSignUp: () -> Unit,
onNavigateToMobileVerificationScreen:(Int,String,String,String,String,) -> Unit
onNavigateToMobileVerificationScreen: (Int, String, String, String, String) -> Unit,
) {
composable(route = LOGIN_ROUTE) {
LoginScreen(

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
@file:Suppress("MaxLineLength")
package org.mifospay.feature.auth.navigation
@ -13,17 +22,32 @@ import org.mifospay.feature.auth.mobileVerify.MobileVerificationScreen
const val MOBILE_VERIFICATION_ROUTE = "mobile_verification_route"
fun NavGraphBuilder.mobileVerificationScreen(
onOtpVerificationSuccess: (String, Map<String, Any?>) -> Unit
onOtpVerificationSuccess: (String, Map<String, Any?>) -> Unit,
) {
composable(
route = "$MOBILE_VERIFICATION_ROUTE?mifosSignedUp={mifosSignedUp}&googleDisplayName={googleDisplayName}&googleEmail={googleEmail}&googleFamilyName={googleFamilyName}&googleGivenName={googleGivenName}",
arguments = listOf(
navArgument("mifosSignedUp") { type = NavType.IntType; defaultValue = 0 },
navArgument("googleDisplayName") { type = NavType.StringType; nullable = true },
navArgument("googleEmail") { type = NavType.StringType; nullable = true },
navArgument("googleFamilyName") { type = NavType.StringType; nullable = true },
navArgument("googleGivenName") { type = NavType.StringType; nullable = true }
)
navArgument("mifosSignedUp") {
type = NavType.IntType
defaultValue = 0
},
navArgument("googleDisplayName") {
type = NavType.StringType
nullable = true
},
navArgument("googleEmail") {
type = NavType.StringType
nullable = true
},
navArgument("googleFamilyName") {
type = NavType.StringType
nullable = true
},
navArgument("googleGivenName") {
type = NavType.StringType
nullable = true
},
),
) { backStackEntry ->
val mifosSignedUp = backStackEntry.arguments?.getInt("mifosSignedUp") ?: 0
val googleDisplayName = backStackEntry.arguments?.getString("googleDisplayName")
@ -40,10 +64,10 @@ fun NavGraphBuilder.mobileVerificationScreen(
Constants.GOOGLE_FAMILY_NAME to googleFamilyName,
Constants.GOOGLE_GIVEN_NAME to googleGivenName,
Constants.COUNTRY to "Canada",
Constants.MOBILE_NUMBER to fullNumber
Constants.MOBILE_NUMBER to fullNumber,
)
onOtpVerificationSuccess(fullNumber, extraData)
}
},
)
}
}
@ -53,8 +77,7 @@ fun NavController.navigateToMobileVerification(
googleDisplayName: String?,
googleEmail: String?,
googleFamilyName: String?,
googleGivenName: String?
googleGivenName: String?,
) {
this.navigate("$MOBILE_VERIFICATION_ROUTE?mifosSignedUp=$mifosSignedUp&googleDisplayName=$googleDisplayName&googleEmail=$googleEmail&googleFamilyName=$googleFamilyName&googleGivenName=$googleGivenName")
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
@file:Suppress("MaxLineLength")
package org.mifospay.feature.auth.navigation
@ -14,19 +23,40 @@ const val SIGNUP_ROUTE = "signup_route"
@Suppress("UnusedParameter")
fun NavGraphBuilder.signupScreen(
onLoginSuccess: () -> Unit,
onRegisterSuccess: () -> Unit
onRegisterSuccess: () -> Unit,
) {
composable(
route = "$SIGNUP_ROUTE?savingProductId={savingProductId}&mobileNumber={mobileNumber}&country={country}&email={email}&firstName={firstName}&lastName={lastName}&businessName={businessName}",
arguments = listOf(
navArgument("savingProductId") { type = NavType.IntType; defaultValue = 0 },
navArgument("mobileNumber") { type = NavType.StringType; defaultValue = "" },
navArgument("country") { type = NavType.StringType; defaultValue = "" },
navArgument("email") { type = NavType.StringType; defaultValue = "" },
navArgument("firstName") { type = NavType.StringType; defaultValue = "" },
navArgument("lastName") { type = NavType.StringType; defaultValue = "" },
navArgument("businessName") { type = NavType.StringType; defaultValue = "" }
)
navArgument("savingProductId") {
type = NavType.IntType
defaultValue = 0
},
navArgument("mobileNumber") {
type = NavType.StringType
defaultValue = ""
},
navArgument("country") {
type = NavType.StringType
defaultValue = ""
},
navArgument("email") {
type = NavType.StringType
defaultValue = ""
},
navArgument("firstName") {
type = NavType.StringType
defaultValue = ""
},
navArgument("lastName") {
type = NavType.StringType
defaultValue = ""
},
navArgument("businessName") {
type = NavType.StringType
defaultValue = ""
},
),
) { backStackEntry ->
val savingProductId = backStackEntry.arguments?.getInt("savingProductId") ?: 0
val mobileNumber = backStackEntry.arguments?.getString("mobileNumber") ?: ""
@ -44,7 +74,7 @@ fun NavGraphBuilder.signupScreen(
email = email,
firstName = firstName,
lastName = lastName,
businessName = businessName
businessName = businessName,
)
}
}
@ -56,12 +86,12 @@ fun NavController.navigateToSignup(
email: String = "",
firstName: String = "",
lastName: String = "",
businessName: String = ""
businessName: String = "",
) {
this.navigate(
"$SIGNUP_ROUTE?savingProductId=$savingProductId" +
"&mobileNumber=$mobileNumber&country=$country&email=$email" +
"&firstName=$firstName&lastName=$lastName&businessName=$businessName"
"&firstName=$firstName&lastName=$lastName&businessName=$businessName",
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.signup
import android.widget.Toast
@ -49,18 +58,18 @@ import org.mifospay.feature.auth.R
import org.mifospay.feature.auth.utils.ValidateUtil.isValidEmail
import java.util.Locale
@Composable
fun SignupScreen(
viewModel: SignupViewModel = hiltViewModel(),
internal fun SignupScreen(
savingProductId: Int,
mobileNumber: String,
country: String,
email: String,
firstName: String,
lastName: String,
businessName: String,
onLoginSuccess: () -> Unit,
savingProductId:Int,
mobileNumber:String,
country:String,
email:String,
firstName:String,
lastName:String,
businessName:String
modifier: Modifier = Modifier,
viewModel: SignupViewModel = hiltViewModel(),
) {
val context = LocalContext.current
@ -74,7 +83,7 @@ fun SignupScreen(
email = email,
firstName = firstName,
lastName = lastName,
businessName = businessName
businessName = businessName,
)
}
LaunchedEffect(viewModel.isLoginSuccess) {
@ -84,6 +93,7 @@ fun SignupScreen(
}
SignupScreenContent(
modifier = modifier,
showProgressState = viewModel.showProgress,
data = viewModel.signupData,
stateList = stateList,
@ -91,27 +101,28 @@ fun SignupScreen(
viewModel.registerUser(it) { message ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
},
)
}
@Composable
@Suppress("LongMethod", "CyclomaticComplexMethod")
fun SignupScreenContent(
showProgressState: Boolean = false,
private fun SignupScreenContent(
data: SignupData,
stateList: List<State>,
onCompleteRegistration: (SignupData) -> Unit
onCompleteRegistration: (SignupData) -> Unit,
modifier: Modifier = Modifier,
showProgressState: Boolean = false,
) {
val context = LocalContext.current
var firstName by rememberSaveable { mutableStateOf(data.firstName ?: "") }
var lastName by rememberSaveable { mutableStateOf(data.lastName ?: "") }
var email by rememberSaveable { mutableStateOf(data.email ?: "") }
var userName by rememberSaveable {
mutableStateOf(data.email?.ifEmpty { "" }
?: data.email?.let { it.substring(0, it.indexOf('@')) } ?: ""
mutableStateOf(
data.email?.ifEmpty { "" }
?: data.email?.let { it.substring(0, it.indexOf('@')) } ?: "",
)
}
var addressLine1 by rememberSaveable { mutableStateOf("") }
@ -127,16 +138,19 @@ fun SignupScreenContent(
var selectedState by rememberSaveable { mutableStateOf<State?>(null) }
fun validateAllFields() {
val isAnyFieldEmpty = firstName.isEmpty() || lastName.isEmpty() || email.isEmpty()
|| userName.isEmpty() || addressLine1.isEmpty() || addressLine2.isEmpty()
|| pinCode.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()
|| selectedState == null
val isNameOfBusinessEmpty = data.mifosSavingsProductId == MIFOS_MERCHANT_SAVINGS_PRODUCT_ID
&& nameOfBusiness.isEmpty()
val isAnyFieldEmpty = firstName.isEmpty() || lastName.isEmpty() || email.isEmpty() ||
userName.isEmpty() || addressLine1.isEmpty() || addressLine2.isEmpty() ||
pinCode.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() ||
selectedState == null
val isNameOfBusinessEmpty = data.mifosSavingsProductId == MIFOS_MERCHANT_SAVINGS_PRODUCT_ID &&
nameOfBusiness.isEmpty()
if (!email.isValidEmail()) {
Toast.makeText(
context, context.getString(R.string.feature_auth_validate_email), Toast.LENGTH_SHORT
context,
context.getString(R.string.feature_auth_validate_email),
Toast.LENGTH_SHORT,
).show()
return
}
@ -145,7 +159,7 @@ fun SignupScreenContent(
Toast.makeText(
context,
context.getString(R.string.feature_auth_all_fields_are_mandatory),
Toast.LENGTH_SHORT
Toast.LENGTH_SHORT,
).show()
return
}
@ -162,12 +176,12 @@ fun SignupScreenContent(
pinCode = pinCode,
businessName = nameOfBusiness,
password = password,
stateId = selectedState?.id
stateId = selectedState?.id,
)
onCompleteRegistration.invoke(signUpData)
}
Box {
Box(modifier) {
Column(
modifier = Modifier
.fillMaxSize()
@ -179,33 +193,37 @@ fun SignupScreenContent(
modifier = Modifier
.fillMaxWidth()
.background(color = MaterialTheme.colorScheme.primary),
verticalArrangement = Arrangement.Top
verticalArrangement = Arrangement.Top,
) {
Text(
modifier = Modifier.padding(top = 48.dp, start = 24.dp, end = 24.dp),
text = stringResource(id = R.string.feature_auth_complete_your_registration),
style = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onPrimary)
style = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onPrimary),
)
Text(
modifier = Modifier.padding(
top = 4.dp, bottom = 32.dp, start = 24.dp, end = 24.dp
top = 4.dp,
bottom = 32.dp,
start = 24.dp,
end = 24.dp,
),
text = stringResource(id = R.string.feature_auth_all_fields_are_mandatory),
style = MaterialTheme.typography.bodySmall.copy(color = Color.White)
style = MaterialTheme.typography.bodySmall.copy(color = Color.White),
)
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp)
.focusable(!showProgressState)
.focusable(!showProgressState),
) {
UserInfoTextField(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
label = stringResource(id = R.string.feature_auth_first_name),
value = firstName
value = firstName,
) {
firstName = it
}
@ -214,7 +232,7 @@ fun SignupScreenContent(
.fillMaxWidth()
.padding(top = 8.dp),
label = stringResource(id = R.string.feature_auth_last_name),
value = lastName
value = lastName,
) {
lastName = it
}
@ -223,7 +241,7 @@ fun SignupScreenContent(
.fillMaxWidth()
.padding(top = 8.dp),
label = stringResource(id = R.string.feature_auth_username),
value = userName
value = userName,
) {
userName = it
}
@ -244,7 +262,7 @@ fun SignupScreenContent(
.fillMaxWidth()
.padding(top = 8.dp),
label = stringResource(id = R.string.feature_auth_email),
value = email
value = email,
) {
email = it
}
@ -254,7 +272,7 @@ fun SignupScreenContent(
.fillMaxWidth()
.padding(top = 8.dp),
label = stringResource(id = R.string.feature_auth_name_of_business),
value = nameOfBusiness
value = nameOfBusiness,
) {
nameOfBusiness = it
}
@ -264,7 +282,7 @@ fun SignupScreenContent(
.fillMaxWidth()
.padding(top = 8.dp),
label = stringResource(id = R.string.feature_auth_address_line_1),
value = addressLine1
value = addressLine1,
) {
addressLine1 = it
}
@ -273,14 +291,14 @@ fun SignupScreenContent(
.fillMaxWidth()
.padding(top = 8.dp),
label = stringResource(id = R.string.feature_auth_address_line_2),
value = addressLine2
value = addressLine2,
) {
addressLine2 = it
}
UserInfoTextField(
modifier = Modifier.padding(top = 8.dp),
label = stringResource(id = R.string.feature_auth_pin_code),
value = pinCode
value = pinCode,
) {
pinCode = it
}
@ -288,7 +306,7 @@ fun SignupScreenContent(
MifosStateDropDownOutlinedTextField(
value = selectedState?.name ?: "",
label = stringResource(id = R.string.feature_auth_state),
stateList = stateList
stateList = stateList,
) {
selectedState = it
}
@ -307,7 +325,7 @@ fun SignupScreenContent(
) {
Text(
text = stringResource(id = R.string.feature_auth_complete),
style = styleMedium16sp.copy(color = MaterialTheme.colorScheme.onPrimary)
style = styleMedium16sp.copy(color = MaterialTheme.colorScheme.onPrimary),
)
}
}
@ -321,19 +339,19 @@ fun SignupScreenContent(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MifosStateDropDownOutlinedTextField(
modifier: Modifier = Modifier,
private fun MifosStateDropDownOutlinedTextField(
value: String,
label: String,
stateList: List<State>,
onSelectedState: (State) -> Unit
modifier: Modifier = Modifier,
onSelectedState: (State) -> Unit = {},
) {
var expanded by rememberSaveable { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
},
) {
OutlinedTextField(
modifier = modifier.menuAnchor(),
@ -343,21 +361,21 @@ fun MifosStateDropDownOutlinedTextField(
label = { Text(label) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
}
},
)
DropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
})
{
},
) {
stateList.forEach {
DropdownMenuItem(
text = { Text(text = it.name) },
onClick = {
expanded = false
onSelectedState(it)
}
},
)
}
}
@ -365,11 +383,11 @@ fun MifosStateDropDownOutlinedTextField(
}
@Composable
fun UserInfoTextField(
modifier: Modifier = Modifier,
private fun UserInfoTextField(
label: String,
value: String,
onValueChange: (String) -> Unit
modifier: Modifier = Modifier,
onValueChange: (String) -> Unit = {},
) {
MfOutlinedTextField(
modifier = modifier,
@ -377,12 +395,12 @@ fun UserInfoTextField(
label = label,
isError = value.isEmpty(),
errorMessage = stringResource(id = R.string.feature_auth_mandatory),
onValueChange = onValueChange
onValueChange = onValueChange,
)
}
@Composable
fun PasswordAndConfirmPassword(
private fun PasswordAndConfirmPassword(
password: String,
onPasswordChange: (String) -> Unit,
confirmPassword: String,
@ -391,8 +409,9 @@ fun PasswordAndConfirmPassword(
onTogglePasswordVisibility: () -> Unit,
isConfirmPasswordVisible: Boolean,
onConfirmTogglePasswordVisibility: () -> Unit,
modifier: Modifier = Modifier,
) {
Column {
Column(modifier) {
MfPasswordTextField(
modifier = Modifier.fillMaxWidth(),
password = password,
@ -402,10 +421,12 @@ fun PasswordAndConfirmPassword(
stringResource(id = R.string.feature_auth_password_cannot_be_empty)
} else if (password.length < 6) {
stringResource(id = R.string.feature_auth_password_must_be_least_6_characters)
} else null,
} else {
null
},
onPasswordChange = onPasswordChange,
isPasswordVisible = isPasswordVisible,
onTogglePasswordVisibility = onTogglePasswordVisibility
onTogglePasswordVisibility = onTogglePasswordVisibility,
)
MfPasswordTextField(
modifier = Modifier.fillMaxWidth(),
@ -416,19 +437,25 @@ fun PasswordAndConfirmPassword(
stringResource(id = R.string.feature_auth_confirm_password_cannot_empty)
} else if (password != confirmPassword) {
stringResource(id = R.string.feature_auth_passwords_do_not_match)
} else null,
} else {
null
},
onPasswordChange = onConfirmPasswordChange,
isPasswordVisible = isConfirmPasswordVisible,
onTogglePasswordVisibility = onConfirmTogglePasswordVisibility
onTogglePasswordVisibility = onConfirmTogglePasswordVisibility,
)
if (password.length >= 6) {
Text(
modifier = Modifier.padding(top = 8.dp),
text = "${stringResource(id = R.string.feature_auth_password_strength)}${
getPasswordStrength(password).replaceFirstChar {
if (it.isLowerCase()) it.titlecase(
Locale.ENGLISH
) else it.toString()
if (it.isLowerCase()) {
it.titlecase(
Locale.ENGLISH,
)
} else {
it.toString()
}
}
}",
color = getPasswordStrengthColor(password),
@ -437,7 +464,6 @@ fun PasswordAndConfirmPassword(
}
}
private fun getPasswordStrength(password: String): String {
val hasUpperCase = password.any { it.isUpperCase() }
val hasLowerCase = password.any { it.isLowerCase() }
@ -448,12 +474,12 @@ private fun getPasswordStrength(password: String): String {
hasUpperCase.toInt(),
hasLowerCase.toInt(),
hasNumbers.toInt(),
hasSymbols.toInt()
hasSymbols.toInt(),
).sum()
return PasswordStrength.entries[numTypesPresent].name
}
fun Boolean.toInt() = if (this) 1 else 0
private fun Boolean.toInt() = if (this) 1 else 0
private fun getPasswordStrengthColor(password: String): Color {
val strength = getPasswordStrength(password)
@ -469,11 +495,11 @@ private fun getPasswordStrengthColor(password: String): Color {
@Preview
@Composable
fun SignupScreenPreview() {
private fun SignupScreenPreview() {
SignupScreenContent(
showProgressState = false,
data = SignupData(),
stateList = listOf(),
onCompleteRegistration = { }
onCompleteRegistration = { },
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.signup
import androidx.compose.runtime.getValue
@ -33,7 +42,6 @@ import org.mifospay.core.data.repository.local.LocalAssetRepository
import org.mifospay.core.datastore.PreferencesHelper
import javax.inject.Inject
@HiltViewModel
class SignupViewModel @Inject constructor(
localAssetRepository: LocalAssetRepository,
@ -46,7 +54,7 @@ class SignupViewModel @Inject constructor(
private val authenticateUserUseCase: AuthenticateUser,
private val fetchClientDataUseCase: FetchClientData,
private val deleteUserUseCase: DeleteUser,
private val fetchUserDetailsUseCase: FetchUserDetails
private val fetchUserDetailsUseCase: FetchUserDetails,
) : ViewModel() {
var showProgress by mutableStateOf(false)
@ -62,7 +70,7 @@ class SignupViewModel @Inject constructor(
email: String?,
firstName: String?,
lastName: String?,
businessName: String?
businessName: String?,
) {
signupData = signupData.copy(
mifosSavingsProductId = savingProductId,
@ -71,19 +79,19 @@ class SignupViewModel @Inject constructor(
email = email,
firstName = firstName!!,
lastName = lastName!!,
businessName = businessName
businessName = businessName,
)
}
val states: StateFlow<List<State>> = combine(
localAssetRepository.getCountries(),
localAssetRepository.getStateList(),
::Pair
::Pair,
)
.map {
val countries = it.first
signupData = signupData.copy(
countryId = countries.find { it.name == signupData.countryName }?.id ?: ""
countryId = countries.find { it.name == signupData.countryName }?.id ?: "",
)
it.second.filter { it.countryId == signupData.countryId }
}
@ -101,7 +109,8 @@ class SignupViewModel @Inject constructor(
// 2. Create user
// 3. Create Client
// 4. Update User and connect client with user
useCaseHandler.execute(searchClientUseCase,
useCaseHandler.execute(
searchClientUseCase,
SearchClient.RequestValues("${signupData.userName}@mifos"),
object : UseCase.UseCaseCallback<SearchClient.ResponseValue> {
override fun onSuccess(response: SearchClient.ResponseValue) {
@ -111,15 +120,21 @@ class SignupViewModel @Inject constructor(
override fun onError(message: String) {
createUser(showToastMessage)
}
})
},
)
}
private fun createUser(showToastMessage: (String) -> Unit) {
val newUser = NewUser(
signupData.userName, signupData.firstName, signupData.lastName,
signupData.email, signupData.password
signupData.userName,
signupData.firstName,
signupData.lastName,
signupData.email,
signupData.password,
)
useCaseHandler.execute(createUserUseCase, CreateUser.RequestValues(newUser),
useCaseHandler.execute(
createUserUseCase,
CreateUser.RequestValues(newUser),
object : UseCase.UseCaseCallback<CreateUser.ResponseValue> {
override fun onSuccess(response: CreateUser.ResponseValue) {
createClient(response.userId, showToastMessage)
@ -129,16 +144,18 @@ class SignupViewModel @Inject constructor(
DebugUtil.log(message)
showToastMessage(message)
}
})
},
)
}
private fun createClient(userId: Int, showToastMessage: (String) -> Unit) {
val newClient = com.mifospay.core.model.domain.client.NewClient(
signupData.businessName, signupData.userName, signupData.addressLine1,
signupData.addressLine2, signupData.city, signupData.pinCode, signupData.stateId,
signupData.countryId, signupData.mobileNumber, signupData.mifosSavingsProductId
signupData.countryId, signupData.mobileNumber, signupData.mifosSavingsProductId,
)
useCaseHandler.execute(createClientUseCase,
useCaseHandler.execute(
createClientUseCase,
CreateClient.RequestValues(newClient),
object : UseCase.UseCaseCallback<CreateClient.ResponseValue> {
override fun onSuccess(response: CreateClient.ResponseValue) {
@ -154,15 +171,17 @@ class SignupViewModel @Inject constructor(
showToastMessage(message)
deleteUser(userId)
}
})
},
)
}
private fun updateClient(
clients: ArrayList<Int>,
userId: Int,
showToastMessage: (String) -> Unit
showToastMessage: (String) -> Unit,
) {
useCaseHandler.execute(updateUserUseCase,
useCaseHandler.execute(
updateUserUseCase,
UpdateUser.RequestValues(UpdateUserEntityClients(clients), userId),
object : UseCase.UseCaseCallback<UpdateUser.ResponseValue?> {
override fun onSuccess(response: UpdateUser.ResponseValue?) {
@ -174,17 +193,20 @@ class SignupViewModel @Inject constructor(
DebugUtil.log(message)
showToastMessage("update client error")
}
})
},
)
}
private fun loginUser(
username: String?,
password: String?,
showToastMessage: (String) -> Unit
showToastMessage: (String) -> Unit,
) {
authenticateUserUseCase.walletRequestValues = AuthenticateUser.RequestValues(username!!, password!!)
val requestValue = authenticateUserUseCase.walletRequestValues
useCaseHandler.execute(authenticateUserUseCase, requestValue,
useCaseHandler.execute(
authenticateUserUseCase,
requestValue,
object : UseCase.UseCaseCallback<AuthenticateUser.ResponseValue> {
override fun onSuccess(response: AuthenticateUser.ResponseValue) {
createAuthenticatedService(response.user)
@ -195,11 +217,13 @@ class SignupViewModel @Inject constructor(
override fun onError(message: String) {
showToastMessage("Login Failed")
}
})
},
)
}
private fun fetchUserDetails(user: User) {
useCaseHandler.execute(fetchUserDetailsUseCase,
useCaseHandler.execute(
fetchUserDetailsUseCase,
FetchUserDetails.RequestValues(user.userId),
object : UseCase.UseCaseCallback<FetchUserDetails.ResponseValue> {
override fun onSuccess(response: FetchUserDetails.ResponseValue) {
@ -209,11 +233,14 @@ class SignupViewModel @Inject constructor(
override fun onError(message: String) {
DebugUtil.log(message)
}
})
},
)
}
private fun fetchClientData(showToastMessage: (String) -> Unit) {
useCaseHandler.execute(fetchClientDataUseCase, null,
useCaseHandler.execute(
fetchClientDataUseCase,
null,
object : UseCase.UseCaseCallback<FetchClientData.ResponseValue> {
override fun onSuccess(response: FetchClientData.ResponseValue) {
saveClientDetails(response.clientDetails)
@ -225,7 +252,8 @@ class SignupViewModel @Inject constructor(
override fun onError(message: String) {
showToastMessage("Fetch Client Error")
}
})
},
)
}
private fun createAuthenticatedService(user: User) {
@ -235,7 +263,7 @@ class SignupViewModel @Inject constructor(
private fun saveUserDetails(
user: User,
userWithRole: UserWithRole
userWithRole: UserWithRole,
) {
val userName = user.username
val userID = user.userId
@ -251,11 +279,14 @@ class SignupViewModel @Inject constructor(
}
private fun deleteUser(userId: Int) {
useCaseHandler.execute(deleteUserUseCase, DeleteUser.RequestValues(userId),
useCaseHandler.execute(
deleteUserUseCase,
DeleteUser.RequestValues(userId),
object : UseCase.UseCaseCallback<DeleteUser.ResponseValue> {
override fun onSuccess(response: DeleteUser.ResponseValue) {}
override fun onError(message: String) {}
})
},
)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
@file:Suppress("MaxLineLength")
package org.mifospay.feature.auth.socialSignup
@ -61,18 +70,22 @@ const val TAG = "Social Login"
// Followed this https://medium.com/@nirmale.ashwin9696/a-comprehensive-guide-to-google-sign-in-integration-with-credential-manager-in-android-apps-05286f8f5848
// Keeping until we fix sign up
@Composable
fun SocialSignupMethodContentScreen(
onDismissSignUp: () -> Unit
internal fun SocialSignupMethodContentScreen(
modifier: Modifier = Modifier,
onDismissSignUp: () -> Unit = {},
) {
SocialSignupMethodScreen(onDismissSignUp = onDismissSignUp)
SocialSignupMethodScreen(
modifier = modifier,
onDismissSignUp = onDismissSignUp,
)
}
@Composable
@Suppress("NestedBlockDepth")
fun SocialSignupMethodScreen(
onDismissSignUp: () -> Unit
private fun SocialSignupMethodScreen(
modifier: Modifier = Modifier,
onDismissSignUp: () -> Unit = {},
) {
val context = LocalContext.current
var mifosSavingProductId by remember { mutableIntStateOf(0) }
var showProgress by remember { mutableStateOf(false) }
@ -91,7 +104,6 @@ fun SocialSignupMethodScreen(
.addCredentialOption(googleIdOption)
.build()
fun signUpWithMifos() {
googleIdTokenCredential.signUpWithMifos(context, mifosSavingProductId) {
coroutineScope.launch {
@ -115,7 +127,7 @@ fun SocialSignupMethodScreen(
Toast.makeText(
context,
Constants.GOOGLE_SIGN_IN_FAILED,
Toast.LENGTH_SHORT
Toast.LENGTH_SHORT,
).show()
}
} catch (e: GoogleIdTokenParsingException) {
@ -134,7 +146,6 @@ fun SocialSignupMethodScreen(
}
}
fun signUpCredentialManager() {
coroutineScope.launch {
try {
@ -161,6 +172,7 @@ fun SocialSignupMethodScreen(
}
MifosBottomSheet(
modifier = modifier,
content = {
SignupMethodContentScreen(
showProgress = showProgress,
@ -171,37 +183,37 @@ fun SocialSignupMethodScreen(
onSignupAsCustomer = { checkedGoogleAccount ->
mifosSavingProductId = MIFOS_CONSUMER_SAVINGS_PRODUCT_ID
signUp(checkedGoogleAccount)
}
},
)
},
onDismiss = {
onDismissSignUp.invoke()
}
},
)
}
@Composable
@Suppress("LongMethod")
fun SignupMethodContentScreen(
private fun SignupMethodContentScreen(
showProgress: Boolean,
onSignUpAsMerchant: (Boolean) -> Unit,
onSignupAsCustomer: (Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
var checkedGoogleAccountState by remember { mutableStateOf(true) }
Box(
modifier = Modifier,
modifier = modifier,
) {
Column(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface),
horizontalAlignment = Alignment.CenterHorizontally
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.padding(top = 16.dp),
text = stringResource(id = R.string.feature_auth_create_an_account)
text = stringResource(id = R.string.feature_auth_create_an_account),
)
OutlinedButton(
modifier = Modifier.padding(top = 48.dp),
@ -210,36 +222,36 @@ fun SignupMethodContentScreen(
},
border = BorderStroke(1.dp, Color.LightGray),
shape = RoundedCornerShape(4.dp),
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary)
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary),
) {
Text(
text = stringResource(id = R.string.feature_auth_sign_up_as_merchant).uppercase(),
style = MaterialTheme.typography.labelMedium
style = MaterialTheme.typography.labelMedium,
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp),
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
HorizontalDivider(
modifier = Modifier
.padding(start = 24.dp, end = 8.dp)
.weight(.4f),
thickness = 1.dp
thickness = 1.dp,
)
Text(
modifier = Modifier
.wrapContentWidth()
.weight(.1f),
text = stringResource(id = R.string.feature_auth_or)
text = stringResource(id = R.string.feature_auth_or),
)
HorizontalDivider(
modifier = Modifier
.padding(start = 8.dp, end = 24.dp)
.weight(.4f),
thickness = 1.dp
thickness = 1.dp,
)
}
OutlinedButton(
@ -249,11 +261,11 @@ fun SignupMethodContentScreen(
},
border = BorderStroke(1.dp, Color.LightGray),
shape = RoundedCornerShape(4.dp),
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary)
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.primary),
) {
Text(
text = stringResource(id = R.string.feature_auth_sign_up_as_customer).uppercase(),
style = MaterialTheme.typography.labelMedium
style = MaterialTheme.typography.labelMedium,
)
}
Row(
@ -262,18 +274,18 @@ fun SignupMethodContentScreen(
.clickable {
checkedGoogleAccountState = !checkedGoogleAccountState
},
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = checkedGoogleAccountState,
onCheckedChange = {
checkedGoogleAccountState = !checkedGoogleAccountState
},
colors = CheckboxDefaults.colors(MaterialTheme.colorScheme.primary)
colors = CheckboxDefaults.colors(MaterialTheme.colorScheme.primary),
)
Text(
text = stringResource(id = R.string.feature_auth_ease_my_sign_up_using_google_account),
style = MaterialTheme.typography.labelSmall
style = MaterialTheme.typography.labelSmall,
)
}
HorizontalDivider(thickness = 48.dp, color = Color.Transparent)
@ -283,7 +295,7 @@ fun SignupMethodContentScreen(
modifier = Modifier
.fillMaxWidth()
.padding(top = 140.dp),
contentAlignment = Alignment.Center
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp),
@ -296,20 +308,20 @@ fun SignupMethodContentScreen(
}
@Suppress("UnusedParameter")
fun GoogleIdTokenCredential?.signUpWithMifos(
private fun GoogleIdTokenCredential?.signUpWithMifos(
context: Context,
mifosSavingsProductId: Int,
signOutGoogleClient: () -> Unit
signOutGoogleClient: () -> Unit,
) {
val googleIdTokenCredential = this
//Todo:navigate to MobileVerificationScreen with googleIdTokenCredential.givenName,profilePictureUri,
// Todo:navigate to MobileVerificationScreen with googleIdTokenCredential.givenName,profilePictureUri,
// familyName,mifosSavingsProductId,displayName,data.getString("com.google.android.libraries.identity.googleid.BUNDLE_KEY_ID")
signOutGoogleClient.invoke()
}
@Preview
@Composable
fun SignupMethodContentScreenPreview() {
private fun SignupMethodContentScreenPreview() {
MaterialTheme {
SignupMethodContentScreen(true, {}, {})
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.auth.utils
import android.util.Patterns

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<string name="feature_auth_login">Login</string>
<string name="feature_auth_welcome_back">Welcome back!</string>

View File

@ -1,16 +0,0 @@
package org.mifospay.mobilewallet.mifospay.auth
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* 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

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest>
</manifest>

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.editpassword
import androidx.compose.foundation.layout.Column
@ -35,13 +44,15 @@ import org.mifospay.core.designsystem.component.MifosScaffold
import org.mifospay.core.designsystem.theme.MifosTheme
@Composable
fun EditPasswordScreen(
viewModel: EditPasswordViewModel = hiltViewModel(),
internal fun EditPasswordScreen(
onBackPress: () -> Unit,
onCancelChanges: () -> Unit
onCancelChanges: () -> Unit,
modifier: Modifier = Modifier,
viewModel: EditPasswordViewModel = hiltViewModel(),
) {
val editPasswordUiState by viewModel.editPasswordUiState.collectAsStateWithLifecycle()
EditPasswordScreen(
modifier = modifier,
editPasswordUiState = editPasswordUiState,
onCancelChanges = onCancelChanges,
onBackPress = onBackPress,
@ -49,18 +60,19 @@ fun EditPasswordScreen(
viewModel.updatePassword(
currentPassword = currentPass,
newPassword = newPass,
newPasswordRepeat = confirmPass
newPasswordRepeat = confirmPass,
)
}
},
)
}
@Composable
fun EditPasswordScreen(
private fun EditPasswordScreen(
editPasswordUiState: EditPasswordUiState,
onCancelChanges: () -> Unit,
onBackPress: () -> Unit,
onSave: (currentPass: String, newPass: String, confirmPass: String) -> Unit
onSave: (currentPass: String, newPass: String, confirmPass: String) -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
var currentPassword by rememberSaveable { mutableStateOf("") }
@ -88,26 +100,25 @@ fun EditPasswordScreen(
EditPasswordUiState.Success -> {
coroutineScope.launch {
currentSnackbarHostState.showSnackbar(
context.getString(R.string.feature_editpassword_password_changed_successfully)
context.getString(R.string.feature_editpassword_password_changed_successfully),
)
}
}
}
}
MifosScaffold(
modifier = modifier,
topBarTitle = R.string.feature_editpassword_change_password,
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
backPress = onBackPress,
scaffoldContent = { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(paddingValues),
) {
MfPasswordTextField(
modifier = Modifier
@ -119,7 +130,6 @@ fun EditPasswordScreen(
isPasswordVisible = isConfirmPasswordVisible,
onTogglePasswordVisibility = {
isConfirmPasswordVisible = !isConfirmPasswordVisible
},
onPasswordChange = { currentPassword = it },
)
@ -131,12 +141,16 @@ fun EditPasswordScreen(
password = newPassword,
label = stringResource(id = R.string.feature_editpassword_new_password),
isError = newPassword.isNotEmpty() && newPassword.length < 6,
errorMessage = if (newPassword.isNotEmpty() && newPassword.length < 6) stringResource(
id = R.string.feature_editpassword_password_length_error
) else null,
errorMessage = if (newPassword.isNotEmpty() && newPassword.length < 6) {
stringResource(
id = R.string.feature_editpassword_password_length_error,
)
} else {
null
},
isPasswordVisible = isNewPasswordVisible,
onTogglePasswordVisibility = { isNewPasswordVisible = !isNewPasswordVisible },
onPasswordChange = { newPassword = it }
onPasswordChange = { newPassword = it },
)
MfPasswordTextField(
modifier = Modifier
@ -147,21 +161,25 @@ fun EditPasswordScreen(
isError = newPassword != confirmNewPassword && confirmNewPassword.isNotEmpty(),
errorMessage = if (newPassword !=
confirmNewPassword && confirmNewPassword.isNotEmpty()
) stringResource(
id = R.string.feature_editpassword_password_mismatch_error
) else null,
) {
stringResource(
id = R.string.feature_editpassword_password_mismatch_error,
)
} else {
null
},
isPasswordVisible = isConfirmNewPasswordVisible,
onTogglePasswordVisibility = {
isConfirmNewPasswordVisible = !isConfirmNewPasswordVisible
},
onPasswordChange = { confirmNewPassword = it }
onPasswordChange = { confirmNewPassword = it },
)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp, start = 16.dp, end = 16.dp),
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
MifosButton(
onClick = { onCancelChanges.invoke() },
@ -169,7 +187,7 @@ fun EditPasswordScreen(
.weight(1f)
.padding(8.dp),
contentPadding = PaddingValues(16.dp),
content = { Text(text = stringResource(id = R.string.feature_editpassword_cancel)) }
content = { Text(text = stringResource(id = R.string.feature_editpassword_cancel)) },
)
MifosButton(
modifier = Modifier
@ -179,11 +197,11 @@ fun EditPasswordScreen(
onSave.invoke(currentPassword, newPassword, confirmNewPassword)
},
contentPadding = PaddingValues(16.dp),
content = { Text(text = stringResource(id = R.string.feature_editpassword_save)) }
content = { Text(text = stringResource(id = R.string.feature_editpassword_save)) },
)
}
}
}
},
)
}
@ -192,14 +210,14 @@ class EditPasswordUiStateProvider : PreviewParameterProvider<EditPasswordUiState
get() = sequenceOf(
EditPasswordUiState.Loading,
EditPasswordUiState.Success,
EditPasswordUiState.Error("Some Error Occurred")
EditPasswordUiState.Error("Some Error Occurred"),
)
}
@Preview
@Composable
private fun EditPasswordScreenPreview(
@PreviewParameter(EditPasswordUiStateProvider::class) editPasswordUiState: EditPasswordUiState
@PreviewParameter(EditPasswordUiStateProvider::class) editPasswordUiState: EditPasswordUiState,
) {
MifosTheme {
EditPasswordScreen(editPasswordUiState = editPasswordUiState, {}, {}, { _, _, _ -> })

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.editpassword
import androidx.lifecycle.ViewModel
@ -19,7 +28,7 @@ class EditPasswordViewModel @Inject constructor(
private val mUseCaseHandler: UseCaseHandler,
private val mPreferencesHelper: PreferencesHelper,
private val authenticateUserUseCase: AuthenticateUser,
private val updateUserUseCase: UpdateUser
private val updateUserUseCase: UpdateUser,
) : ViewModel() {
private val _editPasswordUiState =
@ -29,11 +38,11 @@ class EditPasswordViewModel @Inject constructor(
fun updatePassword(
currentPassword: String?,
newPassword: String?,
newPasswordRepeat: String?
newPasswordRepeat: String?,
) {
_editPasswordUiState.value = EditPasswordUiState.Loading
if (isNotEmpty(currentPassword) && isNotEmpty(newPassword)
&& isNotEmpty(newPasswordRepeat)
if (isNotEmpty(currentPassword) && isNotEmpty(newPassword) &&
isNotEmpty(newPasswordRepeat)
) {
when {
currentPassword == newPassword -> {
@ -45,7 +54,7 @@ class EditPasswordViewModel @Inject constructor(
newPasswordRepeat?.let { it1 ->
isNewPasswordValid(
it,
it1
it1,
)
}
} == true -> {
@ -75,19 +84,21 @@ class EditPasswordViewModel @Inject constructor(
private fun updatePassword(currentPassword: String, newPassword: String) {
// authenticate and then update
mUseCaseHandler.execute(authenticateUserUseCase,
mUseCaseHandler.execute(
authenticateUserUseCase,
AuthenticateUser.RequestValues(
mPreferencesHelper.username,
currentPassword
currentPassword,
),
object : UseCase.UseCaseCallback<AuthenticateUser.ResponseValue> {
override fun onSuccess(response: AuthenticateUser.ResponseValue) {
mUseCaseHandler.execute(updateUserUseCase,
mUseCaseHandler.execute(
updateUserUseCase,
UpdateUser.RequestValues(
UpdateUserEntityPassword(
newPassword
newPassword,
),
mPreferencesHelper.userId.toInt()
mPreferencesHelper.userId.toInt(),
),
object : UseCase.UseCaseCallback<UpdateUser.ResponseValue?> {
override fun onSuccess(response: UpdateUser.ResponseValue?) {
@ -97,13 +108,15 @@ class EditPasswordViewModel @Inject constructor(
override fun onError(message: String) {
_editPasswordUiState.value = EditPasswordUiState.Error(message)
}
})
},
)
}
override fun onError(message: String) {
_editPasswordUiState.value = EditPasswordUiState.Error("Wrong Password")
}
})
},
)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.editpassword.navigation
import androidx.navigation.NavController
@ -9,12 +18,12 @@ const val EDIT_PASSWORD_ROUTE = "edit_password_route"
fun NavGraphBuilder.editPasswordScreen(
onBackPress: () -> Unit,
onCancelChanges: () -> Unit
onCancelChanges: () -> Unit,
) {
composable(route = EDIT_PASSWORD_ROUTE) {
EditPasswordScreen(
onBackPress = onBackPress,
onCancelChanges = onCancelChanges
onCancelChanges = onCancelChanges,
)
}
}

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<string name="feature_editpassword_password_changed_successfully">Password changed successfull</string>
<string name="feature_editpassword_change_password">Change Password</string>

View File

@ -1,16 +0,0 @@
package org.mifospay.feature.editpassword
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* 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

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)

View File

@ -1,2 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest></manifest>

View File

@ -1,6 +1,15 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.faq
data class FAQ(
internal data class FAQ(
var question: Int,
var answer: Int? = null,
)

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.faq
import androidx.lifecycle.ViewModel
@ -5,7 +14,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class FAQViewModel @Inject constructor() : ViewModel() {
internal class FAQViewModel @Inject constructor() : ViewModel() {
/**
* Retrieves a list of Frequently Asked Questions (FAQs).
@ -22,7 +31,7 @@ class FAQViewModel @Inject constructor() : ViewModel() {
FAQ(R.string.feature_faq_question1, R.string.feature_faq_answer1),
FAQ(R.string.feature_faq_question2, R.string.feature_faq_answer2),
FAQ(R.string.feature_faq_question3, R.string.feature_faq_answer3),
FAQ(R.string.feature_faq_question4, R.string.feature_faq_answer4)
FAQ(R.string.feature_faq_question4, R.string.feature_faq_answer4),
)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.faq
import androidx.compose.foundation.layout.Column
@ -14,31 +23,41 @@ import org.mifospay.core.designsystem.component.MifosTopBar
import org.mifospay.core.ui.FaqItemScreen
@Composable
fun FaqScreenRoute(
internal fun FaqScreenRoute(
navigateBack: () -> Unit,
faqViewModel: FAQViewModel = hiltViewModel()
modifier: Modifier = Modifier,
faqViewModel: FAQViewModel = hiltViewModel(),
) {
FaqScreen(navigateBack = { navigateBack.invoke() }, faqViewModel.getFAQ())
FaqScreen(
modifier = modifier,
navigateBack = navigateBack,
faqList = faqViewModel.getFAQ(),
)
}
@Composable
fun FaqScreen(
private fun FaqScreen(
navigateBack: () -> Unit,
faqList: List<FAQ>
faqList: List<FAQ>,
modifier: Modifier = Modifier,
) {
Column(modifier = Modifier.fillMaxSize()) {
Column(
modifier = modifier
.fillMaxSize(),
) {
MifosTopBar(
topBarTitle = R.string.feature_faq_frequently_asked_questions,
backPress = { navigateBack.invoke() })
backPress = { navigateBack.invoke() },
)
LazyColumn(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.fillMaxWidth(),
) {
itemsIndexed(items = faqList) { _, faqItem ->
FaqItemScreen(
question = stringResource(id = faqItem.question),
answer = faqItem.answer?.let { stringResource(id = it) }
answer = faqItem.answer?.let { stringResource(id = it) },
)
}
}
@ -47,13 +66,14 @@ fun FaqScreen(
@Preview(showSystemUi = true)
@Composable
fun FaqScreenPreview() {
private fun FaqScreenPreview() {
FaqScreen(
{}, listOf(
{},
listOf(
FAQ(R.string.feature_faq_question1, R.string.feature_faq_answer1),
FAQ(R.string.feature_faq_question2, R.string.feature_faq_answer2),
FAQ(R.string.feature_faq_question3, R.string.feature_faq_answer3),
FAQ(R.string.feature_faq_question4, R.string.feature_faq_answer4)
)
FAQ(R.string.feature_faq_question4, R.string.feature_faq_answer4),
),
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.faq.navigation
import androidx.navigation.NavController
@ -13,11 +22,11 @@ fun NavController.navigateToFAQ(navOptions: NavOptions? = null) {
}
fun NavGraphBuilder.faqScreen(
navigateBack: () -> Unit
navigateBack: () -> Unit,
) {
composable(route = FAQ_ROUTE) {
FaqScreenRoute(
navigateBack = navigateBack
navigateBack = navigateBack,
)
}
}

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<string name="feature_faq_question1">How can I add an Account?</string>
<string name="feature_faq_question2">How can I access my Payment History?</string>

View File

@ -1,16 +0,0 @@
package com.example.faq
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* 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

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)
@ -8,10 +17,5 @@ android {
}
dependencies {
implementation(projects.core.data)
implementation(projects.feature.kyc)
implementation(projects.feature.savedcards)
implementation(projects.feature.accounts)
implementation(projects.feature.merchants)
implementation(libs.accompanist.pager)
}

View File

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

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.finance
import androidx.compose.foundation.layout.Column
@ -6,50 +15,21 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.google.accompanist.pager.rememberPagerState
import com.mifospay.core.model.domain.BankAccountDetails
import org.mifospay.core.ui.MifosScrollableTabRow
import org.mifospay.core.ui.utility.TabContent
import org.mifospay.feature.bank.accounts.AccountsScreen
import org.mifospay.feature.kyc.KYCScreen
import org.mifospay.feature.merchants.ui.MerchantScreen
import org.mifospay.feature.savedcards.CardsScreen
@Suppress("UnusedParameter")
@Composable
fun FinanceRoute(
onAddBtn: () -> Unit,
onLevel1Clicked: () -> Unit,
onLevel2Clicked: () -> Unit,
onLevel3Clicked: () -> Unit,
navigateToBankAccountDetailScreen: (BankAccountDetails, Int) -> Unit,
navigateToLinkBankAccountScreen: () -> Unit
internal fun FinanceRoute(
tabContents: List<TabContent>,
modifier: Modifier = Modifier,
) {
val pagerState = rememberPagerState(initialPage = 0)
val tabContents = listOf(
TabContent(FinanceScreenContents.ACCOUNTS.name) {
AccountsScreen(
navigateToBankAccountDetailScreen = navigateToBankAccountDetailScreen,
navigateToLinkBankAccountScreen = navigateToLinkBankAccountScreen
Column(modifier = modifier.fillMaxSize()) {
MifosScrollableTabRow(
tabContents = tabContents,
pagerState = pagerState,
)
},
TabContent(FinanceScreenContents.CARDS.name) {
CardsScreen(onEditCard = {})
},
TabContent(FinanceScreenContents.MERCHANTS.name) {
MerchantScreen()
},
TabContent(FinanceScreenContents.KYC.name) {
KYCScreen(
onLevel1Clicked = onLevel1Clicked,
onLevel2Clicked = onLevel2Clicked,
onLevel3Clicked = onLevel3Clicked
)
}
)
Column(modifier = Modifier.fillMaxSize()) {
MifosScrollableTabRow(tabContents = tabContents, pagerState = pagerState)
}
}
@ -57,18 +37,13 @@ enum class FinanceScreenContents {
ACCOUNTS,
CARDS,
MERCHANTS,
KYC
KYC,
}
@Preview(showBackground = true)
@Composable
private fun FinanceScreenPreview() {
FinanceRoute(
onAddBtn = {},
onLevel1Clicked = {},
onLevel2Clicked = {},
onLevel3Clicked = {},
navigateToBankAccountDetailScreen = { _, _ -> },
navigateToLinkBankAccountScreen = {}
tabContents = emptyList(),
)
}

View File

@ -1,10 +1,19 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.finance.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.mifospay.core.model.domain.BankAccountDetails
import org.mifospay.core.ui.utility.TabContent
import org.mifospay.feature.finance.FinanceRoute
const val FINANCE_ROUTE = "finance_route"
@ -12,21 +21,9 @@ const val FINANCE_ROUTE = "finance_route"
fun NavController.navigateToFinance(navOptions: NavOptions) = navigate(FINANCE_ROUTE, navOptions)
fun NavGraphBuilder.financeScreen(
onAddBtn: () -> Unit,
onLevel1Clicked: () -> Unit,
onLevel2Clicked: () -> Unit,
onLevel3Clicked: () -> Unit,
navigateToBankAccountDetailScreen: (BankAccountDetails,Int) -> Unit,
navigateToLinkBankAccountScreen: () -> Unit
tabContents: List<TabContent>,
) {
composable(route = FINANCE_ROUTE) {
FinanceRoute(
onAddBtn = onAddBtn,
onLevel1Clicked = onLevel1Clicked,
onLevel2Clicked = onLevel2Clicked,
onLevel3Clicked = onLevel3Clicked,
navigateToBankAccountDetailScreen = navigateToBankAccountDetailScreen,
navigateToLinkBankAccountScreen = navigateToLinkBankAccountScreen
)
FinanceRoute(tabContents = tabContents)
}
}

View File

@ -1,16 +0,0 @@
package org.mifospay.feature.finance
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* 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

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)
@ -8,6 +17,5 @@ android {
}
dependencies {
implementation(projects.feature.receipt)
implementation(projects.core.data)
}

View File

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

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,12 +0,0 @@
package org.mifospay.feature
import com.mifospay.core.model.domain.Transaction
/**
* Created by naman on 17/8/17.
*/
interface HistoryContract {
interface TransactionsHistoryAsync {
fun onTransactionsFetchCompleted(transactions: List<Transaction>?)
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.history
import android.widget.Toast
@ -46,9 +55,10 @@ import org.mifospay.feature.transaction.detail.TransactionDetailScreen
@Composable
fun HistoryScreen(
viewModel: HistoryViewModel = hiltViewModel(),
viewReceipt: (String) -> Unit,
accountClicked: (String, ArrayList<Transaction>) -> Unit,
modifier: Modifier = Modifier,
viewModel: HistoryViewModel = hiltViewModel(),
) {
val historyUiState by viewModel.historyUiState.collectAsStateWithLifecycle()
@ -56,14 +66,16 @@ fun HistoryScreen(
historyUiState = historyUiState,
viewReceipt = viewReceipt,
accountClicked = accountClicked,
modifier = modifier,
)
}
@Composable
fun HistoryScreen(
private fun HistoryScreen(
historyUiState: HistoryUiState,
viewReceipt: (String) -> Unit,
accountClicked: (String, ArrayList<Transaction>) -> Unit,
modifier: Modifier = Modifier,
) {
var selectedChip by remember { mutableStateOf(TransactionType.OTHER) }
var filteredTransactions by remember { mutableStateOf(emptyList<Transaction>()) }
@ -77,7 +89,7 @@ fun HistoryScreen(
title = stringResource(id = R.string.feature_history_error_oops),
subTitle = stringResource(id = R.string.feature_history_empty_no_transaction_history_title),
iconTint = MaterialTheme.colorScheme.primary,
iconImageVector = Icons.Rounded.Info
iconImageVector = Icons.Rounded.Info,
)
}
@ -87,7 +99,7 @@ fun HistoryScreen(
title = stringResource(id = R.string.feature_history_error_oops),
subTitle = stringResource(id = R.string.feature_history_unexpected_error_subtitle),
iconTint = MaterialTheme.colorScheme.primary,
iconImageVector = Icons.Rounded.Info
iconImageVector = Icons.Rounded.Info,
)
}
@ -104,22 +116,22 @@ fun HistoryScreen(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween
horizontalArrangement = Arrangement.SpaceBetween,
) {
Chip(
selected = selectedChip == TransactionType.OTHER,
onClick = { selectedChip = TransactionType.OTHER },
label = stringResource(R.string.feature_history_all)
label = stringResource(R.string.feature_history_all),
)
Chip(
selected = selectedChip == TransactionType.CREDIT,
onClick = { selectedChip = TransactionType.CREDIT },
label = stringResource(R.string.feature_history_credits)
label = stringResource(R.string.feature_history_credits),
)
Chip(
selected = selectedChip == TransactionType.DEBIT,
onClick = { selectedChip = TransactionType.DEBIT },
label = stringResource(R.string.feature_history_debits)
label = stringResource(R.string.feature_history_debits),
)
}
Spacer(modifier = Modifier.height(24.dp))
@ -130,72 +142,74 @@ fun HistoryScreen(
modifier = Modifier
.padding(start = 24.dp, end = 24.dp)
.clickable { transactionDetailState = it },
transaction = it
transaction = it,
)
Spacer(modifier = Modifier.height(16.dp))
}
}
}
}
}
HistoryUiState.Loading -> {
MifosLoadingWheel(
modifier = Modifier.fillMaxWidth(),
contentDesc = stringResource(R.string.feature_history_loading)
contentDesc = stringResource(R.string.feature_history_loading),
)
}
}
if (transactionDetailState != null) {
MifosBottomSheet(
modifier = modifier,
content = {
TransactionDetailScreen(
transaction = transactionDetailState!!,
viewReceipt = { transactionDetailState?.transactionId?.let { viewReceipt(it) } },
accountClicked = { accountClicked(it, ArrayList(transactionsList)) }
accountClicked = { accountClicked(it, ArrayList(transactionsList)) },
)
},
onDismiss = { transactionDetailState = null }
onDismiss = { transactionDetailState = null },
)
}
}
@Composable
fun Chip(
private fun Chip(
selected: Boolean,
onClick: () -> Unit,
label: String
label: String,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val backgroundColor = if (selected) MaterialTheme.colorScheme.primary else lightGrey
Button(
modifier = modifier,
onClick = {
onClick()
Toast.makeText(context, label, Toast.LENGTH_SHORT).show()
},
colors = ButtonDefaults.buttonColors(backgroundColor)
colors = ButtonDefaults.buttonColors(backgroundColor),
) {
Text(
modifier = Modifier.padding(top = 4.dp, bottom = 4.dp, start = 16.dp, end = 16.dp),
text = label,
color = if (selected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface
color = if (selected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface,
)
}
}
class HistoryPreviewProvider : PreviewParameterProvider<HistoryUiState> {
internal class HistoryPreviewProvider : PreviewParameterProvider<HistoryUiState> {
override val values: Sequence<HistoryUiState>
get() = sequenceOf(
HistoryUiState.Empty,
HistoryUiState.Loading,
HistoryUiState.Error("Error Screen"),
HistoryUiState.HistoryList(sampleHistoryList)
HistoryUiState.HistoryList(sampleHistoryList),
)
}
val sampleHistoryList = List(10) { index ->
internal val sampleHistoryList = List(10) { index ->
Transaction(
transactionId = "txn_123456789",
clientId = 1001L,
@ -206,14 +220,14 @@ val sampleHistoryList = List(10) { index ->
transactionType = TransactionType.CREDIT,
transferId = 3003L,
transferDetail = TransferDetail(),
receiptId = "receipt_123456789"
receiptId = "receipt_123456789",
)
}
@Preview(showBackground = true)
@Composable
fun HistoryScreenPreview(
@PreviewParameter(HistoryPreviewProvider::class) historyUiState: HistoryUiState
private fun HistoryScreenPreview(
@PreviewParameter(HistoryPreviewProvider::class) historyUiState: HistoryUiState,
) {
HistoryScreen(historyUiState = historyUiState, viewReceipt = {}, accountClicked = { _, _ -> })
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.history
import androidx.lifecycle.ViewModel
@ -17,7 +26,7 @@ class HistoryViewModel @Inject constructor(
private val mUseCaseHandler: UseCaseHandler,
private val mLocalRepository: LocalRepository,
private val mFetchAccountUseCase: FetchAccount,
private val fetchAccountTransactionsUseCase: FetchAccountTransactions
private val fetchAccountTransactionsUseCase: FetchAccountTransactions,
) : ViewModel() {
private val _historyUiState = MutableStateFlow<HistoryUiState>(HistoryUiState.Loading)
@ -25,7 +34,8 @@ class HistoryViewModel @Inject constructor(
private fun fetchTransactions() {
_historyUiState.value = HistoryUiState.Loading
mUseCaseHandler.execute(mFetchAccountUseCase,
mUseCaseHandler.execute(
mFetchAccountUseCase,
FetchAccount.RequestValues(mLocalRepository.clientDetails.clientId),
object : UseCase.UseCaseCallback<FetchAccount.ResponseValue> {
override fun onSuccess(response: FetchAccount.ResponseValue) {
@ -37,23 +47,28 @@ class HistoryViewModel @Inject constructor(
override fun onError(message: String) {
_historyUiState.value = HistoryUiState.Error(message)
}
})
},
)
}
fun fetchTransactionsHistory(accountId: Long) {
mUseCaseHandler.execute(fetchAccountTransactionsUseCase,
mUseCaseHandler.execute(
fetchAccountTransactionsUseCase,
FetchAccountTransactions.RequestValues(accountId),
object : UseCase.UseCaseCallback<FetchAccountTransactions.ResponseValue?> {
override fun onSuccess(response: FetchAccountTransactions.ResponseValue?) {
if (response?.transactions?.isNotEmpty() == true)
if (response?.transactions?.isNotEmpty() == true) {
_historyUiState.value = HistoryUiState.HistoryList(response.transactions)
else _historyUiState.value = HistoryUiState.Empty
} else {
_historyUiState.value = HistoryUiState.Empty
}
}
override fun onError(message: String) {
_historyUiState.value = HistoryUiState.Error(message)
}
})
},
)
}
init {

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.specific.transactions
import androidx.compose.foundation.clickable
@ -12,7 +21,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material3.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@ -32,6 +41,7 @@ import com.mifospay.core.model.domain.Transaction
import com.mifospay.core.model.domain.TransactionType
import com.mifospay.core.model.domain.client.Client
import com.mifospay.core.model.entity.accounts.savings.SavingAccount
import org.mifospay.common.Utils.toArrayList
import org.mifospay.core.designsystem.component.MfLoadingWheel
import org.mifospay.core.designsystem.component.MifosScaffold
import org.mifospay.core.designsystem.icon.MifosIcons
@ -43,36 +53,39 @@ import org.mifospay.core.ui.EmptyContentScreen
import org.mifospay.core.ui.ErrorScreenContent
import org.mifospay.feature.history.R
@Composable
fun SpecificTransactionsScreen(
internal fun SpecificTransactionsScreen(
accountNumber: String,
transactions: ArrayList<Transaction>,
viewModel: SpecificTransactionsViewModel = hiltViewModel(),
transactions: List<Transaction>,
backPress: () -> Unit,
transactionItemClicked: (String) -> Unit
transactionItemClicked: (String) -> Unit,
modifier: Modifier = Modifier,
viewModel: SpecificTransactionsViewModel = hiltViewModel(),
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(key1 = Unit) {
viewModel.setArguments(transactions, accountNumber)
viewModel.setArguments(transactions.toArrayList(), accountNumber)
viewModel.getSpecificTransactions()
}
SpecificTransactionsScreen(
uiState = uiState.value,
backPress = backPress,
transactionItemClicked = transactionItemClicked
transactionItemClicked = transactionItemClicked,
modifier = modifier,
)
}
@Composable
fun SpecificTransactionsScreen(
private fun SpecificTransactionsScreen(
uiState: SpecificTransactionsUiState,
backPress: () -> Unit,
transactionItemClicked: (String) -> Unit,
modifier: Modifier = Modifier,
) {
MifosScaffold(
modifier = modifier,
topBarTitle = R.string.feature_history_specific_transactions_history,
backPress = backPress,
scaffoldContent = { paddingValues ->
@ -89,7 +102,7 @@ fun SpecificTransactionsScreen(
SpecificTransactionsUiState.Loading -> {
MfLoadingWheel(
contentDesc = stringResource(R.string.feature_history_loading),
backgroundColor = MaterialTheme.colorScheme.surface
backgroundColor = MaterialTheme.colorScheme.surface,
)
}
@ -100,39 +113,42 @@ fun SpecificTransactionsScreen(
title = stringResource(id = R.string.feature_history_error_oops),
subTitle = stringResource(id = R.string.feature_history_no_transactions_found),
iconTint = MaterialTheme.colorScheme.onSurface,
iconImageVector = Icons.Rounded.Info
iconImageVector = Icons.Rounded.Info,
)
} else {
SpecificTransactionsContent(
transactionList = uiState.transactionsList,
transactionItemClicked = transactionItemClicked
transactionItemClicked = transactionItemClicked,
)
}
}
}
}
}
},
)
}
@Composable
fun SpecificTransactionsContent(
transactionList: ArrayList<Transaction>,
transactionItemClicked: (String) -> Unit
private fun SpecificTransactionsContent(
transactionList: List<Transaction>,
transactionItemClicked: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn {
itemsIndexed(items = transactionList) { index, transaction ->
LazyColumn(modifier = modifier) {
itemsIndexed(
items = transactionList,
) { index, transaction ->
SpecificTransactionItem(
transaction = transaction,
modifier = Modifier
.padding(12.dp)
.clickable {
transaction.transactionId?.let { transactionItemClicked(it) }
}
},
)
Spacer(modifier = Modifier.height(4.dp))
if (index != transactionList.lastIndex) {
Divider()
HorizontalDivider()
}
Spacer(modifier = Modifier.height(4.dp))
}
@ -142,43 +158,43 @@ fun SpecificTransactionsContent(
@Composable
fun SpecificTransactionItem(
transaction: Transaction,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
) {
Column(modifier = modifier.padding(horizontal = 12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
SpecificTransactionAccountInfo(
modifier = Modifier.weight(1f),
account = transaction.transferDetail.fromAccount,
client = transaction.transferDetail.fromClient
client = transaction.transferDetail.fromClient,
modifier = Modifier.weight(1f),
)
Icon(imageVector = MifosIcons.SendRightTilted, contentDescription = null)
SpecificTransactionAccountInfo(
modifier = Modifier.weight(1f),
account = transaction.transferDetail.toAccount,
client = transaction.transferDetail.toClient
client = transaction.transferDetail.toClient,
modifier = Modifier.weight(1f),
)
}
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = Modifier.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Column {
Text(
text = stringResource(id = R.string.feature_receipt_transaction_id) + transaction.transactionId,
text = stringResource(id = R.string.feature_history_transaction_id) + transaction.transactionId,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary
color = MaterialTheme.colorScheme.primary,
)
Text(
text = stringResource(id = R.string.feature_receipt_transaction_date) + transaction.date,
text = stringResource(id = R.string.feature_history_transaction_date) + transaction.date,
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = when (transaction.transactionType) {
TransactionType.DEBIT -> stringResource(id = R.string.feature_history_debits)
TransactionType.CREDIT -> stringResource(id = R.string.feature_history_credits)
TransactionType.OTHER -> stringResource(id = R.string.feature_receipt_other)
TransactionType.OTHER -> stringResource(id = R.string.feature_history_other)
},
style = MaterialTheme.typography.bodyLarge,
)
@ -198,11 +214,11 @@ fun SpecificTransactionItem(
}
@Composable
fun SpecificTransactionAccountInfo(
modifier: Modifier = Modifier,
internal fun SpecificTransactionAccountInfo(
account: SavingAccount,
client: Client,
accountClicked: (String) -> Unit = {}
modifier: Modifier = Modifier,
accountClicked: (String) -> Unit = {},
) {
Column(
modifier = modifier.clickable {
@ -222,7 +238,7 @@ fun SpecificTransactionAccountInfo(
}
}
class SpecificTransactionsUiStateProvider :
internal class SpecificTransactionsUiStateProvider :
PreviewParameterProvider<SpecificTransactionsUiState> {
override val values: Sequence<SpecificTransactionsUiState>
get() = sequenceOf(
@ -235,9 +251,9 @@ class SpecificTransactionsUiStateProvider :
@Preview(showSystemUi = true)
@Composable
fun ShowQrScreenPreview(
private fun ShowQrScreenPreview(
@PreviewParameter(SpecificTransactionsUiStateProvider::class)
uiState: SpecificTransactionsUiState
uiState: SpecificTransactionsUiState,
) {
MifosTheme {
SpecificTransactionsScreen(
@ -250,7 +266,7 @@ fun ShowQrScreenPreview(
@Preview
@Composable
fun SpecificTransactionItemPreview() {
private fun SpecificTransactionItemPreview() {
Surface {
SpecificTransactionItem(transaction = Transaction())
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.specific.transactions
import androidx.lifecycle.ViewModel
@ -14,7 +23,7 @@ import javax.inject.Inject
@HiltViewModel
class SpecificTransactionsViewModel @Inject constructor(
private val mUseCaseFactory: UseCaseFactory,
private var mTaskLooper: TaskLooper? = null
private var mTaskLooper: TaskLooper? = null,
) : ViewModel() {
private val _uiState: MutableStateFlow<SpecificTransactionsUiState> =
@ -42,13 +51,15 @@ class SpecificTransactionsViewModel @Inject constructor(
as UseCase<FetchAccountTransfer.RequestValues, FetchAccountTransfer.ResponseValue>,
values = FetchAccountTransfer.RequestValues(transferId),
taskData = TaskLooper.TaskData(
org.mifospay.common.Constants.TRANSFER_DETAILS, i
)
org.mifospay.common.Constants.TRANSFER_DETAILS,
i,
),
)
}
mTaskLooper!!.listen(object : TaskLooper.Listener {
override fun <R : UseCase.ResponseValue?> onTaskSuccess(
taskData: TaskLooper.TaskData, response: R
taskData: TaskLooper.TaskData,
response: R,
) {
when (taskData.taskName) {
org.mifospay.common.Constants.TRANSFER_DETAILS -> {

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
@file:Suppress("MaxLineLength")
package org.mifospay.feature.specific.transactions.navigation
@ -15,27 +24,32 @@ const val SPECIFIC_TRANSACTIONS_ROUTE = "specific_transactions_route"
fun NavGraphBuilder.specificTransactionsScreen(
onBackClick: () -> Unit,
onTransactionItemClicked: (String) -> Unit
onTransactionItemClicked: (String) -> Unit,
) {
composable(
route = "$SPECIFIC_TRANSACTIONS_ROUTE?${Constants.ACCOUNT_NUMBER}={accountNumber}&${Constants.TRANSACTIONS}={transactions}",
arguments = listOf(
navArgument(Constants.ACCOUNT_NUMBER) { type = NavType.StringType },
navArgument(Constants.TRANSACTIONS) { type = NavType.StringType }
)
navArgument(Constants.TRANSACTIONS) { type = NavType.StringType },
),
) { backStackEntry ->
val accountNumber = backStackEntry.arguments?.getString(Constants.ACCOUNT_NUMBER) ?: ""
val transactions = backStackEntry.arguments?.getParcelableArrayList<Transaction>(Constants.TRANSACTIONS) ?: arrayListOf()
val transactions =
backStackEntry.arguments?.getParcelableArrayList<Transaction>(Constants.TRANSACTIONS)
?: arrayListOf()
SpecificTransactionsScreen(
accountNumber = accountNumber,
transactions = transactions,
transactions = transactions.toList(),
backPress = onBackClick,
transactionItemClicked = onTransactionItemClicked
transactionItemClicked = onTransactionItemClicked,
)
}
}
fun NavController.navigateToSpecificTransactions(accountNumber: String, transactions: ArrayList<Transaction>) {
fun NavController.navigateToSpecificTransactions(
accountNumber: String,
transactions: ArrayList<Transaction>,
) {
this.navigate("$SPECIFIC_TRANSACTIONS_ROUTE?${Constants.ACCOUNT_NUMBER}=$accountNumber&${Constants.TRANSACTIONS}=$transactions")
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.transaction.detail
import androidx.compose.foundation.Image
@ -36,15 +45,14 @@ import org.mifospay.core.ui.ErrorScreenContent
import org.mifospay.feature.history.R
import org.mifospay.feature.specific.transactions.SpecificTransactionAccountInfo
@Composable
fun TransactionDetailScreen(
viewModel: TransactionDetailViewModel = hiltViewModel(),
internal fun TransactionDetailScreen(
transaction: Transaction,
viewReceipt: () -> Unit,
accountClicked: (String) -> Unit,
modifier: Modifier = Modifier,
viewModel: TransactionDetailViewModel = hiltViewModel(),
) {
val uiState = viewModel.transactionDetailUiState.collectAsStateWithLifecycle()
LaunchedEffect(key1 = transaction) {
@ -55,21 +63,23 @@ fun TransactionDetailScreen(
uiState = uiState.value,
transaction = transaction,
viewReceipt = viewReceipt,
accountClicked = accountClicked
accountClicked = accountClicked,
modifier = modifier,
)
}
@Composable
fun TransactionDetailScreen(
private fun TransactionDetailScreen(
uiState: TransactionDetailUiState,
transaction: Transaction,
viewReceipt: () -> Unit,
accountClicked: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = Modifier
modifier = modifier
.padding(20.dp)
.height(300.dp)
.height(300.dp),
) {
when (uiState) {
is TransactionDetailUiState.Error -> {
@ -78,7 +88,7 @@ fun TransactionDetailScreen(
is TransactionDetailUiState.Loading -> {
MfLoadingWheel(
backgroundColor = Color.Black.copy(alpha = 0.6f)
backgroundColor = Color.Black.copy(alpha = 0.6f),
)
}
@ -86,7 +96,7 @@ fun TransactionDetailScreen(
TransactionsDetailContent(
transaction = transaction,
viewReceipt = viewReceipt,
accountClicked = accountClicked
accountClicked = accountClicked,
)
}
}
@ -94,36 +104,36 @@ fun TransactionDetailScreen(
}
@Composable
fun TransactionsDetailContent(
modifier: Modifier = Modifier,
private fun TransactionsDetailContent(
transaction: Transaction,
viewReceipt: () -> Unit,
accountClicked: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier
.padding(horizontal = 12.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Column {
Text(
text = stringResource(id = R.string.feature_receipt_transaction_id) + transaction.transactionId,
text = stringResource(id = R.string.feature_history_transaction_id) + transaction.transactionId,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary
color = MaterialTheme.colorScheme.primary,
)
Text(
text = stringResource(id = R.string.feature_receipt_transaction_date) + transaction.date,
text = stringResource(id = R.string.feature_history_transaction_date) + transaction.date,
style = MaterialTheme.typography.bodyLarge,
)
if (transaction.receiptId != null) {
Text(
text = stringResource(id = R.string.feature_receipt_pan_id) + transaction.receiptId,
text = stringResource(id = R.string.feature_history_pan_id) + transaction.receiptId,
style = MaterialTheme.typography.bodyLarge,
)
}
@ -145,31 +155,32 @@ fun TransactionsDetailContent(
modifier = Modifier.weight(1f),
account = transaction.transferDetail.fromAccount,
client = transaction.transferDetail.fromClient,
accountClicked = accountClicked
accountClicked = accountClicked,
)
Image(
painter = painterResource(id = R.drawable.feature_history_ic_send),
contentDescription = null,
)
Image(painter = painterResource(id = R.drawable.feature_history_ic_send), contentDescription = null)
SpecificTransactionAccountInfo(
modifier = Modifier.weight(1f),
account = transaction.transferDetail.toAccount,
client = transaction.transferDetail.toClient,
accountClicked = accountClicked
accountClicked = accountClicked,
)
}
HorizontalDivider(modifier = Modifier.padding(vertical = 12.dp))
Text(
text = stringResource(id = R.string.feature_receipt_view_Receipt),
text = stringResource(id = R.string.feature_history_view_Receipt),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
textAlign = TextAlign.Center,
modifier = Modifier.clickable { viewReceipt() }
modifier = Modifier.clickable { viewReceipt() },
)
}
}
class TransactionDetailUiStateProvider :
PreviewParameterProvider<TransactionDetailUiState> {
override val values: Sequence<TransactionDetailUiState>
@ -182,7 +193,10 @@ class TransactionDetailUiStateProvider :
@Preview(showSystemUi = true)
@Composable
fun ShowQrScreenPreview(@PreviewParameter(TransactionDetailUiStateProvider::class) uiState: TransactionDetailUiState) {
private fun ShowQrScreenPreview(
@PreviewParameter(TransactionDetailUiStateProvider::class)
uiState: TransactionDetailUiState,
) {
MifosTheme {
TransactionDetailScreen(
uiState = uiState,

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.transaction.detail
import androidx.lifecycle.ViewModel
@ -12,7 +21,7 @@ import javax.inject.Inject
@HiltViewModel
class TransactionDetailViewModel @Inject constructor(
private val mUseCaseHandler: UseCaseHandler,
private val mFetchAccountTransferUseCase: FetchAccountTransfer
private val mFetchAccountTransferUseCase: FetchAccountTransfer,
) : ViewModel() {
private val _transactionDetailUiState: MutableStateFlow<TransactionDetailUiState> =
@ -20,7 +29,8 @@ class TransactionDetailViewModel @Inject constructor(
val transactionDetailUiState get() = _transactionDetailUiState
fun getTransferDetail(transferId: Long) {
mUseCaseHandler.execute(mFetchAccountTransferUseCase,
mUseCaseHandler.execute(
mFetchAccountTransferUseCase,
FetchAccountTransfer.RequestValues(transferId),
object : UseCase.UseCaseCallback<FetchAccountTransfer.ResponseValue?> {
override fun onSuccess(response: FetchAccountTransfer.ResponseValue?) {
@ -31,7 +41,7 @@ class TransactionDetailViewModel @Inject constructor(
override fun onError(message: String) {
_transactionDetailUiState.value = TransactionDetailUiState.Error
}
}
},
)
}
}

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>

View File

@ -1,5 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<string name="feature_history_transaction_id">Transaction ID : </string>
<string name="feature_history_transaction_date">Transaction Date : </string>
<string name="feature_history_other">Other</string>
<string name="feature_history_pan_id">PAN ID</string>
<string name="feature_history_view_Receipt">View Receipt</string>
<string name="feature_history_error_oops">Oops!</string>
<string name="feature_history_empty_no_transaction_history_title">Your history is empty</string>
<string name="feature_history_unexpected_error_subtitle">An unexpected error occurred. Please try again.</string>

View File

@ -1,16 +0,0 @@
package org.mifospay.history
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* 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

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)
@ -8,6 +17,5 @@ android {
}
dependencies {
implementation(projects.feature.history)
implementation(projects.core.data)
}

View File

@ -1,24 +0,0 @@
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

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.home
import androidx.compose.foundation.BorderStroke
@ -48,10 +57,11 @@ import org.mifospay.core.ui.ErrorScreenContent
import org.mifospay.core.ui.TransactionItemScreen
@Composable
fun HomeRoute(
homeViewModel: HomeViewModel = hiltViewModel(),
internal fun HomeRoute(
onRequest: (String) -> Unit,
onPay: () -> Unit
onPay: () -> Unit,
modifier: Modifier = Modifier,
homeViewModel: HomeViewModel = hiltViewModel(),
) {
val homeUIState by homeViewModel
.homeUIState
@ -61,7 +71,7 @@ fun HomeRoute(
is HomeUiState.Loading -> {
MfLoadingWheel(
contentDesc = stringResource(R.string.feature_home_loading),
backgroundColor = MaterialTheme.colorScheme.surface
backgroundColor = MaterialTheme.colorScheme.surface,
)
}
@ -73,7 +83,8 @@ fun HomeRoute(
onRequest = {
onRequest.invoke(successState.vpa ?: "")
},
onPay = onPay
onPay = onPay,
modifier = modifier,
)
}
@ -81,23 +92,23 @@ fun HomeRoute(
ErrorScreenContent(
onClickRetry = {
homeViewModel.fetchAccountDetails()
}
},
)
}
}
}
@Composable
fun HomeScreen(
private fun HomeScreen(
account: Account?,
transactions: List<Transaction>,
onRequest: () -> Unit,
onPay: () -> Unit
onPay: () -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.background(color = MaterialTheme.colorScheme.surface)
.padding(start = 32.dp, end = 32.dp),
) {
@ -107,7 +118,7 @@ fun HomeScreen(
item {
PayRequestScreen(
onRequest = onRequest,
onPay = onPay
onPay = onPay,
)
}
if (transactions.isNotEmpty()) {
@ -116,7 +127,7 @@ fun HomeScreen(
modifier = Modifier.padding(top = 32.dp),
text = "Recent Transactions",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.height(16.dp))
}
@ -131,19 +142,22 @@ fun HomeScreen(
}
@Composable
fun MifosWalletCardScreen(account: Account?) {
private fun MifosWalletCardScreen(
modifier: Modifier = Modifier,
account: Account? = null,
) {
Card(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.height(225.dp)
.padding(top = 20.dp, bottom = 32.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.onSurface)
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.onSurface),
) {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(start = 36.dp),
verticalArrangement = Arrangement.Center
verticalArrangement = Arrangement.Center,
) {
val walletBalanceLabel =
if (account != null) "(${account.currency.displayLabel})" else ""
@ -152,96 +166,109 @@ fun MifosWalletCardScreen(account: Account?) {
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight.W400,
color = lightGrey
)
color = lightGrey,
),
)
Spacer(modifier = Modifier.height(10.dp))
val accountBalance =
if (account != null) Utils.getFormattedAccountBalance(
account.balance, account.currency.code
) else "0"
if (account != null) {
Utils.getFormattedAccountBalance(
account.balance,
account.currency.code,
)
} else {
"0"
}
Text(
text = accountBalance,
style = TextStyle(
fontSize = 42.sp,
fontWeight = FontWeight(600),
color = MaterialTheme.colorScheme.surface
)
color = MaterialTheme.colorScheme.surface,
),
)
Spacer(modifier = Modifier.height(10.dp))
val currencyEqual = if (account != null) {
"${account.currency.code}1 ${account.currency.displayLabel}"
} else ""
} else {
""
}
Text(
text = currencyEqual,
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight(500),
color = lightGrey
)
color = lightGrey,
),
)
}
}
}
@Composable
fun PayRequestScreen(
private fun PayRequestScreen(
onRequest: () -> Unit,
onPay: () -> Unit
onPay: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
PayCard(
modifier = Modifier.weight(1f),
title = "Request",
icon = R.drawable.core_ui_money_in
) {
icon = R.drawable.core_ui_money_in,
{
onRequest.invoke()
}
},
modifier = Modifier.weight(1f),
)
Spacer(modifier = Modifier.width(16.dp))
PayCard(
modifier = Modifier.weight(1f),
title = "Pay",
icon = R.drawable.core_ui_money_out
) {
icon = R.drawable.core_ui_money_out,
{
onPay.invoke()
}
},
modifier = Modifier.weight(1f),
)
}
}
@Composable
fun PayCard(
modifier: Modifier,
private fun PayCard(
title: String,
icon: Int,
onClickCard: () -> Unit
onClickCard: () -> Unit,
modifier: Modifier = Modifier,
) {
Card(
modifier = modifier
.height(144.dp)
.clickable { onClickCard.invoke() },
border = BorderStroke(1.dp, border),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface)
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
) {
Column(
modifier = Modifier
.fillMaxHeight()
.padding(top = 20.dp, bottom = 20.dp, start = 20.dp),
verticalArrangement = Arrangement.SpaceBetween
verticalArrangement = Arrangement.SpaceBetween,
) {
Box(
modifier = Modifier
.size(40.dp)
.background(MaterialTheme.colorScheme.onSurface, shape = RoundedCornerShape(4.dp)),
contentAlignment = Alignment.Center
.background(
MaterialTheme.colorScheme.onSurface,
shape = RoundedCornerShape(4.dp),
),
contentAlignment = Alignment.Center,
) {
Image(
modifier = Modifier.size(20.dp),
painter = painterResource(id = icon),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surface)
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surface),
)
}
Text(text = title)
@ -251,7 +278,7 @@ fun PayCard(
@Preview(showSystemUi = true, device = "id:pixel_5")
@Composable
fun HomeScreenPreview() {
private fun HomeScreenPreview() {
HomeScreen(
account = Account(
image = "",
@ -262,9 +289,9 @@ fun HomeScreenPreview() {
currency = Currency(
code = "USD",
displayLabel = "$",
displaySymbol = "$"
displaySymbol = "$",
),
productId = 1223
productId = 1223,
),
transactions = List(25) { index ->
Transaction(
@ -273,24 +300,29 @@ fun HomeScreenPreview() {
currency = Currency(
code = "USD",
displayLabel = "$",
displaySymbol = "$"
displaySymbol = "$",
),
transactionType = TransactionType.CREDIT
transactionType = TransactionType.CREDIT,
)
},
onPay = {},
onRequest = {}
onRequest = {},
)
}
@Preview
@Composable
fun PayRequestScreenPreview() {
private fun PayRequestScreenPreview() {
PayRequestScreen({}, {})
}
@Preview
@Composable
fun PayCardPreview() {
PayCard(Modifier.width(150.dp), "Request", R.drawable.feature_home_ic_arrow_back_black_24dp) { }
private fun PayCardPreview() {
PayCard(
"Request",
R.drawable.feature_home_ic_arrow_back_black_24dp,
{ },
Modifier.width(150.dp),
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.home
import androidx.lifecycle.ViewModel
@ -11,10 +20,10 @@ 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.domain.usecase.history.HistoryContract
import org.mifospay.core.data.domain.usecase.history.TransactionsHistory
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
@ -23,8 +32,8 @@ class HomeViewModel @Inject constructor(
private val localRepository: LocalRepository,
private val preferencesHelper: PreferencesHelper,
private val fetchAccountUseCase: FetchAccount,
private val transactionsHistory: TransactionsHistory
) : ViewModel(), HistoryContract.TransactionsHistoryAsync{
private val transactionsHistory: TransactionsHistory,
) : ViewModel(), HistoryContract.TransactionsHistoryAsync {
// Expose screen UI state
private val _homeUIState: MutableStateFlow<HomeUiState> = MutableStateFlow(HomeUiState.Loading)
@ -36,7 +45,8 @@ class HomeViewModel @Inject constructor(
}
fun fetchAccountDetails() {
useCaseHandler.execute(fetchAccountUseCase,
useCaseHandler.execute(
fetchAccountUseCase,
FetchAccount.RequestValues(localRepository.clientDetails.clientId),
object : UseCaseCallback<FetchAccount.ResponseValue> {
override fun onSuccess(response: FetchAccount.ResponseValue) {
@ -44,7 +54,7 @@ class HomeViewModel @Inject constructor(
_homeUIState.update {
HomeUiState.Success(
account = response.account,
vpa = localRepository.clientDetails.externalId
vpa = localRepository.clientDetails.externalId,
)
}
response.account.id.let {
@ -55,7 +65,8 @@ class HomeViewModel @Inject constructor(
override fun onError(message: String) {
_homeUIState.update { HomeUiState.Error }
}
})
},
)
}
override fun onTransactionsFetchCompleted(transactions: List<Transaction>?) {
@ -71,7 +82,7 @@ sealed interface HomeUiState {
data class Success(
val account: Account? = null,
val transactions: List<Transaction> = emptyList(),
val vpa: String? = null
val vpa: String? = null,
) : HomeUiState
data object Error : HomeUiState

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.home.navigation
import androidx.navigation.NavController
@ -12,12 +21,12 @@ fun NavController.navigateToHome(navOptions: NavOptions) = navigate(HOME_ROUTE,
fun NavGraphBuilder.homeScreen(
onRequest: (String) -> Unit,
onPay: () -> Unit
onPay: () -> Unit,
) {
composable(route = HOME_ROUTE) {
HomeRoute(
onRequest = onRequest,
onPay = onPay
onPay = onPay,
)
}
}

View File

@ -1,3 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<color name="feature_home_colorBlack87">#DE000000</color>
</resources>

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<resources>
<string name="feature_home_loading">Loading</string>
</resources>

View File

@ -1,16 +0,0 @@
package org.mifospay.feature.home
import org.junit.Assert.assertEquals
import org.junit.Test
/**
* 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

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)
@ -9,5 +18,4 @@ android {
dependencies {
implementation(projects.core.data)
implementation(projects.feature.receipt)
}

View File

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

View File

@ -1,4 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file,
You can obtain one at https://mozilla.org/MPL/2.0/.
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,6 +1,14 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.invoices
import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
@ -14,7 +22,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -40,30 +47,31 @@ import org.mifospay.core.ui.ErrorScreenContent
import org.mifospay.invoices.R
@Composable
fun InvoiceDetailScreen(
viewModel: InvoiceDetailViewModel = hiltViewModel(),
data: Uri?,
internal fun InvoiceDetailScreen(
onBackPress: () -> Unit,
navigateToReceiptScreen: (String) -> Unit
navigateToReceiptScreen: (String) -> Unit,
modifier: Modifier = Modifier,
viewModel: InvoiceDetailViewModel = hiltViewModel(),
) {
val invoiceDetailUiState by viewModel.invoiceDetailUiState.collectAsStateWithLifecycle()
InvoiceDetailScreen(
invoiceDetailUiState = invoiceDetailUiState,
onBackPress = onBackPress,
navigateToReceiptScreen = navigateToReceiptScreen
navigateToReceiptScreen = navigateToReceiptScreen,
modifier = modifier,
)
LaunchedEffect(key1 = true) {
viewModel.getInvoiceDetails(data)
}
}
@Composable
fun InvoiceDetailScreen(
private fun InvoiceDetailScreen(
invoiceDetailUiState: InvoiceDetailUiState,
onBackPress: () -> Unit,
navigateToReceiptScreen: (String) -> Unit
navigateToReceiptScreen: (String) -> Unit,
modifier: Modifier = Modifier,
) {
MifosScaffold(
modifier = modifier,
topBarTitle = R.string.feature_invoices_invoice,
backPress = { onBackPress.invoke() },
scaffoldContent = {
@ -71,7 +79,7 @@ fun InvoiceDetailScreen(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.surface)
.padding(it)
.padding(it),
) {
when (invoiceDetailUiState) {
is InvoiceDetailUiState.Error -> {
@ -80,7 +88,7 @@ fun InvoiceDetailScreen(
InvoiceDetailUiState.Loading -> {
MfOverlayLoadingWheel(
contentDesc = stringResource(R.string.feature_invoices_loading)
contentDesc = stringResource(R.string.feature_invoices_loading),
)
}
@ -89,90 +97,92 @@ fun InvoiceDetailScreen(
invoiceDetailUiState.invoice,
invoiceDetailUiState.merchantId,
invoiceDetailUiState.paymentLink,
navigateToReceiptScreen = navigateToReceiptScreen
navigateToReceiptScreen = navigateToReceiptScreen,
)
}
}
}
})
},
)
}
@Composable
@Suppress("LongMethod")
fun InvoiceDetailsContent(
private fun InvoiceDetailsContent(
invoice: Invoice?,
merchantId: String?,
paymentLink: String?,
navigateToReceiptScreen: (String) -> Unit
navigateToReceiptScreen: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val clipboardManager = LocalClipboardManager.current
Column(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(horizontal = 16.dp),
) {
Text(
text = stringResource(R.string.feature_invoices_invoice_details),
modifier = Modifier.padding(top = 16.dp)
modifier = Modifier.padding(top = 16.dp),
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(R.string.feature_invoices_merchant_id),
modifier = Modifier.padding(top = 16.dp)
modifier = Modifier.padding(top = 16.dp),
)
Text(
text = merchantId.toString(),
modifier = Modifier.padding(top = 16.dp)
modifier = Modifier.padding(top = 16.dp),
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(R.string.feature_invoices_consumer_id),
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
Text(
text = (invoice?.consumerName + " " + invoice?.consumerId),
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = R.string.feature_invoices_amount),
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
Text(
text = Constants.INR + " " + invoice?.amount + "",
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = R.string.feature_invoices_items_bought),
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
Text(
text = invoice?.itemsBought.toString(),
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
}
@ -180,42 +190,42 @@ fun InvoiceDetailsContent(
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = R.string.feature_invoices_status),
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
Text(
text = Constants.DONE,
modifier = Modifier.padding(top = 8.dp)
modifier = Modifier.padding(top = 8.dp),
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = R.string.feature_invoices_transaction_id),
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(top = 10.dp)
.padding(top = 10.dp),
)
Text(
text = invoice.transactionId ?: "",
color = MaterialTheme.colorScheme.primary,
modifier = Modifier
.padding(top = 10.dp)
.then(Modifier.height(0.dp))
.then(Modifier.height(0.dp)),
)
}
Divider()
Text(
text = stringResource(id = R.string.feature_invoices_unique_receipt_link),
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(bottom = 10.dp)
modifier = Modifier.padding(bottom = 10.dp),
)
Text(
text = invoice.transactionId ?: "",
@ -227,33 +237,34 @@ fun InvoiceDetailsContent(
onPress = {
invoice.transactionId?.let { it1 ->
navigateToReceiptScreen.invoke(
it1
it1,
)
}
},
onLongPress = {
clipboardManager.setText(
AnnotatedString(
Constants.RECEIPT_DOMAIN + invoice.transactionId
Constants.RECEIPT_DOMAIN + invoice.transactionId,
),
)
},
)
})
}
},
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(id = R.string.feature_invoices_date),
modifier = Modifier.padding(top = 10.dp)
modifier = Modifier.padding(top = 10.dp),
)
Text(
text = DateHelper.getDateAsString(invoice!!.date),
modifier = Modifier.padding(top = 10.dp)
modifier = Modifier.padding(top = 10.dp),
)
}
@ -261,18 +272,18 @@ fun InvoiceDetailsContent(
Text(
text = stringResource(id = R.string.feature_invoices_payment_options_will_be_fetched_from_upi),
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(vertical = 10.dp)
modifier = Modifier.padding(vertical = 10.dp),
)
Divider()
Text(
text = stringResource(id = R.string.feature_invoices_unique_payment_link),
fontWeight = FontWeight.Normal,
modifier = Modifier.padding(bottom = 10.dp)
modifier = Modifier.padding(bottom = 10.dp),
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = paymentLink.toString(),
@ -283,21 +294,21 @@ fun InvoiceDetailsContent(
detectTapGestures(
onLongPress = {
clipboardManager.setText(AnnotatedString(paymentLink.toString()))
})
}
},
)
},
)
}
}
}
@Composable
fun Divider(modifier: Modifier = Modifier) {
private fun Divider(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxWidth()
.height(1.dp)
.background(Color(0x44000000))
.background(Color(0x44000000)),
)
}
@ -306,21 +317,21 @@ class InvoiceDetailScreenProvider : PreviewParameterProvider<InvoiceDetailUiStat
get() = sequenceOf(
InvoiceDetailUiState.Loading,
InvoiceDetailUiState.Error("Some Error Occurred"),
InvoiceDetailUiState.Success(Invoice(), "", "")
InvoiceDetailUiState.Success(Invoice(), "", ""),
)
}
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun InvoiceDetailScreenPreview(
@PreviewParameter(InvoiceDetailScreenProvider::class) invoiceDetailUiState: InvoiceDetailUiState
private fun InvoiceDetailScreenPreview(
@PreviewParameter(InvoiceDetailScreenProvider::class)
invoiceDetailUiState: InvoiceDetailUiState,
) {
MifosTheme {
InvoiceDetailScreen(
invoiceDetailUiState = invoiceDetailUiState,
onBackPress = {},
navigateToReceiptScreen = {}
navigateToReceiptScreen = {},
)
}
}

View File

@ -1,6 +1,16 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.invoices
import android.net.Uri
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.mifospay.core.model.entity.Invoice
import dagger.hilt.android.lifecycle.HiltViewModel
@ -10,34 +20,48 @@ import org.mifospay.core.data.base.UseCase
import org.mifospay.core.data.base.UseCaseHandler
import org.mifospay.core.data.domain.usecase.invoice.FetchInvoice
import org.mifospay.core.datastore.PreferencesHelper
import org.mifospay.feature.invoices.navigation.INVOICE_DATA_ARG
import javax.inject.Inject
@HiltViewModel
class InvoiceDetailViewModel @Inject constructor(
private val mUseCaseHandler: UseCaseHandler,
private val mPreferencesHelper: PreferencesHelper,
private val fetchInvoiceUseCase: FetchInvoice
private val fetchInvoiceUseCase: FetchInvoice,
savedStateHandle: SavedStateHandle,
) : ViewModel() {
private val _invoiceDetailUiState =
MutableStateFlow<InvoiceDetailUiState>(InvoiceDetailUiState.Loading)
val invoiceDetailUiState: StateFlow<InvoiceDetailUiState> = _invoiceDetailUiState
fun getInvoiceDetails(data: Uri?) {
mUseCaseHandler.execute(fetchInvoiceUseCase, FetchInvoice.RequestValues(data),
init {
savedStateHandle.get<String>(INVOICE_DATA_ARG)?.let { invoiceData ->
Uri.decode(invoiceData)?.let {
getInvoiceDetails(Uri.parse(it))
}
}
}
private fun getInvoiceDetails(data: Uri?) {
mUseCaseHandler.execute(
fetchInvoiceUseCase,
FetchInvoice.RequestValues(data),
object : UseCase.UseCaseCallback<FetchInvoice.ResponseValue> {
override fun onSuccess(response: FetchInvoice.ResponseValue) {
_invoiceDetailUiState.value = InvoiceDetailUiState.Success(
response.invoices[0],
mPreferencesHelper.fullName + " "
+ mPreferencesHelper.clientId, data.toString()
mPreferencesHelper.fullName + " " +
mPreferencesHelper.clientId,
data.toString(),
)
}
override fun onError(message: String) {
_invoiceDetailUiState.value = InvoiceDetailUiState.Error(message)
}
})
},
)
}
}
@ -46,7 +70,7 @@ sealed interface InvoiceDetailUiState {
data class Success(
val invoice: Invoice?,
val merchantId: String?,
val paymentLink: String?
val paymentLink: String?,
) : InvoiceDetailUiState
data class Error(val message: String) : InvoiceDetailUiState

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.invoices
import androidx.compose.foundation.background
@ -25,81 +34,84 @@ import org.mifospay.core.designsystem.theme.grey
import org.mifospay.invoices.R
@Composable
fun InvoiceItem(
internal fun InvoiceItem(
invoiceTitle: String,
invoiceAmount: String,
invoiceStatus: String,
invoiceDate: String,
invoiceId: String,
invoiceStatusIcon: Long,
onClick: (String) -> Unit
onClick: (String) -> Unit,
modifier: Modifier = Modifier,
) {
Card(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.padding(4.dp)
.clickable { onClick(invoiceId) },
elevation = CardDefaults.cardElevation(4.dp),
colors = CardDefaults.cardColors(Color.White)
colors = CardDefaults.cardColors(Color.White),
) {
Column {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(
id = if (invoiceStatusIcon == 0L)
id = if (invoiceStatusIcon == 0L) {
R.drawable.feature_invoices_ic_remove_circle_outline_black_24dp
else R.drawable.feature_invoices_ic_check_round_black_24dp
} else {
R.drawable.feature_invoices_ic_check_round_black_24dp
},
),
contentDescription = "Invoice Status",
modifier = Modifier
.size(64.dp)
.padding(5.dp),
tint = if (invoiceStatusIcon == 0L) Color.Yellow else Color.Blue
tint = if (invoiceStatusIcon == 0L) Color.Yellow else Color.Blue,
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(end = 10.dp)
.padding(end = 10.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = invoiceTitle,
color = Color.Black,
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
)
Text(
text = invoiceAmount,
color = Color.Black,
textAlign = TextAlign.End,
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
)
}
Text(
text = invoiceStatus,
color = grey,
modifier = Modifier.padding(top = 1.dp)
modifier = Modifier.padding(top = 1.dp),
)
Row(
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
) {
Text(
text = invoiceDate,
color = grey,
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
)
Text(
text = invoiceId,
color = grey,
textAlign = TextAlign.End,
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
)
}
Spacer(
@ -107,7 +119,7 @@ fun InvoiceItem(
.padding(top = 10.dp)
.fillMaxWidth()
.height(1.dp)
.background(Color.Gray)
.background(Color.Gray),
)
}
}
@ -117,6 +129,14 @@ fun InvoiceItem(
@Preview(showBackground = true)
@Composable
fun PreviewInvoiceItem() {
InvoiceItem("Logo for Richard", "$3000", "Pending", "12/3/4", "Invoice id:12345", 0L) {}
private fun PreviewInvoiceItem() {
InvoiceItem(
invoiceTitle = "Logo for Richard",
invoiceAmount = "$3000",
invoiceStatus = "Pending",
invoiceDate = "12/3/4",
invoiceId = "Invoice id:12345",
invoiceStatusIcon = 0L,
onClick = {},
)
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2024 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.feature.invoices
import android.net.Uri
@ -25,24 +34,26 @@ import org.mifospay.invoices.R
@Composable
fun InvoiceScreenRoute(
navigateToInvoiceDetailScreen: (Uri) -> Unit,
modifier: Modifier = Modifier,
viewModel: InvoicesViewModel = hiltViewModel(),
navigateToInvoiceDetailScreen: (Uri) -> Unit
) {
val invoiceUiState by viewModel.invoiceUiState.collectAsStateWithLifecycle()
InvoiceScreen(
invoiceUiState = invoiceUiState,
getUniqueInvoiceLink = { viewModel.getUniqueInvoiceLink(it) },
navigateToInvoiceDetailScreen = navigateToInvoiceDetailScreen
navigateToInvoiceDetailScreen = navigateToInvoiceDetailScreen,
modifier = modifier,
)
}
@Composable
fun InvoiceScreen(
private fun InvoiceScreen(
invoiceUiState: InvoicesUiState,
getUniqueInvoiceLink: (Long) -> Uri?,
navigateToInvoiceDetailScreen: (Uri) -> Unit
navigateToInvoiceDetailScreen: (Uri) -> Unit,
modifier: Modifier = Modifier,
) {
when (invoiceUiState) {
is InvoicesUiState.Error -> {
EmptyContentScreen(
@ -50,14 +61,16 @@ fun InvoiceScreen(
title = stringResource(id = R.string.feature_invoices_error_oops),
subTitle = stringResource(id = R.string.feature_invoices_unexpected_error_subtitle),
iconTint = Color.Black,
iconImageVector = Info
iconImageVector = Info,
)
}
is InvoicesUiState.InvoiceList -> {
Column(modifier = Modifier.fillMaxSize()) {
Column(modifier = modifier.fillMaxSize()) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(invoiceUiState.list) {
items(
items = invoiceUiState.list,
) {
InvoiceItem(
invoiceTitle = it?.title.toString(),
invoiceAmount = it?.amount.toString(),
@ -70,7 +83,7 @@ fun InvoiceScreen(
invoiceUri?.let { uri ->
navigateToInvoiceDetailScreen.invoke(uri)
}
}
},
)
}
}
@ -89,7 +102,7 @@ fun InvoiceScreen(
InvoicesUiState.Loading -> {
MifosLoadingWheel(
modifier = Modifier.fillMaxWidth(),
contentDesc = stringResource(R.string.feature_invoices_loading)
contentDesc = stringResource(R.string.feature_invoices_loading),
)
}
}
@ -101,14 +114,14 @@ class InvoicesUiStateProvider : PreviewParameterProvider<InvoicesUiState> {
InvoicesUiState.Loading,
InvoicesUiState.Empty,
InvoicesUiState.InvoiceList(sampleInvoiceList),
InvoicesUiState.Error("Some Error Occurred")
InvoicesUiState.Error("Some Error Occurred"),
)
}
@Preview(showBackground = true, showSystemUi = true)
@Composable
private fun InvoiceScreenPreview(
@PreviewParameter(InvoicesUiStateProvider::class) invoiceUiState: InvoicesUiState
@PreviewParameter(InvoicesUiStateProvider::class) invoiceUiState: InvoicesUiState,
) {
MifosTheme {
InvoiceScreen(invoiceUiState = invoiceUiState, getUniqueInvoiceLink = { Uri.EMPTY }, {})
@ -125,6 +138,6 @@ val sampleInvoiceList = List(10) { index ->
transactionId = "txn_78910",
id = index.toLong(),
title = "Stationery Purchase",
date = mutableListOf(2024, 3, 23)
date = mutableListOf(2024, 3, 23),
)
}

Some files were not shown because too many files have changed in this diff Show More