Skip to content

Commit

Permalink
Feat: Migrated KYC Module to KMP (#1798)
Browse files Browse the repository at this point in the history
  • Loading branch information
niyajali authored Oct 25, 2024
1 parent de590f8 commit 7a4c54e
Show file tree
Hide file tree
Showing 47 changed files with 1,808 additions and 1,604 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ that can be used as a dependency in any other wallet based project. It is develo
| :feature:finance | Done ||||||
| :feature:account | Done ||||||
| :feature:invoices | Done ||||||
| :feature:kyc | Not started | | | | | |
| :feature:kyc | Done | | | | | |
| :feature:make-transfer | Not started ||||||
| :feature:merchants | Not started ||||||
| :feature:notification | Not started ||||||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package org.mifospay.core.common

import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.Month
Expand Down Expand Up @@ -174,7 +175,10 @@ object DateHelper {
}

fun getDateAsStringFromLong(timeInMillis: Long): String {
return fullMonthFormat.parse(timeInMillis.toString()).toString()
val instant = Instant.fromEpochMilliseconds(timeInMillis)
.toLocalDateTime(TimeZone.currentSystemDefault())

return instant.format(shortMonthFormat)
}

val currentDate = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ interface DocumentRepository {
fileName: PartData.FileItem,
): Flow<DataState<Unit>>

suspend fun createDocument(
entityType: String,
entityId: Long,
name: String,
description: String,
file: ByteArray,
): DataState<String>

suspend fun downloadDocument(entityType: String, entityId: Int, documentId: Int): Flow<DataState<Document>>

suspend fun deleteDocument(entityType: String, entityId: Int, documentId: Int): Flow<DataState<Unit>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@ package org.mifospay.core.data.repository

import kotlinx.coroutines.flow.Flow
import org.mifospay.core.common.DataState
import org.mifospay.core.network.model.GenericResponse
import org.mifospay.core.network.model.entity.kyc.KYCLevel1Details
import org.mifospay.core.model.kyc.KYCLevel1Details

interface KycLevelRepository {
suspend fun fetchKYCLevel1Details(clientId: Int): Flow<DataState<List<KYCLevel1Details>>>
fun fetchKYCLevel1Details(clientId: Long): Flow<DataState<KYCLevel1Details?>>

suspend fun addKYCLevel1Details(
clientId: Int,
clientId: Long,
kycLevel1Details: KYCLevel1Details,
): Flow<DataState<GenericResponse>>
): DataState<String>

suspend fun updateKYCLevel1Details(
clientId: Int,
clientId: Long,
kycLevel1Details: KYCLevel1Details,
): Flow<DataState<GenericResponse>>
): DataState<String>
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
*/
package org.mifospay.core.data.repositoryImp

import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.content.PartData
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import org.mifospay.core.common.DataState
import org.mifospay.core.common.asDataStateFlow
import org.mifospay.core.data.repository.DocumentRepository
Expand All @@ -23,7 +28,10 @@ class DocumentRepositoryImpl(
private val apiManager: FineractApiManager,
private val ioDispatcher: CoroutineDispatcher,
) : DocumentRepository {
override suspend fun getDocuments(entityType: String, entityId: Int): Flow<DataState<List<Document>>> {
override suspend fun getDocuments(
entityType: String,
entityId: Int,
): Flow<DataState<List<Document>>> {
return apiManager.documentApi
.getDocuments(entityType, entityId)
.asDataStateFlow().flowOn(ioDispatcher)
Expand All @@ -41,6 +49,46 @@ class DocumentRepositoryImpl(
.asDataStateFlow().flowOn(ioDispatcher)
}

override suspend fun createDocument(
entityType: String,
entityId: Long,
name: String,
description: String,
file: ByteArray,
): DataState<String> {
return try {
val formData = MultiPartFormDataContent(
formData {
// File part
append(
"file",
file,
Headers.build {
append(HttpHeaders.ContentType, "multipart/form-data")
append(HttpHeaders.ContentDisposition, "filename=\"$name\"")
},
)

// Name and description fields
append("name", name)
append("description", description)
},
)

withContext(ioDispatcher) {
apiManager.documentApi.createDocumentFile(
entityType = entityType,
entityId = entityId,
file = formData,
)
}

DataState.Success("Document Uploaded Successfully")
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun downloadDocument(
entityType: String,
entityId: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,57 @@ package org.mifospay.core.data.repositoryImp

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import org.mifospay.core.common.DataState
import org.mifospay.core.common.asDataStateFlow
import org.mifospay.core.data.repository.KycLevelRepository
import org.mifospay.core.model.kyc.KYCLevel1Details
import org.mifospay.core.network.FineractApiManager
import org.mifospay.core.network.model.GenericResponse
import org.mifospay.core.network.model.entity.kyc.KYCLevel1Details

class KycLevelRepositoryImpl(
private val apiManager: FineractApiManager,
private val ioDispatcher: CoroutineDispatcher,
) : KycLevelRepository {
override suspend fun fetchKYCLevel1Details(
clientId: Int,
): Flow<DataState<List<KYCLevel1Details>>> {
override fun fetchKYCLevel1Details(
clientId: Long,
): Flow<DataState<KYCLevel1Details?>> {
return apiManager.kycLevel1Api
.fetchKYCLevel1Details(clientId)
.catch { DataState.Error(it, null) }
.map { it.firstOrNull() }
.asDataStateFlow().flowOn(ioDispatcher)
}

override suspend fun addKYCLevel1Details(
clientId: Int,
clientId: Long,
kycLevel1Details: KYCLevel1Details,
): Flow<DataState<GenericResponse>> {
return apiManager.kycLevel1Api
.addKYCLevel1Details(clientId, kycLevel1Details)
.asDataStateFlow().flowOn(ioDispatcher)
): DataState<String> {
return try {
withContext(ioDispatcher) {
apiManager.kycLevel1Api.addKYCLevel1Details(clientId, kycLevel1Details)
}

DataState.Success("KYC Level One details added successfully")
} catch (e: Exception) {
DataState.Error(e)
}
}

override suspend fun updateKYCLevel1Details(
clientId: Int,
clientId: Long,
kycLevel1Details: KYCLevel1Details,
): Flow<DataState<GenericResponse>> {
return apiManager.kycLevel1Api
.updateKYCLevel1Details(clientId, kycLevel1Details)
.asDataStateFlow().flowOn(ioDispatcher)
): DataState<String> {
return try {
withContext(ioDispatcher) {
apiManager.kycLevel1Api.updateKYCLevel1Details(clientId, kycLevel1Details)
}

DataState.Success("KYC Level One details added successfully")
} catch (e: Exception) {
DataState.Error(e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,22 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.filled.ArrowOutward
import androidx.compose.material.icons.filled.AttachMoney
import androidx.compose.material.icons.filled.Badge
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.Camera
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.ChevronLeft
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Description
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.FlashOff
import androidx.compose.material.icons.filled.FlashOn
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Photo
import androidx.compose.material.icons.filled.PhotoLibrary
import androidx.compose.material.icons.filled.QrCode
Expand All @@ -36,6 +40,7 @@ import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.outlined.AccountCircle
import androidx.compose.material.icons.outlined.Cancel
import androidx.compose.material.icons.outlined.DeleteOutline
import androidx.compose.material.icons.outlined.DoneAll
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.Info
Expand Down Expand Up @@ -113,4 +118,9 @@ object MifosIcons {
val QrCode2 = Icons.Filled.QrCode2
val Edit = Icons.Filled.Edit
val Edit2 = Icons.Outlined.Edit
val CalenderMonth = Icons.Filled.CalendarMonth
val OutlinedDoneAll = Icons.Outlined.DoneAll
val Person = Icons.Filled.Person
val Badge = Icons.Filled.Badge
val DataInfo = Icons.Filled.Description
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@
*
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
package org.mifospay.core.network.model.entity.kyc
package org.mifospay.core.model.kyc

import kotlinx.serialization.Serializable
import org.mifospay.core.common.Parcelable
import org.mifospay.core.common.Parcelize

@Serializable
@Parcelize
data class KYCLevel1Details(
val firstName: String? = null,
val lastName: String? = null,
val addressLine1: String? = null,
val addressLine2: String? = null,
val mobileNo: String? = null,
val dob: String? = null,
val currentLevel: String = "",
)
val firstName: String,
val lastName: String,
val addressLine1: String,
val addressLine2: String,
val mobileNo: String,
val dob: String,
val currentLevel: String,
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
*/
package org.mifospay.core.network.services

import de.jensklingenberg.ktorfit.http.Body
import de.jensklingenberg.ktorfit.http.DELETE
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.Multipart
import de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.PUT
import de.jensklingenberg.ktorfit.http.Part
import de.jensklingenberg.ktorfit.http.Path
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.http.content.PartData
import kotlinx.coroutines.flow.Flow
import org.mifospay.core.network.model.entity.noncore.Document
Expand Down Expand Up @@ -46,6 +48,13 @@ interface DocumentService {
@Part typedFile: PartData,
): Flow<Unit>

@POST("{entityType}/{entityId}/" + ApiEndPoints.DOCUMENTS)
suspend fun createDocumentFile(
@Path("entityType") entityType: String,
@Path("entityId") entityId: Long,
@Body file: MultiPartFormDataContent,
): Unit

/**
* This Service is for downloading the Document with EntityType and EntityId and Document Id
* Rest End Point :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,23 @@ import de.jensklingenberg.ktorfit.http.POST
import de.jensklingenberg.ktorfit.http.PUT
import de.jensklingenberg.ktorfit.http.Path
import kotlinx.coroutines.flow.Flow
import org.mifospay.core.network.model.GenericResponse
import org.mifospay.core.network.model.entity.kyc.KYCLevel1Details
import org.mifospay.core.model.kyc.KYCLevel1Details
import org.mifospay.core.network.utils.ApiEndPoints

interface KYCLevel1Service {

@GET(ApiEndPoints.DATATABLES + "/kyc_level1_details/{clientId}")
suspend fun fetchKYCLevel1Details(@Path("clientId") clientId: Int): Flow<List<KYCLevel1Details>>
fun fetchKYCLevel1Details(@Path("clientId") clientId: Long): Flow<List<KYCLevel1Details>>

@POST(ApiEndPoints.DATATABLES + "/kyc_level1_details/{clientId}")
suspend fun addKYCLevel1Details(
@Path("clientId") clientId: Int,
@Path("clientId") clientId: Long,
@Body kycLevel1Details: KYCLevel1Details,
): Flow<GenericResponse>
): Unit

@PUT(ApiEndPoints.DATATABLES + "/kyc_level1_details/{clientId}/")
suspend fun updateKYCLevel1Details(
@Path("clientId") clientId: Int,
@Path("clientId") clientId: Long,
@Body kycLevel1Details: KYCLevel1Details,
): Flow<GenericResponse>
): Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ fun AvatarBox(
imageVector = icon,
contentDescription = "Avatar",
tint = contentColor,
modifier = Modifier.size((size / 2).dp),
)
}
}
27 changes: 15 additions & 12 deletions feature/kyc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@
* See https://github.com/openMF/mobile-wallet/blob/master/LICENSE.md
*/
plugins {
alias(libs.plugins.mifospay.android.feature)
alias(libs.plugins.mifospay.android.library.compose)
alias(libs.plugins.mifospay.cmp.feature)
alias(libs.plugins.kotlin.parcelize)
}

android {
namespace = "org.mifospay.kyc"
namespace = "org.mifospay.feature.kyc"
}

dependencies {
implementation(projects.libs.countryCodePicker)
implementation(projects.libs.pullrefresh)

implementation(libs.sheets.compose.dialogs.core)
implementation(libs.sheets.compose.dialogs.calender)

// TODO:: this should be removed
implementation(libs.squareup.okhttp)
kotlin {
sourceSets {
commonMain.dependencies {
implementation(compose.ui)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.filekit.compose)
implementation(libs.coil.kt.compose)
}
}
}
Empty file removed feature/kyc/consumer-rules.pro
Empty file.
Loading

0 comments on commit 7a4c54e

Please sign in to comment.