Skip to content

Commit c553d95

Browse files
authored
Feat: Migrated SI Module to KMP (#1803)
1 parent 67661de commit c553d95

File tree

48 files changed

+3444
-1595
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3444
-1595
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ that can be used as a dependency in any other wallet based project. It is develo
5151
| :feature:saved-cards | Done ||||||
5252
| :feature:search | Not started ||||||
5353
| :feature:send-money | Not started ||||||
54-
| :feature:standing-instruction | Not started | | | | | |
54+
| :feature:standing-instruction | Done | | | | | |
5555
| :feature:upi-setup | Not started ||||||
5656
| lint | Not started ||||||
5757

core/common/src/commonMain/kotlin/org/mifospay/core/common/DataState.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ sealed class DataState<out T> {
2828
data class Error<T>(
2929
val exception: Throwable,
3030
override val data: T? = null,
31-
) : DataState<T>()
31+
) : DataState<T>() {
32+
val message = exception.message.toString()
33+
}
3234
}
3335

3436
fun <T> Flow<T>.asDataStateFlow(): Flow<DataState<T>> =

core/common/src/commonMain/kotlin/org/mifospay/core/common/DateHelper.kt

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,18 @@
1010
package org.mifospay.core.common
1111

1212
import kotlinx.datetime.Clock
13-
import kotlinx.datetime.DateTimeUnit
1413
import kotlinx.datetime.Instant
1514
import kotlinx.datetime.LocalDate
1615
import kotlinx.datetime.LocalDateTime
1716
import kotlinx.datetime.Month
1817
import kotlinx.datetime.TimeZone
19-
import kotlinx.datetime.atStartOfDayIn
2018
import kotlinx.datetime.format
2119
import kotlinx.datetime.format.FormatStringsInDatetimeFormats
2220
import kotlinx.datetime.format.byUnicodePattern
23-
import kotlinx.datetime.minus
24-
import kotlinx.datetime.toInstant
2521
import kotlinx.datetime.toLocalDateTime
26-
import kotlin.time.Duration.Companion.days
2722

2823
@OptIn(FormatStringsInDatetimeFormats::class)
2924
object DateHelper {
30-
private const val LOG_TAG = "DateHelper"
31-
3225
/*
3326
* This is the full month format for the date picker.
3427
* "dd MM yyyy" is the format of the date picker.
@@ -41,6 +34,8 @@ object DateHelper {
4134
*/
4235
const val SHORT_MONTH = "dd-MM-yyyy"
4336

37+
const val MONTH_FORMAT = "dd MMMM"
38+
4439
private val fullMonthFormat = LocalDateTime.Format {
4540
byUnicodePattern(FULL_MONTH)
4641
}
@@ -87,7 +82,7 @@ object DateHelper {
8782
return finalFormat.format(pickerFormat.parse(dateString))
8883
}
8984

90-
fun getFormatConverter(
85+
private fun getFormatConverter(
9186
currentFormat: String,
9287
requiredFormat: String,
9388
dateString: String,
@@ -98,8 +93,17 @@ object DateHelper {
9893
return pickerFormat.parse(dateString).format(finalFormat)
9994
}
10095

101-
fun formatTransferDate(dateArray: List<Int>, pattern: String = "dd MM yyyy"): String {
102-
val localDate = LocalDate(dateArray[0], Month(dateArray[1]), dateArray[2])
96+
/**
97+
* Gets the date string in the format "dd-MM-yyyy" from an array of integers representing the year, month, and day.
98+
*
99+
* @param dateComponents An array of three integers representing the year, month, and day, e.g. [2024, 11, 10]
100+
* @return The date string in the format "dd-MM-yyyy", e.g. "10-11-2024"
101+
*/
102+
fun formatTransferDate(dateComponents: List<Int>, pattern: String = SHORT_MONTH): String {
103+
require(dateComponents.size == 3) { "dateComponents must have exactly 3 elements" }
104+
val (year, month, day) = dateComponents
105+
106+
val localDate = LocalDate(year, Month(month), day)
103107
return localDate.format(pattern)
104108
}
105109

@@ -120,65 +124,76 @@ object DateHelper {
120124
* @return string representation of the month like Jan or Feb..etc
121125
*/
122126
private fun getMonthName(month: Int): String {
123-
var monthName = ""
124-
when (month) {
125-
1 -> monthName = "Jan"
126-
2 -> monthName = "Feb"
127-
3 -> monthName = "Mar"
128-
4 -> monthName = "Apr"
129-
5 -> monthName = "May"
130-
6 -> monthName = "Jun"
131-
7 -> monthName = "Jul"
132-
8 -> monthName = "Aug"
133-
9 -> monthName = "Sep"
134-
10 -> monthName = "Oct"
135-
11 -> monthName = "Nov"
136-
12 -> monthName = "Dec"
127+
return when (month) {
128+
1 -> "Jan"
129+
2 -> "Feb"
130+
3 -> "Mar"
131+
4 -> "Apr"
132+
5 -> "May"
133+
6 -> "Jun"
134+
7 -> "Jul"
135+
8 -> "Aug"
136+
9 -> "Sep"
137+
10 -> "Oct"
138+
11 -> "Nov"
139+
12 -> "Dec"
140+
else -> throw IllegalArgumentException("Month should be between 1 and 12")
137141
}
138-
return monthName
139142
}
140143

141-
private fun getDateAsLongFromString(dateStr: String, pattern: String): Long {
142-
return try {
143-
// Create a DateTimeFormatter with the given pattern
144-
val formatter = LocalDateTime.Format { byUnicodePattern(pattern) }
145-
146-
// Parse the string to a LocalDateTime
147-
val localDateTime = LocalDateTime.parse(dateStr, formatter)
148-
149-
// Convert LocalDateTime to Instant (assuming the date is in the system's time zone)
150-
val instant = localDateTime.toInstant(TimeZone.currentSystemDefault())
144+
/**
145+
* Input timestamp string in milliseconds
146+
* Example timestamp "1698278400000"
147+
* Output examples: "dd-MM-yyyy" - "14-04-2016"
148+
*/
149+
fun getDateAsStringFromLong(timeInMillis: Long): String {
150+
val instant = Instant.fromEpochMilliseconds(timeInMillis)
151+
.toLocalDateTime(TimeZone.currentSystemDefault())
151152

152-
// Convert Instant to milliseconds since epoch
153-
instant.toEpochMilliseconds()
154-
} catch (e: Exception) {
155-
0L
156-
}
153+
return instant.format(shortMonthFormat)
157154
}
158155

159-
fun getDateAsLongFromList(integersOfDate: List<Int>): Long {
160-
val dateStr = getDateAsString(integersOfDate)
161-
return getDateAsLongFromString(dateStr, FULL_MONTH)
162-
}
156+
/**
157+
* Input timestamp string in milliseconds
158+
* Example timestamp "1698278400000"
159+
* Output examples: "14 April"
160+
*/
161+
fun getMonthAsStringFromLong(timeInMillis: Long): String {
162+
val instant = Instant.fromEpochMilliseconds(timeInMillis)
163+
.toLocalDateTime(TimeZone.currentSystemDefault())
163164

164-
fun subtractWeeks(number: Int): Long {
165-
val now = Clock.System.now()
166-
val subtracted = now.minus(number.times(7).days)
167-
return subtracted.toEpochMilliseconds()
168-
}
165+
val monthName = instant.month.name.lowercase().capitalize()
169166

170-
fun subtractMonths(number: Int): Long {
171-
val now = Clock.System.now()
172-
val currentDate = now.toLocalDateTime(TimeZone.currentSystemDefault()).date
173-
val subtractedDate = currentDate.minus(number, DateTimeUnit.MONTH)
174-
return subtractedDate.atStartOfDayIn(TimeZone.currentSystemDefault()).toEpochMilliseconds()
167+
return "${instant.dayOfMonth} $monthName"
175168
}
176169

177-
fun getDateAsStringFromLong(timeInMillis: Long): String {
178-
val instant = Instant.fromEpochMilliseconds(timeInMillis)
179-
.toLocalDateTime(TimeZone.currentSystemDefault())
170+
/**
171+
* Gets the date string in the format "day month year" from an array of integers representing the day and month.
172+
*
173+
* @param integersOfDate An array of two integers representing the day and month, e.g. [11, 10]
174+
* @return The date string in the format "day month year", e.g. "11 October"
175+
*/
176+
fun getDateMonthString(integersOfDate: List<Int>): String {
177+
require(integersOfDate.size == 2) { "integersOfDate must have exactly 2 elements" }
178+
val (day, month) = integersOfDate
179+
180+
val monthName = when (month) {
181+
1 -> "January"
182+
2 -> "February"
183+
3 -> "March"
184+
4 -> "April"
185+
5 -> "May"
186+
6 -> "June"
187+
7 -> "July"
188+
8 -> "August"
189+
9 -> "September"
190+
10 -> "October"
191+
11 -> "November"
192+
12 -> "December"
193+
else -> throw IllegalArgumentException("Invalid month value: $month")
194+
}
180195

181-
return instant.format(shortMonthFormat)
196+
return "$day $monthName"
182197
}
183198

184199
/**
@@ -324,13 +339,13 @@ object DateHelper {
324339

325340
val currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
326341

327-
/*
342+
/**
328343
* This is the full date format for the date picker.
329344
* "dd MM yyyy" is the format of the date picker.
330345
*/
331346
val formattedFullDate = currentDate.format(fullMonthFormat)
332347

333-
/*
348+
/**
334349
* This is the short date format for the date picker.
335350
* "dd-MM-yyyy" is the format of the date picker.
336351
*/

core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/StandingInstructionRepository.kt

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,32 @@ package org.mifospay.core.data.repository
1111

1212
import kotlinx.coroutines.flow.Flow
1313
import org.mifospay.core.common.DataState
14-
import org.mifospay.core.network.model.GenericResponse
15-
import org.mifospay.core.network.model.entity.Page
16-
import org.mifospay.core.network.model.entity.payload.StandingInstructionPayload
17-
import org.mifospay.core.network.model.entity.standinginstruction.SDIResponse
18-
import org.mifospay.core.network.model.entity.standinginstruction.StandingInstruction
14+
import org.mifospay.core.model.standinginstruction.SITemplate
15+
import org.mifospay.core.model.standinginstruction.SIUpdatePayload
16+
import org.mifospay.core.model.standinginstruction.StandingInstruction
17+
import org.mifospay.core.model.standinginstruction.StandingInstructionPayload
1918

2019
interface StandingInstructionRepository {
21-
suspend fun getAllStandingInstructions(
20+
fun getStandingInstructionTemplate(
21+
fromOfficeId: Long,
22+
fromClientId: Long,
23+
fromAccountType: Long,
24+
): Flow<DataState<SITemplate>>
25+
26+
fun getAllStandingInstructions(
2227
clientId: Long,
23-
): Flow<DataState<Page<StandingInstruction>>>
28+
): Flow<DataState<List<StandingInstruction>>>
2429

25-
suspend fun getStandingInstruction(instructionId: Long): Flow<DataState<StandingInstruction>>
30+
fun getStandingInstruction(instructionId: Long): Flow<DataState<StandingInstruction>>
2631

2732
suspend fun createStandingInstruction(
2833
payload: StandingInstructionPayload,
29-
): Flow<DataState<SDIResponse>>
34+
): DataState<String>
3035

3136
suspend fun updateStandingInstruction(
3237
instructionId: Long,
33-
payload: StandingInstructionPayload,
34-
): Flow<DataState<GenericResponse>>
38+
payload: SIUpdatePayload,
39+
): DataState<String>
3540

36-
suspend fun deleteStandingInstruction(instructionId: Long): Flow<DataState<GenericResponse>>
41+
suspend fun deleteStandingInstruction(instructionId: Long): DataState<String>
3742
}

core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/ClientRepositoryImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ClientRepositoryImpl(
100100
clientId: Long,
101101
accountType: String,
102102
): Flow<DataState<List<Account>>> {
103-
return apiManager.clientsApi
103+
return fineractApiManager.clientsApi
104104
.getAccounts(clientId, accountType)
105105
.map { it.toAccount() }
106106
.asDataStateFlow().flowOn(ioDispatcher)

core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImp/StandingInstructionRepositoryImpl.kt

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,45 @@ package org.mifospay.core.data.repositoryImp
1111

1212
import kotlinx.coroutines.CoroutineDispatcher
1313
import kotlinx.coroutines.flow.Flow
14+
import kotlinx.coroutines.flow.catch
1415
import kotlinx.coroutines.flow.flowOn
16+
import kotlinx.coroutines.flow.map
17+
import kotlinx.coroutines.withContext
1518
import org.mifospay.core.common.DataState
1619
import org.mifospay.core.common.asDataStateFlow
1720
import org.mifospay.core.data.repository.StandingInstructionRepository
21+
import org.mifospay.core.model.standinginstruction.SITemplate
22+
import org.mifospay.core.model.standinginstruction.SIUpdatePayload
23+
import org.mifospay.core.model.standinginstruction.StandingInstruction
24+
import org.mifospay.core.model.standinginstruction.StandingInstructionPayload
1825
import org.mifospay.core.network.FineractApiManager
19-
import org.mifospay.core.network.model.GenericResponse
20-
import org.mifospay.core.network.model.entity.Page
21-
import org.mifospay.core.network.model.entity.payload.StandingInstructionPayload
22-
import org.mifospay.core.network.model.entity.standinginstruction.SDIResponse
23-
import org.mifospay.core.network.model.entity.standinginstruction.StandingInstruction
2426

2527
class StandingInstructionRepositoryImpl(
2628
private val apiManager: FineractApiManager,
2729
private val ioDispatcher: CoroutineDispatcher,
2830
) : StandingInstructionRepository {
29-
override suspend fun getAllStandingInstructions(
31+
override fun getStandingInstructionTemplate(
32+
fromOfficeId: Long,
33+
fromClientId: Long,
34+
fromAccountType: Long,
35+
): Flow<DataState<SITemplate>> {
36+
return apiManager.standingInstructionApi
37+
.getStandingInstructionTemplate(fromOfficeId, fromClientId, fromAccountType)
38+
.catch { DataState.Error(it, null) }
39+
.asDataStateFlow().flowOn(ioDispatcher)
40+
}
41+
42+
override fun getAllStandingInstructions(
3043
clientId: Long,
31-
): Flow<DataState<Page<StandingInstruction>>> {
44+
): Flow<DataState<List<StandingInstruction>>> {
3245
return apiManager.standingInstructionApi
3346
.getAllStandingInstructions(clientId)
47+
.catch { DataState.Error(it, null) }
48+
.map { it.pageItems }
3449
.asDataStateFlow().flowOn(ioDispatcher)
3550
}
3651

37-
override suspend fun getStandingInstruction(
52+
override fun getStandingInstruction(
3853
instructionId: Long,
3954
): Flow<DataState<StandingInstruction>> {
4055
return apiManager.standingInstructionApi
@@ -44,26 +59,51 @@ class StandingInstructionRepositoryImpl(
4459

4560
override suspend fun createStandingInstruction(
4661
payload: StandingInstructionPayload,
47-
): Flow<DataState<SDIResponse>> {
48-
return apiManager.standingInstructionApi
49-
.createStandingInstruction(payload)
50-
.asDataStateFlow().flowOn(ioDispatcher)
62+
): DataState<String> {
63+
return try {
64+
withContext(ioDispatcher) {
65+
apiManager.standingInstructionApi.createStandingInstruction(payload)
66+
}
67+
68+
DataState.Success("Standing Instruction created successfully")
69+
} catch (e: Exception) {
70+
DataState.Error(e, null)
71+
}
5172
}
5273

5374
override suspend fun updateStandingInstruction(
5475
instructionId: Long,
55-
payload: StandingInstructionPayload,
56-
): Flow<DataState<GenericResponse>> {
57-
return apiManager.standingInstructionApi
58-
.updateStandingInstruction(instructionId, payload, "update")
59-
.asDataStateFlow().flowOn(ioDispatcher)
76+
payload: SIUpdatePayload,
77+
): DataState<String> {
78+
return try {
79+
withContext(ioDispatcher) {
80+
apiManager.standingInstructionApi.updateStandingInstruction(
81+
instructionId = instructionId,
82+
payload = payload,
83+
command = "update",
84+
)
85+
}
86+
87+
DataState.Success("Standing Instruction updated successfully")
88+
} catch (e: Exception) {
89+
DataState.Error(e, null)
90+
}
6091
}
6192

6293
override suspend fun deleteStandingInstruction(
6394
instructionId: Long,
64-
): Flow<DataState<GenericResponse>> {
65-
return apiManager.standingInstructionApi
66-
.deleteStandingInstruction(instructionId, "delete")
67-
.asDataStateFlow().flowOn(ioDispatcher)
95+
): DataState<String> {
96+
return try {
97+
withContext(ioDispatcher) {
98+
apiManager.standingInstructionApi.deleteStandingInstruction(
99+
instructionId = instructionId,
100+
command = "delete",
101+
)
102+
}
103+
104+
DataState.Success("Standing Instruction deleted successfully")
105+
} catch (e: Exception) {
106+
DataState.Error(e, null)
107+
}
68108
}
69109
}

0 commit comments

Comments
 (0)