Skip to content

Commit 3135f66

Browse files
authored
Migrated Notification Module to KMP (#1799)
* Feat: Migrated KYC Module to KMP * Feat: Migrated Notification Module to KMP
1 parent 7a4c54e commit 3135f66

File tree

24 files changed

+535
-331
lines changed

24 files changed

+535
-331
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ that can be used as a dependency in any other wallet based project. It is develo
4444
| :feature:kyc | Done ||||||
4545
| :feature:make-transfer | Not started ||||||
4646
| :feature:merchants | Not started ||||||
47-
| :feature:notification | Not started | | | | | |
47+
| :feature:notification | Done | | | | | |
4848
| :feature:qr | Not started ||||||
4949
| :feature:receipt | Not started ||||||
5050
| :feature:request-money | Not started ||||||

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

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,147 @@ object DateHelper {
181181
return instant.format(shortMonthFormat)
182182
}
183183

184+
/**
185+
* Handles the specific format "yyyy-MM-dd HH:mm:ss.SSSSSS"
186+
* For example "2024-09-19 05:41:18.558995"
187+
* Possible outputs depending on current date:
188+
* "Today at 05:41"
189+
* "Tomorrow at 05:41"
190+
*/
191+
fun String.toFormattedDateTime(): String {
192+
// Parse the datetime string
193+
val dateTime = try {
194+
// Split into date and time parts
195+
val (datePart, timePart) = this.split(" ")
196+
// Remove microseconds from time part
197+
val simplifiedTime = timePart.split(".")[0]
198+
// Combine date and simplified time
199+
val isoString = "${datePart}T$simplifiedTime"
200+
// Parse to LocalDateTime
201+
LocalDateTime.parse(isoString)
202+
} catch (e: Exception) {
203+
return this // Return original string if parsing fails
204+
}
205+
206+
val timeZone = TimeZone.currentSystemDefault()
207+
val now = Clock.System.now()
208+
val nowDateTime = now.toLocalDateTime(timeZone)
209+
210+
return when {
211+
// Same year
212+
nowDateTime.year == dateTime.year -> {
213+
when {
214+
// Same month
215+
nowDateTime.monthNumber == dateTime.monthNumber -> {
216+
when {
217+
// Tomorrow
218+
dateTime.dayOfMonth - nowDateTime.dayOfMonth == 1 -> {
219+
"Tomorrow at ${dateTime.format()}"
220+
}
221+
// Today
222+
dateTime.dayOfMonth == nowDateTime.dayOfMonth -> {
223+
"Today at ${dateTime.format()}"
224+
}
225+
// Yesterday
226+
nowDateTime.dayOfMonth - dateTime.dayOfMonth == 1 -> {
227+
"Yesterday at ${dateTime.format()}"
228+
}
229+
// Same month but different day
230+
else -> {
231+
"${
232+
dateTime.month.name.lowercase().capitalize()
233+
} ${dateTime.dayOfMonth}, ${dateTime.format()}"
234+
}
235+
}
236+
}
237+
// Different month, same year
238+
else -> {
239+
"${
240+
dateTime.month.name.lowercase().capitalize()
241+
} ${dateTime.dayOfMonth}, ${dateTime.format()}"
242+
}
243+
}
244+
}
245+
// Different year
246+
else -> {
247+
"${
248+
dateTime.month.name.lowercase().capitalize()
249+
} ${dateTime.dayOfMonth} ${dateTime.year}, ${dateTime.format()}"
250+
}
251+
}
252+
}
253+
254+
/**
255+
* Input timestamp string in milliseconds
256+
* Example timestamp "1698278400000"
257+
* Output examples:
258+
* "Today at 12:00"
259+
* "Tomorrow at 15:30"
260+
*/
261+
fun String.toPrettyDate(): String {
262+
val timestamp = this.toLong()
263+
val instant = Instant.fromEpochMilliseconds(timestamp)
264+
val timeZone = TimeZone.currentSystemDefault()
265+
val nowDateTime = Clock.System.now().toLocalDateTime(timeZone)
266+
val neededDateTime = instant.toLocalDateTime(timeZone)
267+
268+
return when {
269+
// Same year
270+
nowDateTime.year == neededDateTime.year -> {
271+
when {
272+
// Same month
273+
nowDateTime.monthNumber == neededDateTime.monthNumber -> {
274+
when {
275+
// Tomorrow
276+
neededDateTime.dayOfMonth - nowDateTime.dayOfMonth == 1 -> {
277+
val time = neededDateTime.format()
278+
"Tomorrow at $time"
279+
}
280+
// Today
281+
neededDateTime.dayOfMonth == nowDateTime.dayOfMonth -> {
282+
val time = neededDateTime.format()
283+
"Today at $time"
284+
}
285+
// Yesterday
286+
nowDateTime.dayOfMonth - neededDateTime.dayOfMonth == 1 -> {
287+
val time = neededDateTime.format()
288+
"Yesterday at $time"
289+
}
290+
// Same month but different day
291+
else -> {
292+
"${
293+
neededDateTime.month.name.lowercase().capitalize()
294+
} ${neededDateTime.dayOfMonth}, ${neededDateTime.format()}"
295+
}
296+
}
297+
}
298+
// Different month, same year
299+
else -> {
300+
"${
301+
neededDateTime.month.name.lowercase().capitalize()
302+
} ${neededDateTime.dayOfMonth}, ${neededDateTime.format()}"
303+
}
304+
}
305+
}
306+
// Different year
307+
else -> {
308+
"${
309+
neededDateTime.month.name.lowercase().capitalize()
310+
} ${neededDateTime.dayOfMonth} ${neededDateTime.year}, ${neededDateTime.format()}"
311+
}
312+
}
313+
}
314+
315+
// Helper function to format time
316+
private fun LocalDateTime.format(): String {
317+
return "${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}"
318+
}
319+
320+
// Extension to capitalize first letter
321+
private fun String.capitalize() = replaceFirstChar {
322+
if (it.isLowerCase()) it.titlecase() else it.toString()
323+
}
324+
184325
val currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
185326

186327
/*

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ 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.NotificationPayload
14+
import org.mifospay.core.model.notification.Notification
1515

1616
interface NotificationRepository {
17-
suspend fun fetchNotifications(clientId: Long): Flow<DataState<List<NotificationPayload>>>
17+
fun fetchNotifications(): Flow<DataState<List<Notification>>>
1818
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,21 @@ package org.mifospay.core.data.repositoryImp
1212
import kotlinx.coroutines.CoroutineDispatcher
1313
import kotlinx.coroutines.flow.Flow
1414
import kotlinx.coroutines.flow.flowOn
15+
import kotlinx.coroutines.flow.map
1516
import org.mifospay.core.common.DataState
1617
import org.mifospay.core.common.asDataStateFlow
1718
import org.mifospay.core.data.repository.NotificationRepository
19+
import org.mifospay.core.model.notification.Notification
1820
import org.mifospay.core.network.FineractApiManager
19-
import org.mifospay.core.network.model.NotificationPayload
2021

2122
class NotificationRepositoryImpl(
2223
private val apiManager: FineractApiManager,
2324
private val ioDispatcher: CoroutineDispatcher,
2425
) : NotificationRepository {
25-
override suspend fun fetchNotifications(
26-
clientId: Long,
27-
): Flow<DataState<List<NotificationPayload>>> {
26+
override fun fetchNotifications(): Flow<DataState<List<Notification>>> {
2827
return apiManager.notificationApi
29-
.fetchNotifications(clientId)
28+
.fetchNotifications(true)
29+
.map { it.pageItems }
3030
.asDataStateFlow().flowOn(ioDispatcher)
3131
}
3232
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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.model.notification
11+
12+
import kotlinx.serialization.Serializable
13+
import org.mifospay.core.common.DateHelper.toFormattedDateTime
14+
import org.mifospay.core.common.Parcelable
15+
import org.mifospay.core.common.Parcelize
16+
17+
@Serializable
18+
@Parcelize
19+
data class Notification(
20+
val id: Long,
21+
val objectType: String,
22+
val objectId: Long,
23+
val action: String,
24+
val actorId: Long,
25+
val content: String,
26+
val isRead: Boolean,
27+
val isSystemGenerated: Boolean,
28+
val createdAt: String,
29+
) : Parcelable {
30+
val formattedDate = createdAt.toFormattedDateTime()
31+
}

core/network/src/commonMain/kotlin/org/mifospay/core/network/model/NotificationPayload.kt renamed to core/model/src/commonMain/kotlin/org/mifospay/core/model/notification/NotificationPayload.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
*
88
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
99
*/
10-
package org.mifospay.core.network.model
10+
package org.mifospay.core.model.notification
1111

1212
import kotlinx.serialization.Serializable
13+
import org.mifospay.core.common.Parcelable
14+
import org.mifospay.core.common.Parcelize
1315

1416
@Serializable
17+
@Parcelize
1518
data class NotificationPayload(
16-
val title: String? = null,
17-
val body: String? = null,
18-
val timestamp: String? = null,
19-
)
19+
val totalFilteredRecords: Long,
20+
val pageItems: List<Notification>,
21+
) : Parcelable

core/network/src/commonMain/kotlin/org/mifospay/core/network/services/NotificationService.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
package org.mifospay.core.network.services
1111

1212
import de.jensklingenberg.ktorfit.http.GET
13-
import de.jensklingenberg.ktorfit.http.Path
13+
import de.jensklingenberg.ktorfit.http.Query
1414
import kotlinx.coroutines.flow.Flow
15-
import org.mifospay.core.network.model.NotificationPayload
16-
import org.mifospay.core.network.utils.ApiEndPoints
15+
import org.mifospay.core.model.notification.NotificationPayload
1716

1817
interface NotificationService {
19-
@GET(ApiEndPoints.DATATABLES + "/notifications/{clientId}")
20-
suspend fun fetchNotifications(@Path("clientId") clientId: Long): Flow<List<NotificationPayload>>
18+
@GET("notifications/")
19+
fun fetchNotifications(@Query("isRead") isRead: Boolean): Flow<NotificationPayload>
2120
}

feature/notification/build.gradle.kts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,22 @@
88
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
99
*/
1010
plugins {
11-
alias(libs.plugins.mifospay.android.feature)
12-
alias(libs.plugins.mifospay.android.library.compose)
11+
alias(libs.plugins.mifospay.cmp.feature)
12+
alias(libs.plugins.kotlin.parcelize)
1313
}
1414

1515
android {
16-
namespace = "org.mifospay.notification"
16+
namespace = "org.mifospay.feature.notification"
1717
}
1818

19-
dependencies {
20-
implementation(projects.libs.pullrefresh)
19+
kotlin {
20+
sourceSets {
21+
commonMain.dependencies {
22+
implementation(compose.ui)
23+
implementation(compose.foundation)
24+
implementation(compose.material3)
25+
implementation(compose.components.resources)
26+
implementation(compose.components.uiToolingPreview)
27+
}
28+
}
2129
}

feature/notification/consumer-rules.pro

Whitespace-only changes.

feature/notification/proguard-rules.pro

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)