Skip to content

Commit 4c9aa64

Browse files
committed
feat(feature:send-money): add contact picker screen, contact permission
1 parent daa3a5f commit 4c9aa64

File tree

25 files changed

+781
-13
lines changed

25 files changed

+781
-13
lines changed

cmp-android/dependencies/prodReleaseRuntimeClasspath.tree.txt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,6 +2760,11 @@
27602760
| | | | +--- com.google.firebase:firebase-encoders:16.1.0 -> 17.0.0 (*)
27612761
| | | | \--- com.google.firebase:firebase-encoders-json:17.1.0 -> 18.0.1 (*)
27622762
| | | \--- com.google.mlkit:common:18.9.0 -> 18.11.0 (*)
2763+
| | +--- com.google.accompanist:accompanist-permissions:0.36.0
2764+
| | | +--- androidx.activity:activity-compose:1.9.0 -> 1.10.1 (*)
2765+
| | | +--- androidx.compose.foundation:foundation:1.7.0 -> 1.8.3 (*)
2766+
| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 -> 1.10.2 (*)
2767+
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.1.21 (*)
27632768
| | +--- org.jetbrains.kotlin:kotlin-stdlib:2.1.20 -> 2.1.21 (*)
27642769
| | +--- io.insert-koin:koin-core:4.1.0 (*)
27652770
| | +--- io.insert-koin:koin-annotations:2.1.0 (*)
@@ -2896,11 +2901,7 @@
28962901
| | | +--- androidx.camera:camera-video:1.4.2 (c)
28972902
| | | \--- androidx.camera:camera-view:1.4.2 (c)
28982903
| | +--- androidx.camera:camera-lifecycle:1.4.2 (*)
2899-
| | +--- com.google.accompanist:accompanist-permissions:0.36.0
2900-
| | | +--- androidx.activity:activity-compose:1.9.0 -> 1.10.1 (*)
2901-
| | | +--- androidx.compose.foundation:foundation:1.7.0 -> 1.8.3 (*)
2902-
| | | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4 -> 1.10.2 (*)
2903-
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.9.22 -> 2.1.21 (*)
2904+
| | +--- com.google.accompanist:accompanist-permissions:0.36.0 (*)
29042905
| | +--- com.google.mlkit:barcode-scanning:17.3.0
29052906
| | | +--- com.google.android.gms:play-services-basement:18.4.0 -> 18.5.0 (*)
29062907
| | | +--- com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1

cmp-android/prodRelease-badging.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
package: name='org.mifospay' versionCode='1' versionName='2025.8.2-beta.0.3' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
1+
package: name='org.mifospay' versionCode='1' versionName='2025.8.4-beta.0.11' platformBuildVersionName='15' platformBuildVersionCode='35' compileSdkVersion='35' compileSdkVersionCodename='15'
22
minSdkVersion:'26'
33
targetSdkVersion:'34'
44
uses-permission: name='android.permission.INTERNET'
55
uses-permission: name='android.permission.CAMERA'
66
uses-permission: name='android.permission.READ_EXTERNAL_STORAGE' maxSdkVersion='32'
77
uses-permission: name='android.permission.WRITE_EXTERNAL_STORAGE' maxSdkVersion='32'
88
uses-permission: name='android.permission.VIBRATE'
9+
uses-permission: name='android.permission.READ_CONTACTS'
910
uses-permission: name='android.permission.FLASHLIGHT'
1011
uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
1112
uses-permission: name='android.permission.USE_BIOMETRIC'

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ import org.mifospay.feature.savedcards.details.navigateToCardDetails
7373
import org.mifospay.feature.send.money.SendMoneyScreen
7474
import org.mifospay.feature.send.money.navigation.SEND_MONEY_BASE_ROUTE
7575
import org.mifospay.feature.send.money.navigation.SEND_MONEY_OPTIONS_ROUTE
76+
import org.mifospay.feature.send.money.navigation.contactsPickerScreen
77+
import org.mifospay.feature.send.money.navigation.navigateToContactsPickerScreen
7678
import org.mifospay.feature.send.money.navigation.navigateToPayAnyoneScreen
7779
import org.mifospay.feature.send.money.navigation.navigateToPayeeDetailsScreen
7880
import org.mifospay.feature.send.money.navigation.navigateToSendMoneyOptionsScreen
@@ -325,6 +327,22 @@ internal fun MifosNavHost(
325327

326328
payAnyoneScreen(
327329
onBackClick = navController::popBackStack,
330+
onContactPickerClick = {
331+
navController.navigateToContactsPickerScreen()
332+
},
333+
onContactSelected = { contact ->
334+
// Handle contact selection - this would typically update the input field
335+
// For now, we'll just navigate back
336+
navController.popBackStack()
337+
},
338+
)
339+
340+
contactsPickerScreen(
341+
onBackClick = navController::popBackStack,
342+
onContactSelected = { contact ->
343+
// Navigate back to Pay Anyone screen with selected contact
344+
navController.popBackStack()
345+
},
328346
)
329347

330348
payeeDetailsScreen(

feature/send-money/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ kotlin {
2424
implementation(compose.materialIconsExtended)
2525
implementation(compose.components.resources)
2626
implementation(compose.components.uiToolingPreview)
27+
implementation(projects.core.designsystem)
2728
}
2829

2930
androidMain.dependencies {
3031
implementation(libs.google.play.services.code.scanner)
32+
implementation(libs.accompanist.permissions)
3133
}
3234
}
35+
}
36+
37+
dependencies {
38+
debugImplementation(compose.uiTooling)
3339
}

feature/send-money/src/androidMain/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@
88
99
See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
1010
-->
11-
<manifest />
11+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
12+
<uses-permission android:name="android.permission.READ_CONTACTS" />
13+
</manifest>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.compose.runtime.Composable
13+
import org.mifospay.core.designsystem.component.PermissionBox
14+
15+
@Composable
16+
actual fun ContactPermissionHandler() {
17+
PermissionBox(
18+
title = "Contact Permission Required",
19+
confirmButtonText = "Grant Permission",
20+
dismissButtonText = "Open Settings",
21+
requiredPermissions = listOf("android.permission.READ_CONTACTS"),
22+
description = "To help you select contacts for payments, we need access to your contacts. This allows you to quickly find and select contacts when making payments.",
23+
onGranted = { },
24+
)
25+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 android.content.Context
13+
import android.content.Intent
14+
import android.provider.Settings
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.runtime.remember
17+
import androidx.compose.ui.platform.LocalContext
18+
import androidx.core.net.toUri
19+
import com.google.accompanist.permissions.ExperimentalPermissionsApi
20+
import com.google.accompanist.permissions.PermissionState
21+
import com.google.accompanist.permissions.PermissionStatus
22+
import com.google.accompanist.permissions.rememberPermissionState
23+
24+
@OptIn(ExperimentalPermissionsApi::class)
25+
@Composable
26+
actual fun rememberContactPermissionState(): ContactPermissionState {
27+
val accPermissionState = rememberPermissionState(android.Manifest.permission.READ_CONTACTS)
28+
29+
val context = LocalContext.current
30+
val wrapper = remember(accPermissionState) {
31+
AccompanistContactPermissionWrapper(accPermissionState, context)
32+
}
33+
34+
return wrapper
35+
}
36+
37+
@OptIn(ExperimentalPermissionsApi::class)
38+
class AccompanistContactPermissionWrapper(
39+
private val permissionState: PermissionState,
40+
private val context: Context,
41+
) : ContactPermissionState {
42+
override val status: ContactPermissionStatus
43+
get() = permissionState.status.toContactPermissionStatus()
44+
45+
override fun requestContactPermission() {
46+
permissionState.launchPermissionRequest()
47+
}
48+
49+
override fun goToSettings() {
50+
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
51+
data = "package:${context.packageName}".toUri()
52+
}
53+
context.startActivity(intent)
54+
}
55+
}
56+
57+
@OptIn(ExperimentalPermissionsApi::class)
58+
private fun PermissionStatus.toContactPermissionStatus(): ContactPermissionStatus {
59+
return when (this) {
60+
is PermissionStatus.Granted -> ContactPermissionStatus.Granted
61+
is PermissionStatus.Denied -> ContactPermissionStatus.Denied
62+
}
63+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 android.content.ContentResolver
13+
import android.provider.ContactsContract
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.remember
16+
import androidx.compose.ui.platform.LocalContext
17+
18+
@Composable
19+
actual fun rememberContactRepository(): ContactRepository {
20+
val context = LocalContext.current
21+
return remember { AndroidContactRepository(context.contentResolver) }
22+
}
23+
24+
class AndroidContactRepository(
25+
private val contentResolver: ContentResolver,
26+
) : ContactRepository {
27+
28+
override suspend fun getContacts(): List<Contact> {
29+
return queryContacts(null)
30+
}
31+
32+
override suspend fun searchContacts(query: String): List<Contact> {
33+
val selection = "${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} LIKE ? OR ${ContactsContract.CommonDataKinds.Phone.NUMBER} LIKE ?"
34+
val selectionArgs = arrayOf("%$query%", "%$query%")
35+
return queryContacts(selection, selectionArgs)
36+
}
37+
38+
private fun queryContacts(selection: String? = null, selectionArgs: Array<String>? = null): List<Contact> {
39+
val contacts = mutableListOf<Contact>()
40+
41+
val projection = arrayOf(
42+
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
43+
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
44+
ContactsContract.CommonDataKinds.Phone.NUMBER,
45+
ContactsContract.CommonDataKinds.Phone.TYPE,
46+
)
47+
48+
val sortOrder = "${ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME} ASC"
49+
50+
contentResolver.query(
51+
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
52+
projection,
53+
selection,
54+
selectionArgs,
55+
sortOrder,
56+
)?.use { cursor ->
57+
val idColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID)
58+
val nameColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
59+
val numberColumn = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
60+
61+
while (cursor.moveToNext()) {
62+
val id = cursor.getString(idColumn)
63+
val name = cursor.getString(nameColumn) ?: "Unknown"
64+
val number = cursor.getString(numberColumn) ?: ""
65+
66+
val contact = Contact(
67+
id = id,
68+
name = name,
69+
phoneNumber = number,
70+
upiId = null,
71+
)
72+
73+
if (!contacts.any { it.name == name && it.phoneNumber == number }) {
74+
contacts.add(contact)
75+
}
76+
}
77+
}
78+
79+
return contacts
80+
}
81+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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.compose.runtime.Composable
13+
14+
interface ContactPermissionState {
15+
val status: ContactPermissionStatus
16+
fun requestContactPermission()
17+
fun goToSettings()
18+
}
19+
20+
enum class ContactPermissionStatus {
21+
Denied,
22+
Granted,
23+
}
24+
25+
@Composable
26+
expect fun rememberContactPermissionState(): ContactPermissionState
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.compose.runtime.Composable
13+
14+
interface ContactRepository {
15+
suspend fun getContacts(): List<Contact>
16+
suspend fun searchContacts(query: String): List<Contact>
17+
}
18+
19+
@Composable
20+
expect fun rememberContactRepository(): ContactRepository

0 commit comments

Comments
 (0)