diff --git a/README.md b/README.md
index 5129049..83a8fb3 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,46 @@
-## Ballerine KMP Integration example
+## Ballerine Integration example
-### Integration into Android
+### Integration into Android version of KMP project
-1. Create a Fragment or Activity which contains the WebView, it should load `https://[YOUR-SUBDOMAIN].dev.ballerine.app` (sanbox) or `https://[YOUR-SUBDOMAIN].ballerine.app` (prod) URL.
-2. Set webViewSettings to the following WebView settings:
+1. Generate JWT token in your backend which is required to access the Ballerine KYC flow APIs. Here is the link to the documentation on how to generate token.
+2. Add gradle dependency for Ballerine webview in your app-level `build.gradle` file
```kt
- webviewSettings.javaScriptEnabled = true
- webviewSettings.domStorageEnabled = true
- webviewSettings.allowFileAccess = true
+dependencies {
+ implementation("com.github.gau4sar:Ballerine-android-webview:1.0.0")
+}
```
-3. Setup the WebViewClient and WebChromeClient. In WebChromeClient override the method `onShowFileChooser` the same way as it is implemented in `UserRegistrationFlowActivity`.
-4. Add `onActivityResultListener` or `registerForActivityResult` to listen to the callback from the camera application:
+ We need to add the maven dependency for jitpack in settings.gradle
```kt
- val resultCode = result.resultCode
- val data = result.data
-
- when (resultCode) {
- Activity.RESULT_OK -> {
- //Image Uri will not be null for RESULT_OK
- val uri: Uri = data?.data!!
-
- // Use Uri object instead of File to avoid storage permissions
- filePathCallback!!.onReceiveValue(arrayOf(uri))
- filePathCallback = null
- }
- ImagePicker.RESULT_ERROR -> {
- Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
- }
- else -> {
- Toast.makeText(this, "Task Cancelled", Toast.LENGTH_SHORT).show()
- }
+allprojects {
+ repositories {
+ ...
+ maven("https://jitpack.io")
+ }
}
```
-5. Create a method that is checking for the finished state of the registration flow and saves the received results, see `checkWebViewUrl` method for more details.
+3. Add `BallerineKYCFlowWebview` composable to your Activity/Fragment to initiate the web KYC verification flow process.
+ Then we receive the result of the callback function `onVerificationComplete` in your Activity/Fragment.
+#### MainActivity.kt
+```kt
+BallerineKYCFlowWebView(
+ outputFileDirectory = outputFileDirectory,
+ cameraExecutorService = cameraExecutorService,
+ url = "$BALLERINE_WEB_URL?/b_t=$BALLERINE_API_TOKEN",
+ onVerificationComplete = { verificationResult ->
+
+ //Do something with the verification result
+
+ // Here we are just displaying the verification result as a Toast message
+ val toastMessage = "Idv result : ${verificationResult.idvResult} \n" +
+ "Status : ${verificationResult.status} \n" +
+ "Code : ${verificationResult.code}"
+
+ // Here we are just displaying the verification result as Text on the screen
+ Toast.makeText(this, toastMessage, Toast.LENGTH_LONG).show()
+ })
+```
+4. Once you have received the `VerificationResult` we can do further checks on the different values of the `VerificationResult` like `status`|`idvResult`|`code`|`isSync`.
+ (As shown above in Point 3)
### Integration into iOS
@@ -43,7 +51,7 @@
```swift
webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)
```
-4. Implement observeValue forKeyPath method to handle URL updates:
+4. Implement observeValue forKeyPath method to handle URL updates:
```swift
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let key = change?[NSKeyValueChangeKey.newKey], let url = (key as? NSURL)?.absoluteString else { return }
diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts
index 2a249c7..1256ce9 100644
--- a/androidApp/build.gradle.kts
+++ b/androidApp/build.gradle.kts
@@ -7,7 +7,7 @@ android {
compileSdk = 32
defaultConfig {
applicationId = "io.ballerine.kmp.example.android"
- minSdk = 22
+ minSdk = 23
targetSdk = 32
versionCode = 1
versionName = "1.0"
@@ -17,12 +17,31 @@ android {
isMinifyEnabled = false
}
}
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.1.1"
+ }
}
dependencies {
implementation(project(":shared"))
- implementation("com.google.android.material:material:1.4.0")
- implementation("androidx.appcompat:appcompat:1.3.1")
- implementation("androidx.constraintlayout:constraintlayout:2.1.0")
- implementation("com.github.dhaval2404:imagepicker:2.1")
+
+ implementation("com.github.ballerine-io:ballerine-android-sdk:1.0.4")
+
+ implementation("androidx.appcompat:appcompat:1.5.0")
+
+ val compose_version = "1.1.1"
+
+ implementation ("androidx.core:core-ktx:1.8.0")
+ implementation ("androidx.activity:activity-compose:1.5.1")
+
+ implementation ("androidx.compose.ui:ui:$compose_version")
+ implementation ("androidx.compose.material:material:$compose_version")
+ implementation ("androidx.compose.ui:ui-tooling-preview:$compose_version")
+ androidTestImplementation ("androidx.compose.ui:ui-test-junit4:$compose_version")
+ debugImplementation ("androidx.compose.ui:ui-tooling:$compose_version")
+
+ implementation("com.google.accompanist:accompanist-permissions:0.26.0-alpha")
}
\ No newline at end of file
diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml
index 330ea15..baa6cef 100644
--- a/androidApp/src/main/AndroidManifest.xml
+++ b/androidApp/src/main/AndroidManifest.xml
@@ -3,10 +3,14 @@
package="io.ballerine.kmp.example.android">
+
+
+
+
@@ -16,9 +20,5 @@
-
-
\ No newline at end of file
diff --git a/androidApp/src/main/java/io/ballerine/kmp/example/android/BallerineKYCFlow.kt b/androidApp/src/main/java/io/ballerine/kmp/example/android/BallerineKYCFlow.kt
deleted file mode 100644
index 4c0e5e1..0000000
--- a/androidApp/src/main/java/io/ballerine/kmp/example/android/BallerineKYCFlow.kt
+++ /dev/null
@@ -1,100 +0,0 @@
-package io.ballerine.kmp.example.android
-
-import android.app.Activity
-import android.net.Uri
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.webkit.*
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AppCompatActivity
-import com.github.dhaval2404.imagepicker.ImagePicker
-import io.ballerine.kmp.example.BallerineStorage
-
-
-class BallerineKYCFlow : AppCompatActivity() {
-
- lateinit var webView: WebView
- private var filePathCallback: ValueCallback>? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_user_registration_flow)
-
- webView = findViewById(R.id.web_view)
-
- webView.webViewClient = object : WebViewClient() {}
-
- setWebViewSettings(webView.settings)
- webView.webChromeClient = object : WebChromeClient() {
-
- override fun onShowFileChooser(
- webView: WebView?,
- filePathCallback: ValueCallback>?,
- fileChooserParams: FileChooserParams?
- ): Boolean {
- if (this@BallerineKYCFlow.filePathCallback != null) {
- this@BallerineKYCFlow.filePathCallback!!.onReceiveValue(null)
- }
- this@BallerineKYCFlow.filePathCallback = filePathCallback
- ImagePicker.with(this@BallerineKYCFlow)
- .cameraOnly()
- .createIntent { intent ->
- startForProfileImageResult.launch(intent)
- }
- return true
- }
- }
-
- loadUrl()
- checkWebViewUrl()
- }
-
- private fun checkWebViewUrl(){
- Handler(Looper.getMainLooper()).postDelayed({
- if(webView.url?.contains("final") == true){
- BallerineStorage.saveSecret(Uri.parse(webView.url).query ?: "")
- setResult(Activity.RESULT_OK)
- finish()
- return@postDelayed
- }
- //Log.d("TAG", "WebView url ${webView.url}")
- checkWebViewUrl()
- }, 2000)
- }
-
- private fun setWebViewSettings(webviewSettings: WebSettings) {
- webviewSettings.javaScriptEnabled = true
- webviewSettings.domStorageEnabled = true
- webviewSettings.allowFileAccess = true
- }
-
-
- private fun loadUrl() {
- webView.loadUrl("https://2.dev.ballerine.app")
- }
-
- private val startForProfileImageResult =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- val resultCode = result.resultCode
- val data = result.data
-
- when (resultCode) {
- Activity.RESULT_OK -> {
- //Image Uri will not be null for RESULT_OK
- val uri: Uri = data?.data!!
-
- // Use Uri object instead of File to avoid storage permissions
- filePathCallback!!.onReceiveValue(arrayOf(uri))
- filePathCallback = null
- }
- ImagePicker.RESULT_ERROR -> {
- Toast.makeText(this, ImagePicker.getError(data), Toast.LENGTH_SHORT).show()
- }
- else -> {
- Toast.makeText(this, "Task Cancelled", Toast.LENGTH_SHORT).show()
- }
- }
- }
-}
diff --git a/androidApp/src/main/java/io/ballerine/kmp/example/android/MainActivity.kt b/androidApp/src/main/java/io/ballerine/kmp/example/android/MainActivity.kt
index cc5a28e..b1c8308 100644
--- a/androidApp/src/main/java/io/ballerine/kmp/example/android/MainActivity.kt
+++ b/androidApp/src/main/java/io/ballerine/kmp/example/android/MainActivity.kt
@@ -1,40 +1,135 @@
package io.ballerine.kmp.example.android
-import android.app.Activity
-import android.content.Intent
-import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
-import android.widget.Button
-import android.widget.TextView
-import androidx.activity.result.contract.ActivityResultContracts
-import io.ballerine.kmp.example.BallerineStorage
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.OutlinedButton
+import androidx.compose.material.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import io.ballerine.android_sdk.BallerineKYCFlowWebView
+import java.io.File
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
class MainActivity : AppCompatActivity() {
+
+ companion object {
+
+ const val BALLERINE_WEB_URL = "https://moneco.dev.ballerine.app"
+
+ /**
+ * BALLERINE_API_TOKEN needs to be generated from the backend. Please follow the below link for more information on how to generate the tole
+ * https://www.notion.so/ballerine/Ballerine-s-Developers-Documentation-c9b93462384446ef98ffb69d16865981#228240bfef6f48f3971db07ef03368c3
+ */
+ const val BALLERINE_API_TOKEN =
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbmRVc2VySWQiOiJhMzEyYzk1ZC03ODE4LTQyNDAtOTQ5YS1mMDRmNDEwMzRlYzEiLCJjbGllbnRJZCI6IjI2YTRmOTFiLWFhM2UtNGNlNS1hZDE1LWYzNTRiOTI1NmJmMCIsImlhdCI6MTY1OTYxNzM1NCwiZXhwIjoxNjkwMzc1NzU0LCJpc3MiOiIyNmE0ZjkxYi1hYTNlLTRjZTUtYWQxNS1mMzU0YjkyNTZiZjAifQ.Nm-j9jVh7ByHoo0WkqnIQeVR0mNWcV3TZUNknSLRtbc"
+
+ const val MAIN_SCREEN = 0
+ const val WEB_VIEW_SCREEN = 1
+ }
+
+ private lateinit var outputFileDirectory: File
+ private lateinit var cameraExecutorService: ExecutorService
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- val button = findViewById