diff --git a/build-logic/convention/src/main/kotlin/org/mifospay/Detekt.kt b/build-logic/convention/src/main/kotlin/org/mifospay/Detekt.kt index 1059d2da3..134c76d64 100644 --- a/build-logic/convention/src/main/kotlin/org/mifospay/Detekt.kt +++ b/build-logic/convention/src/main/kotlin/org/mifospay/Detekt.kt @@ -5,9 +5,11 @@ import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.gradle.api.Project import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.named +import org.jetbrains.kotlin.gradle.dsl.JvmTarget internal fun Project.configureDetekt(extension: DetektExtension) = extension.apply { tasks.named("detekt") { + jvmTarget = "17" reports { xml.required.set(true) html.required.set(true) diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt index b4f5fc439..30c643617 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/IconBox.kt @@ -12,6 +12,7 @@ package org.mifospay.core.designsystem.component import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedIconButton import androidx.compose.material3.Surface import androidx.compose.runtime.Composable @@ -21,7 +22,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.mifospay.core.designsystem.icon.MifosIcons import org.mifospay.core.designsystem.theme.MifosTheme -import org.mifospay.core.designsystem.theme.NewUi @Composable fun IconBox( @@ -33,7 +33,7 @@ fun IconBox( onClick = onClick, modifier = modifier, shape = RoundedCornerShape(12.dp), - border = BorderStroke(2.dp, NewUi.onSurface.copy(alpha = 0.1f)), + border = BorderStroke(2.dp, MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)), ) { Icon( imageVector = icon, diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt index fee7acbed..1dff9c437 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt @@ -12,6 +12,7 @@ package org.mifospay.core.designsystem.component import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -43,6 +44,7 @@ fun MifosScaffold( onClick = content.onClick, contentColor = content.contentColor, content = content.content, + containerColor = MaterialTheme.colorScheme.primary, ) } }, diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt index 0aac6d9e6..5a06560d4 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/MifosTab.kt @@ -9,12 +9,17 @@ */ package org.mifospay.core.designsystem.component +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp @Composable fun MifosTab( @@ -22,18 +27,25 @@ fun MifosTab( selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, - selectedContentColor: Color = MaterialTheme.colorScheme.onSurface, - unselectedContentColor: Color = Color.LightGray, + selectedContentColor: Color = MaterialTheme.colorScheme.primary, + unselectedContentColor: Color = MaterialTheme.colorScheme.primaryContainer, ) { Tab( text = { Text( text = text, - color = MaterialTheme.colorScheme.onSurface, + color = if (selected) { + MaterialTheme.colorScheme.onPrimary + } else { + MaterialTheme.colorScheme.onSurface + }, ) }, selected = selected, - modifier = modifier, + modifier = modifier + .clip(RoundedCornerShape(25.dp)) + .background(if (selected) selectedContentColor else unselectedContentColor) + .padding(horizontal = 20.dp), selectedContentColor = selectedContentColor, unselectedContentColor = unselectedContentColor, onClick = onClick, diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/TextField.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/TextField.kt index a7b692f23..f1c90fd8d 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/TextField.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/component/TextField.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -57,7 +58,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.mifospay.core.designsystem.theme.MifosTheme -import org.mifospay.core.designsystem.theme.NewUi @Composable fun MfOutlinedTextField( @@ -84,18 +84,15 @@ fun MfOutlinedTextField( }, singleLine = singleLine, trailingIcon = trailingIcon, - keyboardActions = - KeyboardActions { + keyboardActions = KeyboardActions { onKeyboardActions?.invoke() }, keyboardOptions = keyboardOptions, - colors = - OutlinedTextFieldDefaults.colors( + colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = MaterialTheme.colorScheme.onSurface, focusedLabelColor = MaterialTheme.colorScheme.onSurface, ), - textStyle = - LocalDensity.current.run { + textStyle = LocalDensity.current.run { TextStyle(fontSize = 18.sp, color = MaterialTheme.colorScheme.onSurface) }, ) @@ -118,8 +115,7 @@ fun MfPasswordTextField( onValueChange = onPasswordChange, label = { Text(label) }, isError = isError, - visualTransformation = - if (isPasswordVisible) { + visualTransformation = if (isPasswordVisible) { VisualTransformation.None } else { PasswordVisualTransformation() @@ -157,14 +153,12 @@ fun MifosOutlinedTextField( onValueChange = onValueChange, label = { Text(stringResource(id = label)) }, modifier = modifier, - leadingIcon = - if (icon != null) { + leadingIcon = if (icon != null) { { Image( painter = painterResource(id = icon), contentDescription = null, - colorFilter = - ColorFilter.tint( + colorFilter = ColorFilter.tint( MaterialTheme.colorScheme.onSurface, ), ) @@ -175,13 +169,11 @@ fun MifosOutlinedTextField( trailingIcon = trailingIcon, maxLines = maxLines, singleLine = singleLine, - colors = - OutlinedTextFieldDefaults.colors( + colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = MaterialTheme.colorScheme.onSurface, focusedLabelColor = MaterialTheme.colorScheme.onSurface, ), - textStyle = - LocalDensity.current.run { + textStyle = LocalDensity.current.run { TextStyle(fontSize = 18.sp, color = MaterialTheme.colorScheme.onSurface) }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), @@ -209,6 +201,9 @@ fun MifosTextField( minLines: Int = 1, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + trailingIcon: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + indicatorColor: Color? = null, ) { var isFocused by rememberSaveable { mutableStateOf(false) } @@ -232,31 +227,56 @@ fun MifosTextField( singleLine = singleLine, maxLines = maxLines, minLines = minLines, - cursorBrush = SolidColor(NewUi.primaryColor), + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), decorationBox = { innerTextField -> Column { Text( text = label, - color = NewUi.primaryColor, + color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.labelLarge, modifier = Modifier.align(alignment = Alignment.Start), ) Spacer(modifier = Modifier.height(5.dp)) - innerTextField() + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + if (leadingIcon != null) { + leadingIcon() + } - Spacer(modifier = Modifier.height(5.dp)) - HorizontalDivider( - thickness = 1.dp, - color = if (isFocused) { - NewUi.secondaryColor - } else { - NewUi.onSurface.copy(alpha = 0.05f) - }, - ) + Box(modifier = Modifier.weight(1f)) { + innerTextField() + } + + if (trailingIcon != null) { + trailingIcon() + } + } + indicatorColor?.let { color -> + HorizontalDivider( + thickness = 1.dp, + color = if (isFocused) { + color + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f) + }, + ) + } ?: run { + HorizontalDivider( + thickness = 1.dp, + color = if (isFocused) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f) + }, + ) + } } }, + ) } diff --git a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/theme/Color.kt b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/theme/Color.kt index f7ac9f335..8ffbff153 100644 --- a/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/theme/Color.kt +++ b/core/designsystem/src/main/kotlin/org/mifospay/core/designsystem/theme/Color.kt @@ -11,9 +11,9 @@ package org.mifospay.core.designsystem.theme import androidx.compose.ui.graphics.Color -val md_theme_light_primary = Color(0xFF000000) -val md_theme_light_onPrimary = Color(0xFFFFFFFF) -val md_theme_light_primaryContainer = Color(0xFFFFD9E2) +val md_theme_light_primary = Color(0xFF0673BA) // primary +val md_theme_light_onPrimary = Color(0xFFFFFFFF) // gradientOne +val md_theme_light_primaryContainer = Color(0xFFF5F5F5) // container color val md_theme_light_onPrimaryContainer = Color(0xFF3E001D) val md_theme_light_secondary = Color(0xFF984061) val md_theme_light_onSecondary = Color(0xFFFFFFFF) @@ -30,7 +30,7 @@ val md_theme_light_onErrorContainer = Color(0xFF410002) val md_theme_light_background = Color(0xFFFFFBFF) val md_theme_light_onBackground = Color(0xFF330045) val md_theme_light_surface = Color(0xFFFFFBFF) -val md_theme_light_onSurface = Color(0xFF000000) +val md_theme_light_onSurface = Color(0xFF333333) // onSurface val md_theme_light_surfaceVariant = Color(0xFFF2DDE1) val md_theme_light_onSurfaceVariant = Color(0xFF514347) val md_theme_light_outline = Color(0xFF837377) diff --git a/core/ui/src/main/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt b/core/ui/src/main/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt index b806e14fb..48c53fa4e 100644 --- a/core/ui/src/main/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt +++ b/core/ui/src/main/kotlin/org/mifospay/core/ui/ScrollableTabRow.kt @@ -29,9 +29,9 @@ fun MifosScrollableTabRow( tabContents: List, pagerState: PagerState, modifier: Modifier = Modifier, - containerColor: Color = MaterialTheme.colorScheme.surface, - selectedContentColor: Color = MaterialTheme.colorScheme.onSurface, - unselectedContentColor: Color = Color.LightGray, + containerColor: Color = MaterialTheme.colorScheme.primaryContainer, + selectedContentColor: Color = MaterialTheme.colorScheme.primary, + unselectedContentColor: Color = MaterialTheme.colorScheme.primaryContainer, edgePadding: Dp = 8.dp, ) { val scope = rememberCoroutineScope() @@ -41,6 +41,8 @@ fun MifosScrollableTabRow( containerColor = containerColor, selectedTabIndex = pagerState.currentPage, edgePadding = edgePadding, + indicator = {}, + divider = {}, ) { tabContents.forEachIndexed { index, currentTab -> MifosTab( diff --git a/feature/history/src/main/kotlin/org/mifospay/feature/history/HistoryScreen.kt b/feature/history/src/main/kotlin/org/mifospay/feature/history/HistoryScreen.kt index 8d7ad0d7c..f2f3d0d7d 100644 --- a/feature/history/src/main/kotlin/org/mifospay/feature/history/HistoryScreen.kt +++ b/feature/history/src/main/kotlin/org/mifospay/feature/history/HistoryScreen.kt @@ -10,6 +10,7 @@ package org.mifospay.feature.history import android.widget.Toast +import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -21,6 +22,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -46,7 +49,6 @@ import org.mifospay.core.designsystem.component.MifosBottomSheet import org.mifospay.core.designsystem.component.MifosButton import org.mifospay.core.designsystem.component.MifosLoadingWheel import org.mifospay.core.designsystem.icon.MifosIcons -import org.mifospay.core.designsystem.theme.lightGrey import org.mifospay.core.ui.EmptyContentScreen import org.mifospay.core.ui.TransactionItemScreen import org.mifospay.feature.transaction.detail.TransactionDetailScreen @@ -141,6 +143,8 @@ private fun HistoryScreen( modifier = Modifier .clickable { transactionDetailState = it }, ) + HorizontalDivider(thickness = 0.5.dp, modifier = Modifier.padding(5.dp)) + Spacer(modifier = Modifier.height(15.dp)) } } } @@ -178,9 +182,19 @@ private fun Chip( modifier: Modifier = Modifier, ) { val context = LocalContext.current - val backgroundColor = if (selected) MaterialTheme.colorScheme.primary else lightGrey + val backgroundColor = MaterialTheme.colorScheme.onPrimary MifosButton( - modifier = modifier, + modifier = modifier.then( + if (selected) { + Modifier.border( + width = 1.dp, + color = MaterialTheme.colorScheme.primary, + shape = RoundedCornerShape(25.dp), + ) + } else { + Modifier + }, + ), onClick = { onClick() Toast.makeText(context, label, Toast.LENGTH_SHORT).show() @@ -190,7 +204,7 @@ private fun Chip( 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 = MaterialTheme.colorScheme.onSurface, ) } } diff --git a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/RequestScreen.kt b/feature/payments/src/main/kotlin/org/mifospay/feature/payments/RequestScreen.kt index 222a24c3c..6e5d76bb0 100644 --- a/feature/payments/src/main/kotlin/org/mifospay/feature/payments/RequestScreen.kt +++ b/feature/payments/src/main/kotlin/org/mifospay/feature/payments/RequestScreen.kt @@ -10,11 +10,13 @@ package org.mifospay.feature.payments import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -66,56 +68,45 @@ internal fun RequestScreenContent( style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary, ) - - Row( + Column( modifier = Modifier .fillMaxWidth() - .padding(start = 20.dp), - verticalAlignment = Alignment.CenterVertically, + .padding(start = 20.dp, top = 20.dp, end = 15.dp) + .weight(1f), ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 20.dp) - .weight(1f), - ) { - Column { - Text(text = stringResource(id = R.string.feature_payments_virtual_payment_address_vpa)) - Text( - text = vpa, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - ) - } - - Column(modifier = Modifier.padding(top = 10.dp)) { - Text(text = stringResource(id = R.string.feature_payments_mobile_number)) - Text( - text = mobile, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface, - ) - } - } - - Column( - modifier = Modifier - .fillMaxWidth() - .weight(1f), - horizontalAlignment = Alignment.CenterHorizontally, + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, ) { + Text(text = stringResource(id = R.string.feature_payments_virtual_payment_address_vpa)) + Text( + text = vpa, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) IconButton( onClick = { showQr(vpa) }, ) { Icon( imageVector = MifosIcons.QrCode, - tint = MaterialTheme.colorScheme.primary, + tint = MaterialTheme.colorScheme.onSurface, contentDescription = stringResource(id = R.string.feature_payments_show_code), ) } + } + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy + (alpha = 0.05f), + ) + Column(modifier = Modifier.padding(top = 10.dp)) { + Text(text = stringResource(id = R.string.feature_payments_mobile_number)) Text( - text = stringResource(id = R.string.feature_payments_show_code), + text = mobile, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, ) } } diff --git a/feature/payments/src/main/res/values/strings.xml b/feature/payments/src/main/res/values/strings.xml index 5eb8935d6..58b227c27 100644 --- a/feature/payments/src/main/res/values/strings.xml +++ b/feature/payments/src/main/res/values/strings.xml @@ -10,7 +10,7 @@ --> Virtual Payment Address (VPA) - Mobile Number + Phone Number Receive Show code diff --git a/feature/profile/src/main/kotlin/org/mifospay/feature/profile/ProfileCard.kt b/feature/profile/src/main/kotlin/org/mifospay/feature/profile/ProfileCard.kt index 9ee3b9a1d..44aab968b 100644 --- a/feature/profile/src/main/kotlin/org/mifospay/feature/profile/ProfileCard.kt +++ b/feature/profile/src/main/kotlin/org/mifospay/feature/profile/ProfileCard.kt @@ -26,7 +26,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import org.mifospay.core.designsystem.theme.NewUi @Composable fun ProfileDetailsCard( @@ -45,7 +44,7 @@ fun ProfileDetailsCard( ), shape = RoundedCornerShape(15.dp), colors = CardDefaults.cardColors( - containerColor = NewUi.containerColor, + containerColor = MaterialTheme.colorScheme.primaryContainer, ), ) { Column( @@ -79,7 +78,7 @@ fun ProfileItem( ) { Text( text = label, - color = NewUi.primaryColor, + color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.labelLarge, ) Spacer(modifier = Modifier.height(10.dp)) @@ -92,7 +91,7 @@ fun ProfileItem( Spacer(modifier = Modifier.height(4.dp)) HorizontalDivider( thickness = 1.dp, - color = NewUi.onSurface.copy(alpha = 0.05f), + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f), ) } } diff --git a/feature/send-money/src/main/kotlin/org/mifospay/feature/send/money/SendScreenRoute.kt b/feature/send-money/src/main/kotlin/org/mifospay/feature/send/money/SendScreenRoute.kt index 4452bd258..bf4541b15 100644 --- a/feature/send-money/src/main/kotlin/org/mifospay/feature/send/money/SendScreenRoute.kt +++ b/feature/send-money/src/main/kotlin/org/mifospay/feature/send/money/SendScreenRoute.kt @@ -15,6 +15,7 @@ import android.provider.ContactsContract import android.util.Log import android.widget.Toast import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -22,13 +23,15 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AttachMoney import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -42,7 +45,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource @@ -53,15 +55,16 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions import com.google.mlkit.vision.codescanner.GmsBarcodeScanning -import com.mifos.library.countrycodepicker.CountryCodePicker +import com.mifos.library.countrycodepicker.CountryCodePickerPayment import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.koin.androidx.compose.koinViewModel -import org.mifospay.core.designsystem.component.MfOutlinedTextField import org.mifospay.core.designsystem.component.MfOverlayLoadingWheel import org.mifospay.core.designsystem.component.MifosButton import org.mifospay.core.designsystem.component.MifosNavigationTopAppBar +import org.mifospay.core.designsystem.component.MifosTextField import org.mifospay.core.designsystem.icon.MifosIcons +import org.mifospay.core.designsystem.theme.MifosBlue import org.mifospay.core.designsystem.theme.styleMedium16sp import org.mifospay.core.designsystem.theme.styleNormal18sp @@ -182,7 +185,11 @@ internal fun SendMoneyScreen( } } - Box(modifier) { + Box( + modifier + .padding(top = 5.dp) + .imePadding(), + ) { Column(Modifier.fillMaxSize()) { if (showToolBar) { MifosNavigationTopAppBar( @@ -190,113 +197,125 @@ internal fun SendMoneyScreen( onNavigationClick = onBackClick, ) } + + MifosTextField( + value = amount, + label = stringResource(id = R.string.feature_send_money_amount), + onValueChange = { + amount = it + validateInfo() + }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + singleLine = true, + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), + leadingIcon = { + Icon( + imageVector = Icons.Default.AttachMoney, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.padding(end = 8.dp), + ) + }, + indicatorColor = MifosBlue, + ) + + when (sendMethodType) { + SendMethodType.VPA -> { + MifosTextField( + value = vpa, + label = stringResource(id = R.string.feature_send_money_virtual_payment_address), + onValueChange = { + vpa = it + validateInfo() + }, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + trailingIcon = { + IconButton( + onClick = { startScan() }, + ) { + Icon( + imageVector = MifosIcons.QrCode2, + contentDescription = "Scan QR", + tint = MaterialTheme.colorScheme.onSurface, + ) + } + }, + indicatorColor = MaterialTheme.colorScheme.primary, + ) + } + + SendMethodType.MOBILE -> { + EnterPhoneScreen( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + initialPhoneNumber = mobileNumber, + onNumberUpdated = { _, fullPhone, valid -> + if (valid) { + mobileNumber = fullPhone + } + isValidMobileNumber = valid + validateInfo() + }, + ) + } + } + + Spacer(modifier = Modifier.weight(1f)) + Text( modifier = Modifier.padding(start = 20.dp, top = 20.dp), text = stringResource(id = R.string.feature_send_money_select_transfer_method), style = styleNormal18sp, ) - Column(modifier = Modifier.padding(16.dp)) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = - Modifier - .fillMaxWidth() - .padding(top = 20.dp, bottom = 20.dp), - ) { - VpaMobileChip( - label = stringResource(id = R.string.feature_send_money_vpa), - selected = sendMethodType == SendMethodType.VPA, - onClick = { sendMethodType = SendMethodType.VPA }, - ) - Spacer(modifier = Modifier.width(8.dp)) - VpaMobileChip( - label = stringResource(id = R.string.feature_send_money_mobile), - selected = sendMethodType == SendMethodType.MOBILE, - onClick = { sendMethodType = SendMethodType.MOBILE }, - ) - } - MfOutlinedTextField( - value = amount, - label = stringResource(id = R.string.feature_send_money_amount), - onValueChange = { - amount = it - validateInfo() - }, - modifier = Modifier.fillMaxWidth(), - singleLine = true, - keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number), + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + VpaMobileChip( + label = stringResource(id = R.string.feature_send_money_vpa), + selected = sendMethodType == SendMethodType.VPA, + onClick = { sendMethodType = SendMethodType.VPA }, ) - when (sendMethodType) { - SendMethodType.VPA -> { - MfOutlinedTextField( - value = vpa, - label = stringResource(id = R.string.feature_send_money_virtual_payment_address), - onValueChange = { - vpa = it - validateInfo() - }, - modifier = Modifier.fillMaxWidth(), - trailingIcon = { - IconButton( - onClick = { - startScan() - }, - ) { - Icon( - imageVector = MifosIcons.QrCode2, - contentDescription = "Scan QR", - tint = MaterialTheme.colorScheme.primary, - ) - } - }, - ) - } + Spacer(modifier = Modifier.width(8.dp)) + VpaMobileChip( + label = stringResource(id = R.string.feature_send_money_mobile), + selected = sendMethodType == SendMethodType.MOBILE, + onClick = { sendMethodType = SendMethodType.MOBILE }, + ) + } - SendMethodType.MOBILE -> { - EnterPhoneScreen( - modifier = - Modifier - .fillMaxWidth() - .padding(bottom = 8.dp), - initialPhoneNumber = mobileNumber, - onNumberUpdated = { _, fullPhone, valid -> - if (valid) { - mobileNumber = fullPhone - } - isValidMobileNumber = valid - validateInfo() - }, - ) - } - } - Spacer(modifier = Modifier.height(16.dp)) - MifosButton( - modifier = - Modifier - .fillMaxWidth() - .padding(top = 16.dp) - .align(Alignment.CenterHorizontally), - color = MaterialTheme.colorScheme.onSurface, - enabled = isValidInfo, - onClick = { - if (!isValidInfo) return@MifosButton - onSubmit( - amount, - when (sendMethodType) { - SendMethodType.VPA -> vpa - SendMethodType.MOBILE -> mobileNumber - }, - sendMethodType, - ) - // TODO: Navigate to MakeTransferScreenRoute - }, - contentPadding = PaddingValues(12.dp), - ) { - Text( - stringResource(id = R.string.feature_send_money_submit), - style = styleMedium16sp.copy(color = MaterialTheme.colorScheme.surface), + MifosButton( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + color = MaterialTheme.colorScheme.primary, + enabled = isValidInfo, + onClick = { + if (!isValidInfo) return@MifosButton + onSubmit( + amount, + when (sendMethodType) { + SendMethodType.VPA -> vpa + SendMethodType.MOBILE -> mobileNumber + }, + sendMethodType, ) - } + // TODO: Navigate to MakeTransferScreenRoute + }, + contentPadding = PaddingValues(18.dp), + ) { + Text( + stringResource(id = R.string.feature_send_money_submit), + style = styleMedium16sp.copy(color = MaterialTheme.colorScheme.surface), + ) } } @@ -315,9 +334,8 @@ private fun EnterPhoneScreen( initialPhoneNumber: String? = null, ) { val keyboardController = LocalSoftwareKeyboardController.current - CountryCodePicker( + CountryCodePickerPayment( modifier = modifier, - shape = RoundedCornerShape(8.dp), colors = OutlinedTextFieldDefaults.colors( focusedBorderColor = MaterialTheme.colorScheme.primary, ), @@ -325,8 +343,15 @@ private fun EnterPhoneScreen( onValueChange = { (code, phone), isValid -> onNumberUpdated(phone, code + phone, isValid) }, - label = { Text(stringResource(id = R.string.feature_send_money_phone_number)) }, + label = { + Text( + stringResource(id = R.string.feature_send_money_phone_number), + color = MaterialTheme.colorScheme.primary, + ) + }, keyboardActions = KeyboardActions { keyboardController?.hide() }, + indicatorColor = MaterialTheme.colorScheme.primary, + errorIndicatorColor = MaterialTheme.colorScheme.error, ) } @@ -339,14 +364,26 @@ private fun VpaMobileChip( ) { MifosButton( onClick = onClick, - color = if (selected) MaterialTheme.colorScheme.primary else Color.LightGray, + color = MaterialTheme.colorScheme.onPrimary, modifier = modifier + .wrapContentSize() .padding(4.dp) - .wrapContentSize(), + .then( + if (selected) { + Modifier.border( + width = 1.dp, + color = MaterialTheme.colorScheme.primary, + shape = RoundedCornerShape(25.dp), + ) + } else { + Modifier + }, + ), ) { Text( modifier = Modifier.padding(top = 4.dp, bottom = 4.dp), + color = MaterialTheme.colorScheme.onSurface, text = label, ) } diff --git a/feature/send-money/src/main/res/values/strings.xml b/feature/send-money/src/main/res/values/strings.xml index c249059b6..9d0e56b0f 100644 --- a/feature/send-money/src/main/res/values/strings.xml +++ b/feature/send-money/src/main/res/values/strings.xml @@ -13,12 +13,12 @@ Error fetching balance Self Account transfer is not allowed Send - Select transfer method + Select Method VPA Mobile number - Amount + Enter your Amount Virtual Payment Address - Submit + Proceed Please wait… - Phone Number + Enter Mobile Number \ No newline at end of file diff --git a/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionScreen.kt b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionScreen.kt index 006193666..c823e32e8 100644 --- a/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionScreen.kt +++ b/feature/standing-instruction/src/main/kotlin/org/mifospay/feature/standing/instruction/StandingInstructionScreen.kt @@ -57,7 +57,7 @@ internal fun StandingInstructionScreen( ) { val floatingActionButtonContent = FloatingActionButtonContent( onClick = onNewSI, - contentColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary, content = { Icon( imageVector = MifosIcons.Add, @@ -70,8 +70,7 @@ internal fun StandingInstructionScreen( backPress = onBackPress, floatingActionButtonContent = floatingActionButtonContent, modifier = modifier, - scaffoldContent = { - }, + scaffoldContent = {}, ) { when (standingInstructionsUiState) { StandingInstructionsUiState.Empty -> { diff --git a/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/CountryCodePicker.kt b/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/CountryCodePicker.kt index 86da45a5d..e5072349c 100644 --- a/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/CountryCodePicker.kt +++ b/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/CountryCodePicker.kt @@ -15,12 +15,19 @@ import androidx.compose.foundation.focusable import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle @@ -37,6 +44,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.autofill.AutofillType @@ -279,6 +287,246 @@ fun CountryCodePicker( ) } +@OptIn(ExperimentalComposeUiApi::class) +@Suppress("LongMethod", "LongParameterList", "ComplexMethod") +@Composable +fun CountryCodePickerPayment( + onValueChange: (Pair, Boolean) -> Unit, + modifier: Modifier = Modifier, + autoDetectCode: Boolean = false, + enabled: Boolean = true, + showCountryCode: Boolean = true, + showCountryFlag: Boolean = true, + colors: TextFieldColors = OutlinedTextFieldDefaults.colors(), + fallbackCountry: CountryData = CountryData.UnitedStates, + showPlaceholder: Boolean = true, + includeOnly: ImmutableSet? = null, + clearIcon: ImageVector? = Icons.Filled.Clear, + initialPhoneNumber: String? = null, + initialCountryIsoCode: Iso31661alpha2? = null, + initialCountryPhoneCode: PhoneCode? = null, + label: @Composable (() -> Unit)? = null, + textStyle: TextStyle = LocalTextStyle.current, + keyboardOptions: KeyboardOptions? = null, + keyboardActions: KeyboardActions? = null, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + showError: Boolean = true, + indicatorColor: Color? = null, + errorIndicatorColor: Color? = null, +) { + val context = LocalContext.current + val focusRequester = remember { FocusRequester() } + + val countryCode = autoDetectedCountryCode( + autoDetectCode = autoDetectCode, + initialPhoneNumber = initialPhoneNumber, + ) + + val phoneNumberWithoutCode = if (countryCode != null) { + initialPhoneNumber?.replace(countryCode, "") + } else { + initialPhoneNumber + } + + var phoneNumber by remember { + mutableStateOf( + TextFieldValue( + text = phoneNumberWithoutCode.orEmpty(), + selection = TextRange(phoneNumberWithoutCode?.length ?: 0), + ), + ) + } + val keyboardController = LocalSoftwareKeyboardController.current + + var country: CountryData by rememberSaveable( + context, + countryCode, + initialCountryPhoneCode, + initialCountryIsoCode, + ) { + mutableStateOf( + configureInitialCountry( + initialCountryPhoneCode = countryCode ?: initialCountryPhoneCode, + context = context, + initialCountryIsoCode = initialCountryIsoCode, + fallbackCountry = fallbackCountry, + ), + ) + } + + val phoneNumberTransformation = remember(country) { + PhoneNumberTransformation(country.countryIso, context) + } + val validatePhoneNumber = remember(context) { ValidatePhoneNumber(context) } + + var isNumberValid: Boolean by rememberSaveable(country, phoneNumber) { + mutableStateOf( + validatePhoneNumber( + fullPhoneNumber = country.countryPhoneCode + phoneNumber.text, + ), + ) + } + + val coroutineScope = rememberCoroutineScope() + BasicTextField( + value = phoneNumber, + onValueChange = { enteredPhoneNumber -> + val preFilteredPhoneNumber = phoneNumberTransformation.preFilter(enteredPhoneNumber) + phoneNumber = TextFieldValue( + text = preFilteredPhoneNumber, + selection = TextRange(preFilteredPhoneNumber.length), + ) + isNumberValid = validatePhoneNumber( + fullPhoneNumber = country.countryPhoneCode + phoneNumber.text, + ) + onValueChange(country.countryPhoneCode to phoneNumber.text, isNumberValid) + }, + modifier = modifier + .fillMaxWidth() + .focusable() + .autofill( + autofillTypes = listOf(AutofillType.PhoneNumberNational), + onFill = { filledPhoneNumber -> + val preFilteredPhoneNumber = + phoneNumberTransformation.preFilter(filledPhoneNumber) + phoneNumber = TextFieldValue( + text = preFilteredPhoneNumber, + selection = TextRange(preFilteredPhoneNumber.length), + ) + isNumberValid = validatePhoneNumber( + fullPhoneNumber = country.countryPhoneCode + phoneNumber.text, + ) + onValueChange(country.countryPhoneCode to phoneNumber.text, isNumberValid) + keyboardController?.hide() + coroutineScope.launch { + focusRequester.safeFreeFocus() + } + }, + focusRequester = focusRequester, + ) + .focusRequester(focusRequester = focusRequester), + enabled = enabled, + textStyle = textStyle, + decorationBox = { innerTextField -> + Column { + if (label != null) { + label() + } + Spacer(modifier = Modifier.height(5.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + CountryCodeDialogWrapper( + country = country, + onCountryChange = { countryData -> + country = countryData + isNumberValid = validatePhoneNumber( + fullPhoneNumber = country.countryPhoneCode + phoneNumber.text, + ) + onValueChange( + country.countryPhoneCode to phoneNumber.text, + isNumberValid, + ) + }, + includeOnly = includeOnly, + showCountryCode = showCountryCode, + showFlag = showCountryFlag, + textStyle = textStyle, + ) + + Box(modifier = Modifier.weight(1f)) { + innerTextField() + if (showPlaceholder && phoneNumber.text.isEmpty()) { + PlaceholderNumberHint(country.countryIso) + } + } + if (clearIcon != null) { + ClearIconButtonWrapper( + clearIcon = clearIcon, + colors = colors, + isNumberValid = !showError || isNumberValid, + onClearClick = { + phoneNumber = TextFieldValue("") + isNumberValid = false + onValueChange( + country.countryPhoneCode to phoneNumber.text, + isNumberValid, + ) + }, + ) + } + } + if (showError && !isNumberValid) { + errorIndicatorColor?.let { + HorizontalDivider( + thickness = 1.dp, + color = it, + ) + } + } else { + indicatorColor?.let { + HorizontalDivider( + thickness = 1.dp, + color = it, + ) + } + } + } + }, + interactionSource = interactionSource, + visualTransformation = phoneNumberTransformation, + keyboardOptions = keyboardOptions ?: KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Phone, + autoCorrect = true, + imeAction = ImeAction.Done, + ), + keyboardActions = keyboardActions ?: KeyboardActions( + onDone = { + keyboardController?.hide() + coroutineScope.launch { + focusRequester.safeFreeFocus() + } + }, + ), + singleLine = true, + ) +} + +@Composable +fun CountryCodeDialogWrapper( + country: CountryData, + onCountryChange: (CountryData) -> Unit, + includeOnly: ImmutableSet?, + showCountryCode: Boolean, + showFlag: Boolean, + textStyle: TextStyle, +) { + CountryCodeDialog( + selectedCountry = country, + includeOnly = includeOnly, + onCountryChange = onCountryChange, + showCountryCode = showCountryCode, + showFlag = showFlag, + textStyle = textStyle, + ) +} + +@Composable +fun ClearIconButtonWrapper( + clearIcon: ImageVector, + colors: TextFieldColors, + isNumberValid: Boolean, + onClearClick: () -> Unit, +) { + ClearIconButton( + imageVector = clearIcon, + colors = colors, + isNumberValid = isNumberValid, + onClick = onClearClick, + ) +} + private fun configureInitialCountry( initialCountryPhoneCode: PhoneCode?, context: Context, diff --git a/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/transformation/PhoneNumberTransformation.kt b/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/transformation/PhoneNumberTransformation.kt index 9af61b140..10bed7f68 100644 --- a/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/transformation/PhoneNumberTransformation.kt +++ b/libs/country-code-picker/src/main/kotlin/com/mifos/library/countrycodepicker/transformation/PhoneNumberTransformation.kt @@ -53,6 +53,9 @@ class PhoneNumberTransformation(countryCode: String, context: Context) : VisualT @Suppress("AvoidMutableCollections", "AvoidVarsExceptWithDelegate") private fun reformat(s: CharSequence, cursor: Int): Transformation { + if (s.isEmpty()) { + return Transformation("", listOf(0), listOf(0)) + } phoneNumberFormatter.clear() val curIndex = cursor - 1 @@ -79,17 +82,24 @@ class PhoneNumberTransformation(countryCode: String, context: Context) : VisualT val originalToTransformed = mutableListOf() val transformedToOriginal = mutableListOf() var specialCharsCount = 0 - formatted?.forEachIndexed { index, char -> - if (!PhoneNumberUtils.isNonSeparator(char)) { - specialCharsCount++ - } else { - originalToTransformed.add(index) + if (formatted != null) { + formatted?.forEachIndexed { index, char -> + if (!PhoneNumberUtils.isNonSeparator(char)) { + specialCharsCount++ + } else { + originalToTransformed.add(index) + } + transformedToOriginal.add(index - specialCharsCount) } - transformedToOriginal.add(index - specialCharsCount) + originalToTransformed.add(originalToTransformed.maxOrNull()?.plus(1) ?: 0) + transformedToOriginal.add(transformedToOriginal.maxOrNull()?.plus(1) ?: 0) + } else { + originalToTransformed.add(0) + transformedToOriginal.add(0) + } + if (transformedToOriginal.any { it < 0 }) { + transformedToOriginal.replaceAll { if (it < 0) 0 else it } } - originalToTransformed.add(originalToTransformed.maxOrNull()?.plus(1) ?: 0) - transformedToOriginal.add(transformedToOriginal.maxOrNull()?.plus(1) ?: 0) - return Transformation(formatted, originalToTransformed, transformedToOriginal) } diff --git a/mifospay/prodRelease-badging.txt b/mifospay/prodRelease-badging.txt index 23d6cb8d7..b46890558 100644 --- a/mifospay/prodRelease-badging.txt +++ b/mifospay/prodRelease-badging.txt @@ -1,4 +1,4 @@ -package: name='org.mifospay' versionCode='1' versionName='0.0.4-beta.0.6' platformBuildVersionName='14' platformBuildVersionCode='34' compileSdkVersion='34' compileSdkVersionCodename='14' +package: name='org.mifospay' versionCode='1' versionName='0.0.1-beta.0.836' platformBuildVersionName='14' platformBuildVersionCode='34' compileSdkVersion='34' compileSdkVersionCodename='14' sdkVersion:'26' targetSdkVersion:'34' uses-permission: name='android.permission.INTERNET'