Skip to content

Commit f9fb864

Browse files
committed
feat(feature:send-money): add navigation, fix lazycolumn,format phone numbers in contact picker
1 parent 4c9aa64 commit f9fb864

File tree

12 files changed

+356
-28
lines changed

12 files changed

+356
-28
lines changed

cmp-android/prodRelease-badging.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package: name='org.mifospay' versionCode='1' versionName='2025.8.4-beta.0.11' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
1+
package: name='org.mifospay' versionCode='1' versionName='2025.8.4-beta.0.12' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
22
minSdkVersion:'26'
33
targetSdkVersion:'34'
44
uses-permission: name='android.permission.INTERNET'

cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import org.mifospay.feature.savedcards.createOrUpdate.navigateToCardAddEdit
7171
import org.mifospay.feature.savedcards.details.cardDetailRoute
7272
import org.mifospay.feature.savedcards.details.navigateToCardDetails
7373
import org.mifospay.feature.send.money.SendMoneyScreen
74+
import org.mifospay.feature.send.money.navigation.CONTACTS_PICKER_ROUTE
7475
import org.mifospay.feature.send.money.navigation.SEND_MONEY_BASE_ROUTE
7576
import org.mifospay.feature.send.money.navigation.SEND_MONEY_OPTIONS_ROUTE
7677
import org.mifospay.feature.send.money.navigation.contactsPickerScreen
@@ -341,7 +342,12 @@ internal fun MifosNavHost(
341342
onBackClick = navController::popBackStack,
342343
onContactSelected = { contact ->
343344
// Navigate back to Pay Anyone screen with selected contact
344-
navController.popBackStack()
345+
navController.navigateToPayAnyoneScreen(
346+
selectedContact = contact,
347+
navOptions = navOptions {
348+
popUpTo(CONTACTS_PICKER_ROUTE) { inclusive = true }
349+
},
350+
)
345351
},
346352
)
347353

feature/send-money/src/androidMain/kotlin/org/mifospay/feature/send/money/ContactRepository.android.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ class AndroidContactRepository(
2626
) : ContactRepository {
2727

2828
override suspend fun getContacts(): List<Contact> {
29-
return queryContacts(null)
29+
val rawContacts = queryContacts(null)
30+
return PhoneNumberUtils.filterAndFormatContacts(rawContacts)
3031
}
3132

3233
override suspend fun searchContacts(query: String): List<Contact> {
3334
val selection = "${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} LIKE ? OR ${ContactsContract.CommonDataKinds.Phone.NUMBER} LIKE ?"
3435
val selectionArgs = arrayOf("%$query%", "%$query%")
35-
return queryContacts(selection, selectionArgs)
36+
val rawContacts = queryContacts(selection, selectionArgs)
37+
return PhoneNumberUtils.filterAndFormatContacts(rawContacts)
3638
}
3739

3840
private fun queryContacts(selection: String? = null, selectionArgs: Array<String>? = null): List<Contact> {

feature/send-money/src/commonMain/kotlin/org/mifospay/feature/send/money/ContactsPickerScreen.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import androidx.compose.ui.text.input.ImeAction
4141
import androidx.compose.ui.text.input.KeyboardType
4242
import androidx.compose.ui.text.style.TextAlign
4343
import androidx.compose.ui.unit.dp
44+
import kotlinx.serialization.Serializable
4445
import org.jetbrains.compose.ui.tooling.preview.Preview
4546
import org.mifospay.core.designsystem.component.MifosGradientBackground
4647
import org.mifospay.core.designsystem.component.MifosOutlinedTextField
@@ -53,6 +54,7 @@ import template.core.base.designsystem.theme.KptTheme
5354
@Composable
5455
expect fun ContactPermissionHandler()
5556

57+
@Serializable
5658
data class Contact(
5759
val id: String,
5860
val name: String,
@@ -151,7 +153,7 @@ fun ContactsPickerScreen(
151153
Spacer(modifier = Modifier.height(KptTheme.spacing.md))
152154

153155
Text(
154-
text = "All Contacts",
156+
text = "Mobile Contacts",
155157
style = KptTheme.typography.labelLarge,
156158
color = KptTheme.colorScheme.onSurface.copy(alpha = 0.8f),
157159
textAlign = TextAlign.Left,
@@ -177,7 +179,7 @@ fun ContactsPickerScreen(
177179
contentAlignment = Alignment.Center,
178180
) {
179181
Text(
180-
text = if (searchQuery.isEmpty()) "No contacts found" else "No contacts match your search",
182+
text = if (searchQuery.isEmpty()) "No mobile contacts found" else "No mobile contacts match your search",
181183
style = KptTheme.typography.bodyMedium,
182184
color = KptTheme.colorScheme.onSurface.copy(alpha = 0.7f),
183185
)
@@ -189,11 +191,12 @@ fun ContactsPickerScreen(
189191
) {
190192
items(
191193
items = contacts,
192-
key = { it.id },
194+
key = { contact -> "${contact.id}_${contact.phoneNumber}" },
193195
) { contact ->
194196
ContactItem(
195197
contact = contact,
196198
onClick = {
199+
println("ContactsPickerScreen: Contact clicked - ${contact.phoneNumber}")
197200
onContactSelected(contact)
198201
},
199202
)

feature/send-money/src/commonMain/kotlin/org/mifospay/feature/send/money/PayAnyoneScreen.kt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import androidx.compose.material3.IconButtonDefaults
3131
import androidx.compose.material3.Text
3232
import androidx.compose.runtime.Composable
3333
import androidx.compose.runtime.LaunchedEffect
34+
import androidx.compose.runtime.collectAsState
3435
import androidx.compose.runtime.getValue
3536
import androidx.compose.runtime.mutableStateOf
3637
import androidx.compose.runtime.remember
@@ -40,6 +41,7 @@ import androidx.compose.ui.Modifier
4041
import androidx.compose.ui.text.input.KeyboardType
4142
import androidx.compose.ui.text.style.TextAlign
4243
import androidx.compose.ui.unit.dp
44+
import androidx.lifecycle.SavedStateHandle
4345
import kotlinx.coroutines.delay
4446
import org.jetbrains.compose.ui.tooling.preview.Preview
4547
import org.mifospay.core.designsystem.component.MifosGradientBackground
@@ -54,11 +56,12 @@ fun PayAnyoneScreen(
5456
onBackClick: () -> Unit,
5557
onContactPickerClick: () -> Unit,
5658
onContactSelected: (Contact) -> Unit = {},
59+
selectedContact: Contact? = null,
5760
modifier: Modifier = Modifier,
5861
) {
59-
var inputValue by remember { mutableStateOf("") }
60-
var isKeyboardNumeric by remember { mutableStateOf(false) }
61-
var showClearIcon by remember { mutableStateOf(false) }
62+
val viewModel: PayAnyoneViewModel = remember { PayAnyoneViewModel(SavedStateHandle()) }
63+
val state by viewModel.stateFlow.collectAsState()
64+
6265
var currentPlaceholderIndex by remember { mutableStateOf(0) }
6366

6467
val placeholderMessages = listOf(
@@ -68,13 +71,13 @@ fun PayAnyoneScreen(
6871

6972
val currentPlaceholder = placeholderMessages[currentPlaceholderIndex]
7073

71-
val keyboardType = if (isKeyboardNumeric) {
74+
val keyboardType = if (state.isKeyboardNumeric) {
7275
KeyboardType.Number
7376
} else {
7477
KeyboardType.Text
7578
}
7679

77-
val keyboardToggleText = if (isKeyboardNumeric) "ABC" else "123"
80+
val keyboardToggleText = if (state.isKeyboardNumeric) "ABC" else "123"
7881

7982
LaunchedEffect(Unit) {
8083
while (true) {
@@ -83,7 +86,11 @@ fun PayAnyoneScreen(
8386
}
8487
}
8588

86-
LaunchedEffect(onContactSelected) {
89+
LaunchedEffect(selectedContact) {
90+
selectedContact?.let { contact ->
91+
println("PayAnyoneScreen: Selected contact received - ${contact.phoneNumber}")
92+
viewModel.trySendAction(PayAnyoneAction.ContactSelected(contact))
93+
}
8794
}
8895

8996
MifosGradientBackground {
@@ -114,10 +121,9 @@ fun PayAnyoneScreen(
114121
Spacer(modifier = Modifier.height(KptTheme.spacing.lg))
115122

116123
MifosOutlinedTextField(
117-
value = inputValue,
124+
value = state.inputValue,
118125
onValueChange = {
119-
inputValue = it
120-
showClearIcon = it.isNotEmpty()
126+
viewModel.trySendAction(PayAnyoneAction.InputValueChanged(it))
121127
},
122128
label = "",
123129
placeholder = {
@@ -147,7 +153,7 @@ fun PayAnyoneScreen(
147153
verticalAlignment = Alignment.CenterVertically,
148154
) {
149155
AnimatedContent(
150-
targetState = showClearIcon,
156+
targetState = state.showClearIcon,
151157
transitionSpec = {
152158
fadeIn(animationSpec = tween(200)) togetherWith
153159
fadeOut(animationSpec = tween(200))
@@ -156,8 +162,7 @@ fun PayAnyoneScreen(
156162
if (showClear) {
157163
IconButton(
158164
onClick = {
159-
inputValue = ""
160-
showClearIcon = false
165+
viewModel.trySendAction(PayAnyoneAction.ClearInput)
161166
},
162167
colors = IconButtonDefaults.iconButtonColors(
163168
contentColor = KptTheme.colorScheme.onSurface.copy(alpha = 0.6f),
@@ -173,7 +178,7 @@ fun PayAnyoneScreen(
173178
Row {
174179
IconButton(
175180
onClick = {
176-
isKeyboardNumeric = !isKeyboardNumeric
181+
viewModel.trySendAction(PayAnyoneAction.ToggleKeyboardType)
177182
},
178183
colors = IconButtonDefaults.iconButtonColors(
179184
contentColor = KptTheme.colorScheme.primary,
@@ -218,5 +223,6 @@ fun PayAnyoneScreenPreview() {
218223
onBackClick = {},
219224
onContactPickerClick = {},
220225
onContactSelected = {},
226+
selectedContact = null,
221227
)
222228
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2024 Mifos Initiative
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla Public
5+
* License, v. 2.0. If a copy of the MPL was not distributed with this
6+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
7+
*
8+
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
9+
*/
10+
package org.mifospay.feature.send.money
11+
12+
import androidx.lifecycle.SavedStateHandle
13+
import androidx.lifecycle.viewModelScope
14+
import kotlinx.coroutines.flow.launchIn
15+
import kotlinx.coroutines.flow.onEach
16+
import kotlinx.coroutines.flow.update
17+
import kotlinx.serialization.Serializable
18+
import org.mifospay.core.common.getSerialized
19+
import org.mifospay.core.common.setSerialized
20+
import org.mifospay.core.ui.utils.BaseViewModel
21+
22+
class PayAnyoneViewModel(
23+
savedStateHandle: SavedStateHandle,
24+
) : BaseViewModel<PayAnyoneState, PayAnyoneEvent, PayAnyoneAction>(
25+
initialState = savedStateHandle.getSerialized(KEY_STATE) ?: PayAnyoneState(),
26+
) {
27+
28+
companion object {
29+
private const val KEY_STATE = "pay_anyone_state"
30+
}
31+
32+
init {
33+
stateFlow
34+
.onEach { savedStateHandle.setSerialized(key = KEY_STATE, value = it) }
35+
.launchIn(viewModelScope)
36+
}
37+
38+
override fun handleAction(action: PayAnyoneAction) {
39+
when (action) {
40+
is PayAnyoneAction.InputValueChanged -> {
41+
mutableStateFlow.update {
42+
it.copy(
43+
inputValue = action.value,
44+
showClearIcon = action.value.isNotEmpty(),
45+
)
46+
}
47+
}
48+
49+
is PayAnyoneAction.ContactSelected -> {
50+
println("PayAnyoneViewModel: Contact selected - ${action.contact.phoneNumber}")
51+
mutableStateFlow.update {
52+
it.copy(
53+
selectedContact = action.contact,
54+
inputValue = action.contact.phoneNumber,
55+
showClearIcon = true,
56+
)
57+
}
58+
}
59+
60+
PayAnyoneAction.ClearInput -> {
61+
mutableStateFlow.update {
62+
it.copy(
63+
inputValue = "",
64+
showClearIcon = false,
65+
)
66+
}
67+
}
68+
69+
PayAnyoneAction.ToggleKeyboardType -> {
70+
mutableStateFlow.update {
71+
it.copy(
72+
isKeyboardNumeric = !it.isKeyboardNumeric,
73+
)
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
@Serializable
81+
data class PayAnyoneState(
82+
val inputValue: String = "",
83+
val isKeyboardNumeric: Boolean = false,
84+
val showClearIcon: Boolean = false,
85+
val selectedContact: Contact? = null,
86+
)
87+
88+
sealed interface PayAnyoneEvent {
89+
data object NavigateBack : PayAnyoneEvent
90+
data object NavigateToContactPicker : PayAnyoneEvent
91+
}
92+
93+
sealed interface PayAnyoneAction {
94+
data class InputValueChanged(val value: String) : PayAnyoneAction
95+
data class ContactSelected(val contact: Contact) : PayAnyoneAction
96+
data object ClearInput : PayAnyoneAction
97+
data object ToggleKeyboardType : PayAnyoneAction
98+
}

0 commit comments

Comments
 (0)