Skip to content

Commit

Permalink
Migrate from android views to compose (#329)
Browse files Browse the repository at this point in the history
* Make composable welcome screen

* Select available document types from supported countries

* Navigate to document capture methods

* Pick and upload images

* Manually take photos and upload them for verification

* Navigate to the confirmation screen

* Scan verification documents

* Check pending requirements and navigate appropriately

* Show progress indicator when uploading documents

* Update CameraViews and Document and Face Analysis tools to work with compse

* Move navigation utilities to navigation package

* Display Error screen when there is camera timeout

* Update how navigation routes are handled

* Ensure loading  screen is displayed

* Load svg flag images

* Update countries screen us

* Complete the verification process

* Update welcome screen to pass identity verification result

* Update WelcomeScreen.kt

* Create Welcome view tests

* Create test app

* Add document selection screen tests

* Add tests for Manual Capture Screen

* Tests for Document Capture Method Screen

* Test Confirmation screen UI

* Add support screen

* Check if camera permissions have been allowed

* Update implementation of how to navigate to Error Screens

* Navigate to requirement errors

* Delete android views and fragment

* Build view for ID number verification

* Migrate from Id number verification to compose

* Compose tax pin verification views

* Check if client application is supported

* Cancel the flow on back press

* Navigate up to resolve error

* Use Single line text Textfields

* Fix formatting issues

* Resolve formating issues

* Ignore compose dialect

* Update test cases

* Fix failing tests

---------

Co-authored-by: Seth Onyango <[email protected]>
  • Loading branch information
sethonyango and sethonyango authored Nov 18, 2024
1 parent fa07948 commit 5d05d8a
Show file tree
Hide file tree
Showing 162 changed files with 6,105 additions and 5,880 deletions.
3 changes: 2 additions & 1 deletion .codespellrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[codespell]
skip = .git,countries.json
skip = .git,countries.json,libs.versions.toml
ignore-words-list = OptIn,expresso,statics
7 changes: 5 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ ij_smart_tabs = false
ij_visual_guides = none
ij_wrap_on_typing = false

[*.{kt,kts}]
disabled_rules = import-ordering
[{*.kt,*.kts}]
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_packages_to_use_import_on_demand = unset
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
Empty file.
Empty file.
7 changes: 4 additions & 3 deletions build-config/klint.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
val ktlint by configurations.creating

dependencies {
ktlint("com.pinterest:ktlint:0.34.2")
ktlint("com.pinterest:ktlint:0.48.2")
}

tasks {
Expand All @@ -19,8 +19,9 @@ tasks {
val ktlintFormat by creating(JavaExec::class) {
group = "formatting"
description = "Fix Kotlin code style deviations."
mainClass.set("com.pinterest.ktlint.Main")
classpath = configurations["ktlint"]
args("-F", "src/**/*.kt")
jvmArgs = listOf("--add-opens=java.base/java.lang=ALL-UNNAMED")
setProperty("mainClass", "com.pinterest.ktlint.Main")
args = listOf("-F", "src/**/*.kt")
}
}
2 changes: 1 addition & 1 deletion falu-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ apply {
apply(from = "${rootDir}/build-config/klint.gradle.kts")

android {
compileSdk = 34
compileSdk = 35
namespace = project.properties["FALU_SDK_NAMESPACE"].toString()

val publishVersionCode: String by project.extra
Expand Down
10 changes: 4 additions & 6 deletions falu-core/src/main/java/io/falu/core/ApiKeyValidator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ class ApiKeyValidator {

fun requireValid(apiKey: String?): String {
require(!apiKey.isNullOrBlank()) {
"Invalid Publishable Key: " +
"You must use a valid FALU API key to make a FALU API request. " +
"For more info, see https://falu.io"
"Invalid Publishable Key: You must use a valid FALU API key to make a FALU API request. " +
"For more info, see https://falu.io"
}

require(!apiKey.startsWith("sk_")) {
"Invalid Publishable Key: " +
"You are using a secret key instead of a publishable one. " +
"For more info, see https://falu.io"
"Invalid Publishable Key: You are using a secret key instead of a publishable one. " +
"For more info, see https://falu.io"
}

return apiKey
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class ApiVersionInterceptor(private val code: String) : Interceptor {
.request()
.newBuilder()
.header("X-Falu-Version", code)

.build()

return chain.proceed(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class AppDetailsInterceptor(private val context: Context) : Interceptor {
val versionRelease = Build.VERSION.RELEASE

return "falu-android/${BuildConfig.FALU_VERSION_NAME} (Android $versionRelease; $manufacturer $model) " +
"$packageName/${context.appVersionName}"
"$packageName/${context.appVersionName}"
}

private val packageName: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class ApiConnectionException(
).joinToString(" ")
return ApiConnectionException(
"IOException during API request to $displayUrl: ${e.message}. " +
"Please check your internet connection and try again. " +
"If this problem persists, let us know at [email protected].",
"Please check your internet connection and try again. " +
"If this problem persists, let us know at [email protected].",
e
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import java.net.HttpURLConnection
* No valid API key provided.
*/
class AuthenticationException
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) constructor(problem: HttpApiResponseProblem) :
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
constructor(problem: HttpApiResponseProblem) :
FaluException(
problem,
HttpURLConnection.HTTP_UNAUTHORIZED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ abstract class FaluException(
}

private fun typedEquals(ex: FaluException): Boolean {
return problem == ex.problem &&
statusCode == ex.statusCode &&
errorCode == ex.errorCode &&
message == ex.message
return problem == ex.problem && statusCode == ex.statusCode && errorCode == ex.errorCode &&
message == ex.message
}

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ package io.falu.core
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.robolectric.RobolectricTestRunner
import kotlin.test.BeforeTest
import org.junit.Test
import org.mockito.Mockito
import kotlin.test.assertEquals
import org.mockito.kotlin.times
import org.mockito.kotlin.verify

@RunWith(RobolectricTestRunner::class)
class NetworkRetriesInterceptorTest {
Expand Down
2 changes: 1 addition & 1 deletion falu/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ id("com.android.library")
apply(from = "${rootDir}/build-config/klint.gradle.kts")

android {
compileSdk = 34
compileSdk = 35
namespace = project.properties["FALU_SDK_NAMESPACE"].toString()

defaultConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package io.falu.android.models.payments

import com.google.gson.annotations.JsonAdapter
import io.falu.core.models.FaluModel
import java.util.Date
import kotlinx.parcelize.Parcelize
import software.tingle.api.adapters.ISO8601DateAdapter
import java.util.Date

/**
* [The payment object](https://falu.io)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package io.falu.android.models.payments

import android.os.Parcelable
import java.util.Date
import kotlinx.parcelize.Parcelize
import java.util.Date

/**
* [The Payment Failure object](https://falu.io)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package io.falu.android.networking
import android.content.Context
import io.falu.core.exceptions.ApiException
import io.falu.core.exceptions.FaluException
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
import software.tingle.api.ResourceResponse
import kotlin.coroutines.CoroutineContext

/**
* A base class for Falu-related API requests.
Expand Down
2 changes: 1 addition & 1 deletion falu/src/test/java/io/falu/android/ApiKeyValidatorTests.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.falu.android

import io.falu.core.ApiKeyValidator
import kotlin.test.assertFailsWith
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.test.assertFailsWith

class ApiKeyValidatorTests {

Expand Down
5 changes: 2 additions & 3 deletions falu/src/test/java/io/falu/android/FaluEndToEndTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import io.falu.android.models.payments.Payment
import io.falu.android.models.payments.PaymentRequest
import io.falu.android.networking.FaluRepository
import io.falu.core.ApiResultCallback
import java.util.Date
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import java.util.Date
import kotlin.test.Test

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.O_MR1])
Expand Down Expand Up @@ -45,7 +45,6 @@ class FaluEndToEndTest {

@Test
fun `test mpesa payment works`() {

val mpesa = MpesaPaymentRequest(
phone = "+254712345678",
reference = "254712345678",
Expand Down
2 changes: 1 addition & 1 deletion falu/src/test/java/io/falu/android/MoneyPatternTests.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package io.falu.android

import io.falu.android.models.payments.Money
import org.junit.Test
import java.util.Currency
import kotlin.test.assertEquals
import org.junit.Test

class MoneyPatternTests {

Expand Down
25 changes: 23 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,16 @@ material = "1.12.0"
joda = "2.13.0"
nexus = "2.0.0"
coil = "2.7.0"
compose-bom = "2024.09.02"
accompanist = "0.34.0"
runtime-livedata = "1.7.2"
ui-test-junit4-android = "1.7.2"

[libraries]
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
abstractions = { module = "software.tingle:abstractions", version.ref = "abstractions" }
junit = { module = "junit:junit", version.ref = "junit" }
Expand Down Expand Up @@ -73,19 +80,33 @@ androidx-test-runner = { module = "androidx.test:runner", version.ref = "android
androidx-browser = { group = "androidx.browser", name = "browser", version = "1.8.0" }
coil-ktx = { module = "io.coil-kt:coil", version.ref = "coil" }
coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }

compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
compose-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
runtime = { module = "androidx.compose.runtime:runtime" }
ui-tests = { module = "androidx.compose.ui:ui-test-junit4" }
ui-tests-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
material3 = { module = "androidx.compose.material3:material3" }
accompanist = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
compose-activity = { module = "androidx.activity:activity-compose" }
compose-navigation = { module = "androidx.navigation:navigation-compose" }

tensorflow-lite = { module = "org.tensorflow:tensorflow-lite", version.ref = "tensorflow-lite" }
tensorflow-support = { module = "org.tensorflow:tensorflow-lite-support", version.ref = "tensorflow-support" }

joda = { module = "joda-time:joda-time", version.ref = "joda" }
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtime-livedata" }
androidx-ui-test-junit4-android = { group = "androidx.compose.ui", name = "ui-test-junit4-android", version.ref = "ui-test-junit4-android" }

[bundles]
mokito = ["mokito-core", "mokito-inline"]
kotlin-test = ["kotlin-test-junit", "kotlin-test-annotations"]
androidx-lifecycle = ["androidx-livedata", "androidx-viewmodel", "androidx-runtime"]
camera = ["androidx-camera-core", "androidx-camera-camera2", "androidx-camera-lifecycle", "androidx-camera-video", "androidx-camera-extensions", "androidx-camera-view"]
navigation = ["androidx-navigation-fragment", "androidx-navigation-ui"]
coil = ["coil-ktx", "coil-svg"]
coil = ["coil-ktx", "coil-svg", "coil-compose"]

[plugins]
nexus = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus" }
nexus = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
4 changes: 2 additions & 2 deletions identity-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ plugins {
apply(from = "${rootDir}/build-config/klint.gradle.kts")

android {
compileSdk = 34
compileSdk = 35
namespace = project.properties["FALU_SDK_NAMESPACE"].toString()

defaultConfig {
applicationId = "io.falu.identity.sample"
minSdk = 26
targetSdk = 34
targetSdk = 35

versionCode = 1
versionName = "1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,18 @@ class VerificationViewModel(application: Application) : AndroidViewModel(applica
allowIdNumberVerification: Boolean,
allowTaxPin: Boolean
) = liveData {

val request = IdentityVerificationCreationRequest(
options = IdentityVerificationOptions(
allowUploads = allowUploads,
document = if (allowDrivingLicense || allowPassport || allowIdentityCard)
document = if (allowDrivingLicense || allowPassport || allowIdentityCard) {
generateDocumentOptions(
allowDrivingLicense,
allowPassport,
allowIdentityCard
) else null,
)
} else {
null
},
selfie = if (allowDocumentSelfie) IdentityVerificationOptionsForSelfie() else null,
idNumber = if (allowIdNumberVerification) IdentityVerificationOptionsForIdNumber() else null,
tax = if (allowTaxPin) IdentityVerificationOptionsForTax(allowed = mutableListOf("ken_pin")) else null
Expand Down Expand Up @@ -92,7 +94,7 @@ class VerificationViewModel(application: Application) : AndroidViewModel(applica

class ApiClient : AbstractHttpApiClient(EmptyAuthenticationProvider()) {
suspend fun createIdentityVerification(request: IdentityVerificationCreationRequest):
ResourceResponse<IdentityVerification> {
ResourceResponse<IdentityVerification> {
val builder = Request.Builder()
.url("https://identity-verification.hst-smpls.falu.io/identity/create-verification")
.post(makeJson(request).toRequestBody(MEDIA_TYPE_JSON))
Expand Down
21 changes: 19 additions & 2 deletions identity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ plugins {
id("com.android.library")
kotlin("android")
id("kotlin-parcelize")
alias(libs.plugins.compose.compiler)
}

apply(from = "${rootDir}/build-config/klint.gradle.kts")

android {
compileSdk = 34
compileSdk = 35
namespace = project.properties["FALU_SDK_NAMESPACE"].toString()

defaultConfig {
Expand Down Expand Up @@ -46,6 +47,7 @@ android {
buildFeatures {
viewBinding = true
buildConfig = true
compose = true
}

lint.enable += "Interoperability"
Expand Down Expand Up @@ -73,6 +75,18 @@ dependencies {
implementation(libs.constraint)
implementation(libs.androidx.appcompat)

implementation(platform(libs.compose.bom))
implementation(libs.compose.preview)
implementation(libs.material3)
implementation(libs.compose.activity)
implementation(libs.compose.navigation)
implementation(libs.runtime)
implementation(libs.androidx.ui.graphics)
implementation(libs.accompanist)
implementation(libs.androidx.runtime.livedata)
implementation(libs.androidx.ui.test.junit4.android)
implementation(libs.androidx.ui.tooling.preview)

implementation(libs.tensorflow.lite)
implementation(libs.tensorflow.support)

Expand All @@ -85,6 +99,7 @@ dependencies {
implementation(libs.material)
implementation(libs.joda)
implementation(libs.bundles.coil)
implementation(libs.androidx.browser)

testImplementation(libs.junit)
testImplementation(libs.bundles.mokito)
Expand All @@ -98,7 +113,9 @@ dependencies {
testImplementation(libs.androidx.espresso.core)
testImplementation(libs.androidx.fragment.testing)
testImplementation(libs.androidx.core.ktx)
implementation(libs.androidx.browser)
testImplementation(libs.ui.tests)
testImplementation(libs.ui.tests.manifest)
testImplementation(libs.androidx.ui.tooling)

androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.test.runner)
Expand Down
Loading

0 comments on commit 5d05d8a

Please sign in to comment.