Skip to content

Commit 8f19f14

Browse files
committed
feat(feature:autopay): implement add biller screen
1 parent 49ba5ec commit 8f19f14

File tree

20 files changed

+1114
-0
lines changed

20 files changed

+1114
-0
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import org.mifospay.feature.accounts.savingsaccount.details.savingAccountDetailR
2424
import org.mifospay.feature.accounts.savingsaccount.navigateToSavingAccountAddEdit
2525
import org.mifospay.feature.autopay.AutoPayScreen
2626
import org.mifospay.feature.autopay.autoPayGraph
27+
import org.mifospay.feature.autopay.navigateToAddBiller
2728
import org.mifospay.feature.autopay.navigateToAutoPay
2829
import org.mifospay.feature.autopay.navigateToAutoPayHistory
2930
import org.mifospay.feature.autopay.navigateToAutoPayPreferences
@@ -152,6 +153,9 @@ internal fun MifosNavHost(
152153
onNavigateToScheduleDetails = { scheduleId ->
153154
navController.navigateToAutoPayScheduleDetails(scheduleId)
154155
},
156+
onNavigateToAddBiller = {
157+
navController.navigateToAddBiller()
158+
},
155159
showTopBar = false,
156160
)
157161
},

core/data/src/commonMain/kotlin/org/mifospay/core/data/di/RepositoryModule.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import org.mifospay.core.data.repository.AssetRepository
1818
import org.mifospay.core.data.repository.AuthenticationRepository
1919
import org.mifospay.core.data.repository.AutoPayRepository
2020
import org.mifospay.core.data.repository.BeneficiaryRepository
21+
import org.mifospay.core.data.repository.BillerRepository
2122
import org.mifospay.core.data.repository.ClientRepository
2223
import org.mifospay.core.data.repository.DocumentRepository
2324
import org.mifospay.core.data.repository.InvoiceRepository
@@ -39,6 +40,7 @@ import org.mifospay.core.data.repositoryImpl.AssetRepositoryImpl
3940
import org.mifospay.core.data.repositoryImpl.AuthenticationRepositoryImpl
4041
import org.mifospay.core.data.repositoryImpl.AutoPayRepositoryImpl
4142
import org.mifospay.core.data.repositoryImpl.BeneficiaryRepositoryImpl
43+
import org.mifospay.core.data.repositoryImpl.BillerRepositoryImpl
4244
import org.mifospay.core.data.repositoryImpl.ClientRepositoryImpl
4345
import org.mifospay.core.data.repositoryImpl.DocumentRepositoryImpl
4446
import org.mifospay.core.data.repositoryImpl.InvoiceRepositoryImpl
@@ -97,6 +99,13 @@ val RepositoryModule = module {
9799
single<UserRepository> { UserRepositoryImpl(get(), get(ioDispatcher)) }
98100
single<AutoPayRepository> { AutoPayRepositoryImpl(get(), get(ioDispatcher)) }
99101

102+
// TODO: Switch to network-based implementation when APIs are finalized
103+
// or use hybrid approach syncing local and remote data
104+
// single<BillerRepository> { BillerRepositoryImpl(get(), get(ioDispatcher)) }
105+
106+
// Current local storage implementation
107+
single<BillerRepository> { BillerRepositoryImpl(get(), get(ioDispatcher)) }
108+
100109
includes(platformModule)
101110
single<PlatformDependentDataModule> { getPlatformDataModule }
102111
single<NetworkMonitor> { getPlatformDataModule.networkMonitor }
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.core.data.repository
11+
12+
import kotlinx.coroutines.flow.Flow
13+
import org.mifospay.core.common.DataState
14+
import org.mifospay.core.model.autopay.Biller
15+
import org.mifospay.core.model.autopay.BillerCategory
16+
17+
interface BillerRepository {
18+
/**
19+
* Get all saved billers
20+
*/
21+
fun getAllBillers(): Flow<List<Biller>>
22+
23+
/**
24+
* Get biller by ID
25+
*/
26+
suspend fun getBillerById(id: String): Biller?
27+
28+
/**
29+
* Save a new biller
30+
*/
31+
suspend fun saveBiller(biller: Biller): DataState<Biller>
32+
33+
/**
34+
* Update an existing biller
35+
*/
36+
suspend fun updateBiller(biller: Biller): DataState<Biller>
37+
38+
/**
39+
* Delete a biller
40+
*/
41+
suspend fun deleteBiller(id: String): DataState<Unit>
42+
43+
/**
44+
* Get billers by category
45+
*/
46+
suspend fun getBillersByCategory(category: BillerCategory): List<Biller>
47+
48+
/**
49+
* Search billers by name
50+
*/
51+
suspend fun searchBillersByName(query: String): List<Biller>
52+
53+
/**
54+
* Check if biller exists by name and account number
55+
*/
56+
suspend fun isBillerExists(name: String, accountNumber: String): Boolean
57+
58+
/**
59+
* Clear all billers
60+
*/
61+
suspend fun clearAllBillers(): DataState<Unit>
62+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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.core.data.repositoryImpl
11+
12+
import kotlinx.coroutines.CoroutineDispatcher
13+
import kotlinx.coroutines.flow.Flow
14+
import kotlinx.coroutines.flow.catch
15+
import kotlinx.coroutines.flow.first
16+
import kotlinx.coroutines.flow.flowOn
17+
import kotlinx.coroutines.withContext
18+
import org.mifospay.core.common.DataState
19+
import org.mifospay.core.data.repository.BillerRepository
20+
import org.mifospay.core.model.autopay.Biller
21+
import org.mifospay.core.model.autopay.BillerCategory
22+
import org.mifospay.core.network.FineractApiManager
23+
24+
/**
25+
* Network-based implementation of BillerRepository.
26+
*
27+
* TODO: This implementation uses placeholder API endpoints. When the backend APIs
28+
* for biller management are finalized, update the endpoints and request/response
29+
* models according to the actual API contract.
30+
*/
31+
class BillerRepositoryImpl(
32+
private val apiManager: FineractApiManager,
33+
private val ioDispatcher: CoroutineDispatcher,
34+
) : BillerRepository {
35+
36+
override fun getAllBillers(): Flow<List<Biller>> {
37+
return apiManager.billerApi
38+
.getAllBillers()
39+
.catch {
40+
// Return empty list on error for now
41+
emit(emptyList())
42+
}
43+
.flowOn(ioDispatcher)
44+
}
45+
46+
override suspend fun getBillerById(id: String): Biller? {
47+
return try {
48+
withContext(ioDispatcher) {
49+
apiManager.billerApi.getBillerById(id).first()
50+
}
51+
} catch (e: Exception) {
52+
null
53+
}
54+
}
55+
56+
override suspend fun saveBiller(biller: Biller): DataState<Biller> {
57+
return try {
58+
val result = withContext(ioDispatcher) {
59+
apiManager.billerApi.createBiller(biller).first()
60+
}
61+
DataState.Success(result)
62+
} catch (e: Exception) {
63+
DataState.Error(e)
64+
}
65+
}
66+
67+
override suspend fun updateBiller(biller: Biller): DataState<Biller> {
68+
return try {
69+
val billerId = biller.id ?: return DataState.Error(Exception("Biller ID is required for update"))
70+
val result = withContext(ioDispatcher) {
71+
apiManager.billerApi.updateBiller(billerId, biller).first()
72+
}
73+
DataState.Success(result)
74+
} catch (e: Exception) {
75+
DataState.Error(e)
76+
}
77+
}
78+
79+
override suspend fun deleteBiller(id: String): DataState<Unit> {
80+
return try {
81+
withContext(ioDispatcher) {
82+
apiManager.billerApi.deleteBiller(id).first()
83+
}
84+
DataState.Success(Unit)
85+
} catch (e: Exception) {
86+
DataState.Error(e)
87+
}
88+
}
89+
90+
override suspend fun getBillersByCategory(category: BillerCategory): List<Biller> {
91+
return try {
92+
withContext(ioDispatcher) {
93+
apiManager.billerApi.getBillersByCategory(category).first()
94+
}
95+
} catch (e: Exception) {
96+
emptyList()
97+
}
98+
}
99+
100+
override suspend fun searchBillersByName(query: String): List<Biller> {
101+
return try {
102+
withContext(ioDispatcher) {
103+
apiManager.billerApi.searchBillersByName(query).first()
104+
}
105+
} catch (e: Exception) {
106+
emptyList()
107+
}
108+
}
109+
110+
override suspend fun isBillerExists(name: String, accountNumber: String): Boolean {
111+
return try {
112+
withContext(ioDispatcher) {
113+
apiManager.billerApi.isBillerExists(name, accountNumber).first()
114+
}
115+
} catch (e: Exception) {
116+
false
117+
}
118+
}
119+
120+
override suspend fun clearAllBillers(): DataState<Unit> {
121+
return try {
122+
withContext(ioDispatcher) {
123+
apiManager.billerApi.clearAllBillers().first()
124+
}
125+
DataState.Success(Unit)
126+
} catch (e: Exception) {
127+
DataState.Error(e)
128+
}
129+
}
130+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
@file:OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
11+
12+
package org.mifospay.core.datastore
13+
14+
import com.russhwolf.settings.ExperimentalSettingsApi
15+
import com.russhwolf.settings.Settings
16+
import com.russhwolf.settings.serialization.decodeValue
17+
import com.russhwolf.settings.serialization.encodeValue
18+
import kotlinx.coroutines.CoroutineDispatcher
19+
import kotlinx.coroutines.flow.Flow
20+
import kotlinx.coroutines.flow.MutableStateFlow
21+
import kotlinx.coroutines.withContext
22+
import kotlinx.serialization.ExperimentalSerializationApi
23+
import kotlinx.serialization.builtins.ListSerializer
24+
import org.mifospay.core.model.autopay.Biller
25+
26+
private const val BILLERS_KEY = "billers"
27+
28+
class BillerDataSource(
29+
private val settings: Settings,
30+
private val dispatcher: CoroutineDispatcher,
31+
) {
32+
private val _billers = MutableStateFlow(
33+
settings.decodeValue(
34+
key = BILLERS_KEY,
35+
serializer = ListSerializer(Biller.serializer()),
36+
defaultValue = emptyList(),
37+
),
38+
)
39+
40+
val billers: Flow<List<Biller>> = _billers
41+
42+
suspend fun updateBillers(billers: List<Biller>) {
43+
withContext(dispatcher) {
44+
settings.putBillers(billers)
45+
_billers.value = billers
46+
}
47+
}
48+
49+
suspend fun addBiller(biller: Biller) {
50+
withContext(dispatcher) {
51+
val currentBillers = _billers.value.toMutableList()
52+
if (!currentBillers.any { it.id == biller.id }) {
53+
currentBillers.add(biller)
54+
settings.putBillers(currentBillers)
55+
_billers.value = currentBillers
56+
}
57+
}
58+
}
59+
60+
suspend fun removeBiller(billerId: String) {
61+
withContext(dispatcher) {
62+
val currentBillers = _billers.value.toMutableList()
63+
currentBillers.removeAll { it.id == billerId }
64+
settings.putBillers(currentBillers)
65+
_billers.value = currentBillers
66+
}
67+
}
68+
69+
suspend fun clearBillers() {
70+
withContext(dispatcher) {
71+
settings.remove(BILLERS_KEY)
72+
_billers.value = emptyList()
73+
}
74+
}
75+
}
76+
77+
private fun Settings.putBillers(billers: List<Biller>) {
78+
encodeValue(
79+
key = BILLERS_KEY,
80+
serializer = ListSerializer(Biller.serializer()),
81+
value = billers,
82+
)
83+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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.core.datastore
11+
12+
import kotlinx.coroutines.flow.Flow
13+
import org.mifospay.core.common.DataState
14+
import org.mifospay.core.model.autopay.Biller
15+
import org.mifospay.core.model.autopay.BillerCategory
16+
17+
interface BillerRepository {
18+
/**
19+
* Get all saved billers
20+
*/
21+
fun getAllBillers(): Flow<List<Biller>>
22+
23+
/**
24+
* Get biller by ID
25+
*/
26+
suspend fun getBillerById(id: String): Biller?
27+
28+
/**
29+
* Save a new biller
30+
*/
31+
suspend fun saveBiller(biller: Biller): DataState<Biller>
32+
33+
/**
34+
* Update an existing biller
35+
*/
36+
suspend fun updateBiller(biller: Biller): DataState<Biller>
37+
38+
/**
39+
* Delete a biller
40+
*/
41+
suspend fun deleteBiller(id: String): DataState<Unit>
42+
43+
/**
44+
* Get billers by category
45+
*/
46+
suspend fun getBillersByCategory(category: BillerCategory): List<Biller>
47+
48+
/**
49+
* Search billers by name
50+
*/
51+
suspend fun searchBillersByName(query: String): List<Biller>
52+
53+
/**
54+
* Check if biller exists by name and account number
55+
*/
56+
suspend fun isBillerExists(name: String, accountNumber: String): Boolean
57+
58+
/**
59+
* Clear all billers
60+
*/
61+
suspend fun clearAllBillers(): DataState<Unit>
62+
}

0 commit comments

Comments
 (0)