Skip to content

Re Org android sdk 🧱 #180

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Jun 18, 2025
Merged

Re Org android sdk 🧱 #180

merged 22 commits into from
Jun 18, 2025

Conversation

jumaallan
Copy link
Member

@jumaallan jumaallan commented Jun 17, 2025

User description

Story: https://app.shortcut.com/smileid/story/xxx

Summary

We need to deprecate use of onSuccessJson and onError, and replace this with onResult(result: T) which wraps both the success and error responses - and uses proper types and not pass back a string that needs to be encoded again into a type. This will make it easier for partners to implement the sdk without having to worry about any response changes from the sdk side

Known Issues

Any shortcomings in your work. This may include corner cases not correctly handled or issues related
to but not within the scope of your PR. Design compromises should be discussed here if they were not
already discussed above.

Test Instructions

Concise test instructions on how to verify that your feature works as intended. This should include
changes to the development environment (if applicable) and all commands needed to run your work.

Screenshot

If applicable (e.g. UI changes), add screenshots to help explain your work.


PR Type

Enhancement


Description

• Reorganized Android SDK code into product-specific packages
• Moved platform view classes to dedicated views package
• Consolidated mapper functions into single file
• Updated import statements across all affected files


Changes walkthrough 📝

Relevant files
Enhancement
13 files
SmileIDPlugin.kt
Updated imports and variable naming                                           
+36/-29 
Mapper.kt
Added comprehensive mapper functions                                         
[link]   
SmileIDBiometricKYC.kt
Moved to biometric product package                                             
+21/-20 
SmileIDDocumentCaptureView.kt
Moved to capture product package                                                 
+2/-1     
SmileIDSmartSelfieCaptureView.kt
Moved to capture product package                                                 
+4/-1     
SmileIDDocumentVerification.kt
Moved to document product package                                               
+6/-5     
SmileIDEnhancedDocumentVerification.kt
Moved to enhanced document verification package                   
+10/-9   
SmileIDSmartSelfieAuthenticationEnhanced.kt
Moved to enhanced selfie package                                                 
+2/-2     
SmileIDSmartSelfieEnrollmentEnhanced.kt
Moved to enhanced selfie package                                                 
+2/-2     
SmileIDSmartSelfieAuthentication.kt
Moved to selfie product package                                                   
+2/-1     
SmileIDSmartSelfieEnrollment.kt
Moved to selfie product package                                                   
+2/-1     
SmileComposablePlatformView.kt
Moved to views package                                                                     
+1/-1     
SmileSelfieComposablePlatformView.kt
Moved to views package                                                                     
+1/-1     

Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @prfectionist
    Copy link
    Contributor

    prfectionist bot commented Jun 17, 2025

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Possible Issue

    In the doSmartSelfieEnrollment and doSmartSelfieAuthentication methods, the livenessImages mapping uses the wrong variable selfieImage instead of the iterator variable it. This will cause all liveness images to be the same as the selfie image.

    livenessImages =
        livenessImages.map {
            File(selfieImage).asFormDataPart(
                partName = "liveness_images",
                mediaType = "image/jpeg",
            )
        },
    Code Quality

    The new consolidated mapper file is very large (601 lines) and contains many different mapping functions. Consider breaking this into smaller, more focused files organized by domain or functionality to improve maintainability.

    package com.smileidentity.flutter
    
    import FlutterActionResult
    import FlutterActions
    import FlutterAntifraud
    import FlutterAuthenticationRequest
    import FlutterAuthenticationResponse
    import FlutterAvailableIdType
    import FlutterBankCode
    import FlutterBiometricKycJobResult
    import FlutterBiometricKycJobStatusResponse
    import FlutterConfig
    import FlutterConsentInfo
    import FlutterConsentInformation
    import FlutterCountry
    import FlutterCountryInfo
    import FlutterDocumentVerificationJobResult
    import FlutterDocumentVerificationJobStatusResponse
    import FlutterEnhancedDocumentVerificationJobResult
    import FlutterEnhancedDocumentVerificationJobStatusResponse
    import FlutterEnhancedKycAsyncResponse
    import FlutterEnhancedKycRequest
    import FlutterEnhancedKycResponse
    import FlutterHostedWeb
    import FlutterIdInfo
    import FlutterIdSelection
    import FlutterIdType
    import FlutterImageLinks
    import FlutterImageType
    import FlutterJobStatusRequest
    import FlutterJobType
    import FlutterJobTypeV2
    import FlutterPartnerParams
    import FlutterPrepUploadRequest
    import FlutterPrepUploadResponse
    import FlutterProductsConfigRequest
    import FlutterProductsConfigResponse
    import FlutterServicesResponse
    import FlutterSmartSelfieJobResult
    import FlutterSmartSelfieJobStatusResponse
    import FlutterSmartSelfieResponse
    import FlutterSmartSelfieStatus
    import FlutterSuspectUser
    import FlutterUploadImageInfo
    import FlutterUploadRequest
    import FlutterValidDocument
    import FlutterValidDocumentsResponse
    import com.smileidentity.flutter.utils.getCurrentIsoTimestamp
    import com.smileidentity.models.ActionResult
    import com.smileidentity.models.Actions
    import com.smileidentity.models.Antifraud
    import com.smileidentity.models.AuthenticationRequest
    import com.smileidentity.models.AuthenticationResponse
    import com.smileidentity.models.AvailableIdType
    import com.smileidentity.models.BankCode
    import com.smileidentity.models.BiometricKycJobResult
    import com.smileidentity.models.BiometricKycJobStatusResponse
    import com.smileidentity.models.Config
    import com.smileidentity.models.ConsentInfo
    import com.smileidentity.models.ConsentInformation
    import com.smileidentity.models.ConsentedInformation
    import com.smileidentity.models.Country
    import com.smileidentity.models.CountryInfo
    import com.smileidentity.models.DocumentVerificationJobResult
    import com.smileidentity.models.DocumentVerificationJobStatusResponse
    import com.smileidentity.models.EnhancedDocumentVerificationJobResult
    import com.smileidentity.models.EnhancedDocumentVerificationJobStatusResponse
    import com.smileidentity.models.EnhancedKycAsyncResponse
    import com.smileidentity.models.EnhancedKycRequest
    import com.smileidentity.models.EnhancedKycResponse
    import com.smileidentity.models.HostedWeb
    import com.smileidentity.models.IdInfo
    import com.smileidentity.models.IdSelection
    import com.smileidentity.models.IdType
    import com.smileidentity.models.ImageLinks
    import com.smileidentity.models.ImageType
    import com.smileidentity.models.JobResult
    import com.smileidentity.models.JobStatusRequest
    import com.smileidentity.models.JobType
    import com.smileidentity.models.PartnerParams
    import com.smileidentity.models.PrepUploadRequest
    import com.smileidentity.models.PrepUploadResponse
    import com.smileidentity.models.ProductsConfigRequest
    import com.smileidentity.models.ProductsConfigResponse
    import com.smileidentity.models.ServicesResponse
    import com.smileidentity.models.SmartSelfieJobResult
    import com.smileidentity.models.SmartSelfieJobStatusResponse
    import com.smileidentity.models.SuspectUser
    import com.smileidentity.models.UploadImageInfo
    import com.smileidentity.models.UploadRequest
    import com.smileidentity.models.ValidDocument
    import com.smileidentity.models.ValidDocumentsResponse
    import com.smileidentity.models.v2.JobType as JobTypeV2
    import com.smileidentity.models.v2.SmartSelfieResponse
    import com.smileidentity.models.v2.SmartSelfieStatus
    import java.io.File
    
    /**
     * Pigeon does not allow non nullable types in this example here
     *
     *  final Map<String, String> extras;
     *
     *  Error: pigeons/messages.dart:18: Generic type arguments must be nullable in field "extras" in
     *  class "FlutterPartnerParams".
     *
     *  The fix is these two helper functions to convert maps to nullable types, and vice versa
     */
    fun convertNullableMapToNonNull(map: Map<String?, String?>?): Map<String, String> = map
        ?.filterKeys { it != null }
        ?.filterValues { it != null }
        ?.mapKeys { it.key!! }
        ?.mapValues { it.value!! } ?: mapOf()
    
    fun convertNonNullMapToNullable(map: Map<String, String>): Map<String?, String?> = map
        .mapKeys { it.key }
        .mapValues { it.value }
    
    fun FlutterJobType.toRequest() = when (this) {
        FlutterJobType.ENHANCEDKYC -> JobType.EnhancedKyc
        FlutterJobType.DOCUMENTVERIFICATION -> JobType.DocumentVerification
        FlutterJobType.BIOMETRICKYC -> JobType.BiometricKyc
        FlutterJobType.ENHANCEDDOCUMENTVERIFICATION -> JobType.EnhancedDocumentVerification
        FlutterJobType.SMARTSELFIEENROLLMENT -> JobType.SmartSelfieEnrollment
        FlutterJobType.SMARTSELFIEAUTHENTICATION -> JobType.SmartSelfieAuthentication
    }
    
    fun JobType.toResponse() = when (this) {
        JobType.EnhancedKyc -> FlutterJobType.ENHANCEDKYC
        JobType.DocumentVerification -> FlutterJobType.DOCUMENTVERIFICATION
        JobType.BiometricKyc -> FlutterJobType.BIOMETRICKYC
        JobType.EnhancedDocumentVerification -> FlutterJobType.ENHANCEDDOCUMENTVERIFICATION
        JobType.SmartSelfieEnrollment -> FlutterJobType.SMARTSELFIEENROLLMENT
        JobType.SmartSelfieAuthentication -> FlutterJobType.SMARTSELFIEAUTHENTICATION
        else -> TODO("Not yet implemented")
    }
    
    fun FlutterJobTypeV2.toRequest() = when (this) {
        FlutterJobTypeV2.SMARTSELFIEAUTHENTICATION -> JobTypeV2.SmartSelfieAuthentication
        FlutterJobTypeV2.SMARTSELFIEENROLLMENT -> JobTypeV2.SmartSelfieEnrollment
    }
    
    fun JobTypeV2.toResponse() = when (this) {
        JobTypeV2.SmartSelfieAuthentication -> FlutterJobTypeV2.SMARTSELFIEAUTHENTICATION
        JobTypeV2.SmartSelfieEnrollment -> FlutterJobTypeV2.SMARTSELFIEENROLLMENT
        else -> TODO("Not yet implemented")
    }
    
    fun FlutterAuthenticationRequest.toRequest() = AuthenticationRequest(
        jobType = jobType.toRequest(),
        country = country,
        idType = idType,
        updateEnrolledImage = updateEnrolledImage,
        jobId = jobId,
        userId = userId,
    )
    
    fun PartnerParams.toResponse() = FlutterPartnerParams(
        jobType = jobType?.toResponse(),
        jobId = jobId,
        userId = userId,
        extras = convertNonNullMapToNullable(extras),
    )
    
    fun FlutterPartnerParams.toRequest() = PartnerParams(
        jobType = jobType?.toRequest(),
        jobId = jobId,
        userId = userId,
        extras = convertNullableMapToNonNull(extras),
    )
    
    fun ConsentInfo.toRequest() = FlutterConsentInfo(
        canAccess = canAccess,
        consentRequired = consentRequired,
    )
    
    fun AuthenticationResponse.toResponse() = FlutterAuthenticationResponse(
        success = success,
        signature = signature,
        timestamp = timestamp,
        partnerParams = partnerParams.toResponse(),
        callbackUrl = callbackUrl,
        consentInfo = consentInfo?.toRequest(),
    )
    
    fun FlutterPrepUploadRequest.toRequest() = PrepUploadRequest(
        partnerParams = partnerParams.toRequest(),
        callbackUrl = callbackUrl,
        allowNewEnroll = allowNewEnroll,
        partnerId = partnerId,
        sourceSdk = "android (flutter)",
        timestamp = timestamp,
        signature = signature,
    )
    
    fun PrepUploadResponse.toResponse() = FlutterPrepUploadResponse(
        code = code,
        refId = refId,
        uploadUrl = uploadUrl,
        smileJobId = smileJobId,
    )
    
    fun FlutterUploadRequest.toRequest() = UploadRequest(
        images = images.mapNotNull { it?.toRequest() },
        idInfo = idInfo?.toRequest(),
    )
    
    fun FlutterUploadImageInfo.toRequest() = UploadImageInfo(
        imageTypeId = imageTypeId.toRequest(),
        image = File(imageName),
    )
    
    fun FlutterImageType.toRequest() = when (this) {
        FlutterImageType.SELFIEJPGFILE -> ImageType.SelfieJpgFile
        FlutterImageType.IDCARDJPGFILE -> ImageType.IdCardJpgFile
        FlutterImageType.SELFIEJPGBASE64 -> ImageType.SelfieJpgBase64
        FlutterImageType.IDCARDJPGBASE64 -> ImageType.IdCardJpgBase64
        FlutterImageType.LIVENESSJPGFILE -> ImageType.LivenessJpgFile
        FlutterImageType.IDCARDREARJPGFILE -> ImageType.IdCardRearJpgFile
        FlutterImageType.LIVENESSJPGBASE64 -> ImageType.LivenessJpgBase64
        FlutterImageType.IDCARDREARJPGBASE64 -> ImageType.IdCardRearJpgBase64
    }
    
    fun FlutterIdInfo.toRequest() = IdInfo(
        country = country,
        idType = idType,
        idNumber = idNumber,
        firstName = firstName,
        middleName = middleName,
        lastName = lastName,
        dob = dob,
        bankCode = bankCode,
        entered = entered,
    )
    
    fun FlutterConsentInformation.toRequest() = ConsentInformation(
        consented = ConsentedInformation(
            consentGrantedDate = consentGrantedDate,
            personalDetails = personalDetailsConsentGranted,
            contactInformation = contactInfoConsentGranted,
            documentInformation = documentInfoConsentGranted,
        ),
    )
    
    fun FlutterEnhancedKycRequest.toRequest() = EnhancedKycRequest(
        country = country,
        idType = idType,
        idNumber = idNumber,
        firstName = firstName,
        middleName = middleName,
        lastName = lastName,
        dob = dob,
        phoneNumber = phoneNumber,
        bankCode = bankCode,
        callbackUrl = callbackUrl,
        partnerParams = partnerParams.toRequest(),
        sourceSdk = "android (flutter)",
        timestamp = timestamp,
        signature = signature,
        consentInformation =
        consentInformation?.toRequest() ?: ConsentInformation(
            consented = ConsentedInformation(
                consentGrantedDate = getCurrentIsoTimestamp(),
                personalDetails = false,
                contactInformation = false,
                documentInformation = false,
            ),
        ),
    )
    
    fun EnhancedKycResponse.toResponse() = FlutterEnhancedKycResponse(
        smileJobId = smileJobId,
        partnerParams = partnerParams.toResponse(),
        resultText = resultText,
        resultCode = resultCode,
        actions = actions.toResponse(),
        country = country,
        idType = idType,
        idNumber = idNumber,
        fullName = fullName,
        expirationDate = expirationDate,
        dob = dob,
        base64Photo = base64Photo,
    )
    
    fun EnhancedKycAsyncResponse.toResponse() = FlutterEnhancedKycAsyncResponse(
        success = success,
    )
    
    fun Actions.toResponse() = FlutterActions(
        documentCheck = documentCheck.toResponse(),
        humanReviewCompare = humanReviewCompare.toResponse(),
        humanReviewDocumentCheck = humanReviewDocumentCheck.toResponse(),
        humanReviewLivenessCheck = humanReviewLivenessCheck.toResponse(),
        humanReviewSelfieCheck = humanReviewSelfieCheck.toResponse(),
        humanReviewUpdateSelfie = humanReviewUpdateSelfie.toResponse(),
        livenessCheck = livenessCheck.toResponse(),
        registerSelfie = registerSelfie.toResponse(),
        returnPersonalInfo = returnPersonalInfo.toResponse(),
        selfieCheck = selfieCheck.toResponse(),
        selfieProvided = selfieProvided.toResponse(),
        selfieToIdAuthorityCompare = selfieToIdAuthorityCompare.toResponse(),
        selfieToIdCardCompare = selfieToIdCardCompare.toResponse(),
        selfieToRegisteredSelfieCompare = selfieToRegisteredSelfieCompare.toResponse(),
        updateRegisteredSelfieOnFile = updateRegisteredSelfieOnFile.toResponse(),
        verifyDocument = verifyDocument.toResponse(),
        verifyIdNumber = verifyIdNumber.toResponse(),
    )
    
    fun ActionResult.toResponse() = when (this) {
        ActionResult.Passed -> FlutterActionResult.PASSED
        ActionResult.Completed -> FlutterActionResult.COMPLETED
        ActionResult.Approved -> FlutterActionResult.APPROVED
        ActionResult.Verified -> FlutterActionResult.VERIFIED
        ActionResult.ProvisionallyApproved -> FlutterActionResult.PROVISIONALLYAPPROVED
        ActionResult.Returned -> FlutterActionResult.RETURNED
        ActionResult.NotReturned -> FlutterActionResult.NOTRETURNED
        ActionResult.Failed -> FlutterActionResult.FAILED
        ActionResult.Rejected -> FlutterActionResult.REJECTED
        ActionResult.UnderReview -> FlutterActionResult.UNDERREVIEW
        ActionResult.UnableToDetermine -> FlutterActionResult.UNABLETODETERMINE
        ActionResult.NotApplicable -> FlutterActionResult.NOTAPPLICABLE
        ActionResult.NotVerified -> FlutterActionResult.NOTVERIFIED
        ActionResult.NotDone -> FlutterActionResult.NOTDONE
        ActionResult.IssuerUnavailable -> FlutterActionResult.ISSUERUNAVAILABLE
        ActionResult.IdAuthorityPhotoNotAvailable ->
            FlutterActionResult.IDAUTHORITYPHOTONOTAVAILABLE
    
        ActionResult.SentToHumanReview -> FlutterActionResult.SENTTOHUMANREVIEW
        ActionResult.Unknown -> FlutterActionResult.UNKNOWN
    }
    
    fun ImageLinks.toResponse() = FlutterImageLinks(
        selfieImageUrl = selfieImageUrl,
        error = error,
    )
    
    fun Antifraud.toResponse() = FlutterAntifraud(
        suspectUsers = suspectUsers.map { it.toResponse() },
    )
    
    fun SuspectUser.toResponse() = FlutterSuspectUser(
        reason = reason,
        userId = userId,
    )
    
    fun FlutterJobStatusRequest.toRequest() = JobStatusRequest(
        userId = userId,
        jobId = jobId,
        includeImageLinks = includeImageLinks,
        includeHistory = includeHistory,
        partnerId = partnerId,
        timestamp = timestamp,
        signature = signature,
    )
    
    fun SmartSelfieJobStatusResponse.toResponse() = FlutterSmartSelfieJobStatusResponse(
        timestamp = timestamp,
        jobComplete = jobComplete,
        jobSuccess = jobSuccess,
        code = code,
        result = result?.toResponse() as? FlutterSmartSelfieJobResult,
        resultString = result?.toResponse() as? String,
        imageLinks = imageLinks?.toResponse(),
    )
    
    fun SmartSelfieJobResult.toResponse(): Any = when (this) {
        is JobResult.Freeform -> this.result
        is SmartSelfieJobResult.Entry ->
            FlutterSmartSelfieJobResult(
                actions = actions.toResponse(),
                resultCode = resultCode,
                resultText = resultText,
                smileJobId = smileJobId,
                partnerParams = partnerParams.toResponse(),
                confidence = confidence,
            )
    }
    
    fun SmartSelfieStatus.toResponse() = when (this) {
        SmartSelfieStatus.Approved -> FlutterSmartSelfieStatus.APPROVED
        SmartSelfieStatus.Pending -> FlutterSmartSelfieStatus.PENDING
        SmartSelfieStatus.Rejected -> FlutterSmartSelfieStatus.REJECTED
        SmartSelfieStatus.Unknown -> FlutterSmartSelfieStatus.UNKNOWN
    }
    
    fun SmartSelfieResponse.toResponse() = FlutterSmartSelfieResponse(
        code = code,
        createdAt = createdAt,
        jobId = jobId,
        jobType = jobType.toResponse(),
        message = message,
        partnerId = partnerId,
        partnerParams = convertNonNullMapToNullable(partnerParams),
        status = status.toResponse(),
        updatedAt = updatedAt,
        userId = userId,
    )
    
    fun DocumentVerificationJobStatusResponse.toResponse() =
        FlutterDocumentVerificationJobStatusResponse(
            timestamp = timestamp,
            jobComplete = jobComplete,
            jobSuccess = jobSuccess,
            code = code,
            result = result?.toResponse() as? FlutterDocumentVerificationJobResult,
            resultString = result?.toResponse() as? String,
            imageLinks = imageLinks?.toResponse(),
        )
    
    fun DocumentVerificationJobResult.toResponse(): Any = when (this) {
        is JobResult.Freeform -> this.result
        is DocumentVerificationJobResult.Entry ->
            FlutterDocumentVerificationJobResult(
                actions = actions.toResponse(),
                resultCode = resultCode,
                resultText = resultText,
                smileJobId = smileJobId,
                partnerParams = partnerParams.toResponse(),
                country = country,
                idType = idType,
                fullName = fullName,
                idNumber = idNumber,
                dob = dob,
                gender = gender,
                expirationDate = expirationDate,
                documentImageBase64 = documentImageBase64,
                phoneNumber = phoneNumber,
                phoneNumber2 = phoneNumber2,
                address = address,
            )
    }
    
    fun BiometricKycJobStatusResponse.toResponse() = FlutterBiometricKycJobStatusResponse(
        timestamp = timestamp,
        jobComplete = jobComplete,
        jobSuccess = jobSuccess,
        code = code,
        result = result?.toResponse() as? FlutterBiometricKycJobResult,
        resultString = result?.toResponse() as? String,
        imageLinks = imageLinks?.toResponse(),
    )
    
    fun BiometricKycJobResult.toResponse(): Any = when (this) {
        is JobResult.Freeform -> this.result
        is BiometricKycJobResult.Entry ->
            FlutterBiometricKycJobResult(
                actions = actions.toResponse(),
                resultCode = resultCode,
                resultText = resultText,
                resultType = resultType,
                smileJobId = smileJobId,
                partnerParams = partnerParams.toResponse(),
                antifraud = antifraud?.toResponse(),
                dob = dob,
                photoBase64 = photoBase64,
                gender = gender,
                idType = idType,
                address = address,
                country = country,
                documentImageBase64 = documentImageBase64,
                fullData = fullData?.mapKeys { it.key }?.mapValues { it.value.toString() },
                fullName = fullName,
                idNumber = idNumber,
                phoneNumber = phoneNumber,
                phoneNumber2 = phoneNumber2,
                expirationDate = expirationDate,
            )
    }
    
    fun EnhancedDocumentVerificationJobStatusResponse.toResponse() =
        FlutterEnhancedDocumentVerificationJobStatusResponse(
            timestamp = timestamp,
            jobComplete = jobComplete,
            jobSuccess = jobSuccess,
            code = code,
            result = result?.toResponse() as? FlutterEnhancedDocumentVerificationJobResult,
            resultString = result?.toResponse() as? String,
            imageLinks = imageLinks?.toResponse(),
        )
    
    fun EnhancedDocumentVerificationJobResult.toResponse(): Any = when (this) {
        is JobResult.Freeform -> this.result
        is EnhancedDocumentVerificationJobResult.Entry ->
            FlutterEnhancedDocumentVerificationJobResult(
                actions = actions.toResponse(),
                resultCode = resultCode,
                resultText = resultText,
                smileJobId = smileJobId,
                resultType = resultType,
                partnerParams = partnerParams.toResponse(),
                antifraud = antifraud?.toResponse(),
                dob = dob,
                photoBase64 = photoBase64,
                gender = gender,
                idType = idType,
                address = address,
                country = country,
                documentImageBase64 = documentImageBase64,
                fullData = fullData?.mapKeys { it.key }?.mapValues { it.value.toString() },
                fullName = fullName,
                idNumber = idNumber,
                phoneNumber = phoneNumber,
                phoneNumber2 = phoneNumber2,
                expirationDate = expirationDate,
            )
    }
    
    fun FlutterProductsConfigRequest.toRequest() = ProductsConfigRequest(
        partnerId = partnerId,
        timestamp = timestamp,
        signature = signature,
    )
    
    fun ProductsConfigResponse.toResponse() = FlutterProductsConfigResponse(
        consentRequired = consentRequired.mapKeys { it.key },
        idSelection = idSelection.toResponse(),
    )
    
    fun IdSelection.toResponse() = FlutterIdSelection(
        basicKyc = basicKyc.mapKeys { it.key },
        biometricKyc = biometricKyc.mapKeys { it.key },
        enhancedKyc = enhancedKyc.mapKeys { it.key },
        documentVerification = documentVerification.mapKeys { it.key },
    )
    
    fun ValidDocumentsResponse.toResponse() = FlutterValidDocumentsResponse(
        validDocuments = validDocuments.map { it.toResponse() },
    )
    
    fun ValidDocument.toResponse() = FlutterValidDocument(
        country = country.toResponse(),
        idTypes = idTypes.map { it.toResponse() },
    )
    
    fun Country.toResponse() = FlutterCountry(
        name = name,
        code = code,
        continent = continent,
    )
    
    fun IdType.toResponse() = FlutterIdType(
        name = name,
        code = code,
        example = example.map { it },
        hasBack = hasBack,
    )
    
    fun ServicesResponse.toResponse() = FlutterServicesResponse(
        bankCodes = bankCodes.map { it.toResponse() },
        hostedWeb = hostedWeb.toResponse(),
    )
    
    fun BankCode.toResponse() = FlutterBankCode(
        name = name,
        code = code,
    )
    
    fun HostedWeb.toResponse() = FlutterHostedWeb(
        basicKyc = basicKyc.groupBy { it.countryCode }.mapValues { it.value.first().toResponse() },
        biometricKyc =
        biometricKyc
            .groupBy { it.countryCode }
            .mapValues { it.value.first().toResponse() },
        enhancedKyc =
        enhancedKyc
            .groupBy { it.countryCode }
            .mapValues { it.value.first().toResponse() },
        documentVerification =
        docVerification
            .groupBy { it.countryCode }
            .mapValues { it.value.first().toResponse() },
        enhancedKycSmartSelfie =
        enhancedKycSmartSelfie
            .groupBy { it.countryCode }
            .mapValues { it.value.first().toResponse() },
        enhancedDocumentVerification =
        enhancedDocumentVerification
            .groupBy { it.countryCode }
            .mapValues { it.value.first().toResponse() },
    )
    
    fun CountryInfo.toResponse() = FlutterCountryInfo(
        countryCode = countryCode,
        name = name,
        availableIdTypes = availableIdTypes.map { it.toResponse() },
    )
    
    fun AvailableIdType.toResponse() = FlutterAvailableIdType(
        idTypeKey = idTypeKey,
        label = label,
        requiredFields = requiredFields.map { it.name },
        testData = testData,
        idNumberRegex = idNumberRegex,
    )
    
    fun FlutterConfig.toRequest() = Config(
        partnerId = partnerId,
        authToken = authToken,
        prodLambdaUrl = prodBaseUrl,
        testLambdaUrl = sandboxBaseUrl,
    )

    @wangerekaharun wangerekaharun requested a review from Copilot June 18, 2025 09:20
    Copy link

    @Copilot Copilot AI left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Pull Request Overview

    This PR reorganizes the Android SDK and Dart plugin code into product-specific packages, replaces platform‐view factories with a generic SmileIDViewFactory, and updates CI to enforce formatting.

    • Moved Kotlin platform‐view and result classes into products/* and views packages and removed duplicated inner Factory classes.
    • Updated Dart widgets under lib/products/* to align imports and constructors for platform views.
    • Added dart format check before flutter analyze in the CI workflow.

    Reviewed Changes

    Copilot reviewed 46 out of 46 changed files in this pull request and generated no comments.

    Show a summary per file
    File Description
    lib/products/** Relocated Flutter widgets into product folders and updated doc comments/imports
    android/src/main/kotlin/com/smileidentity/flutter/views/SmileComposablePlatformView.kt Introduced SmileIDViewFactory and moved views into views package
    android/src/main/kotlin/com/smileidentity/flutter/**/SmileID*.kt Split product implementations under products/* and wired them to the new factory
    android/src/main/kotlin/com/smileidentity/flutter/SmileIDPlugin.kt Updated plugin registrations to use the new createFactory methods
    .github/workflows/build.yaml Added dart format check to the lint step
    Comments suppressed due to low confidence (4)

    lib/products/capture/smile_id_document_capture_view.dart:9

    • The doc comment mentions the "smart selfie enrollment" flow but this widget captures documents; update to describe the document capture flow correctly.
      /// Called when the user successfully completes the smart selfie enrollment flow. The result is a
    

    lib/products/biometric/smile_id_biometric_kyc.dart:61

    • The map key uses contactInfoConsentGranted while the parameter is named contactInformationConsentGranted. Consider aligning these names to avoid confusion or mismatches on the native side.
            "contactInfoConsentGranted": contactInformationConsentGranted,
    

    lib/products/document/smile_id_document_verification.dart:12

    • The PR description calls for deprecating onSuccessJson/onError in favor of a typed onResult callback, but this widget still exposes separate onSuccess and onError. Consider updating to the new unified API or marking the old callbacks as deprecated.
      final Function(String) onError;
    

    .github/workflows/build.yaml:24

    • [nitpick] You may also want to run flutter format . to cover Flutter‐specific formatting rules in addition to dart format, ensuring consistent style across Dart and Flutter code.
            run: dart format . --set-exit-if-changed && flutter analyze
    

    @jumaallan jumaallan merged commit 64d6835 into main Jun 18, 2025
    4 checks passed
    @jumaallan jumaallan deleted the feat/reorg-project branch June 18, 2025 09:40
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    2 participants