mirror of
https://github.com/openMF/mobile-wallet.git
synced 2026-02-06 09:37:24 +00:00
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:
parent
6f87da5869
commit
e45c9b6f59
7
.editorconfig
Normal file
7
.editorconfig
Normal 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
|
||||
29
.github/workflows/master_dev_ci.yml
vendored
29
.github/workflows/master_dev_ci.yml
vendored
@ -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
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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>?)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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())
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
|
||||
@ -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),
|
||||
)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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",
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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 = {},
|
||||
)
|
||||
}
|
||||
@ -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(
|
||||
viewModel: AccountViewModel = hiltViewModel(),
|
||||
navigateToBankAccountDetailScreen: (BankAccountDetails, Int) -> Unit,
|
||||
navigateToLinkBankAccountScreen: () -> Unit
|
||||
navigateToLinkBankAccountScreen: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: AccountViewModel = hiltViewModel(),
|
||||
) {
|
||||
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()
|
||||
@ -55,26 +66,29 @@ fun AccountsScreen(
|
||||
onUpdateAccount = { bankAccountDetails, index ->
|
||||
viewModel.updateBankAccount(index, bankAccountDetails)
|
||||
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",
|
||||
)
|
||||
}
|
||||
|
||||
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
{}, {}, {}, {}
|
||||
{}, {}, {}, {},
|
||||
)
|
||||
}
|
||||
@ -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 = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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),
|
||||
)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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
|
||||
|
||||
@ -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 = "Don’t 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 = { _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = { _, _ -> },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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(),
|
||||
onLoginSuccess: () -> Unit,
|
||||
internal fun SignupScreen(
|
||||
savingProductId: Int,
|
||||
mobileNumber: String,
|
||||
country: String,
|
||||
email: String,
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
businessName:String
|
||||
businessName: String,
|
||||
onLoginSuccess: () -> Unit,
|
||||
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 = { },
|
||||
)
|
||||
}
|
||||
|
||||
@ -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) {}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,10 +308,10 @@ 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,
|
||||
@ -309,7 +321,7 @@ fun GoogleIdTokenCredential?.signUpWithMifos(
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SignupMethodContentScreenPreview() {
|
||||
private fun SignupMethodContentScreenPreview() {
|
||||
MaterialTheme {
|
||||
SignupMethodContentScreen(true, {}, {})
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
@ -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, {}, {}, { _, _, _ -> })
|
||||
|
||||
@ -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")
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
@ -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,
|
||||
)
|
||||
|
||||
@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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(),
|
||||
)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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>?)
|
||||
}
|
||||
}
|
||||
@ -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 = { _, _ -> })
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
@ -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 -> {
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"/>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@ -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,7 +32,7 @@ class HomeViewModel @Inject constructor(
|
||||
private val localRepository: LocalRepository,
|
||||
private val preferencesHelper: PreferencesHelper,
|
||||
private val fetchAccountUseCase: FetchAccount,
|
||||
private val transactionsHistory: TransactionsHistory
|
||||
private val transactionsHistory: TransactionsHistory,
|
||||
) : ViewModel(), HistoryContract.TransactionsHistoryAsync {
|
||||
|
||||
// Expose screen UI state
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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 = {},
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user