Skip to content

Commit

Permalink
Release 4.0.3
Browse files Browse the repository at this point in the history
  • Loading branch information
fernando-iproov committed Jun 28, 2024
1 parent ffdcd59 commit 74c8434
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 134 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# iProov Biometrics Flutter SDK

## 4.0.3

iProov Biometrics Flutter SDK v4.0.3 includes the following changes

### Flutter

- New UI Event API.

### iOS

* Upgraded SDK to [v11.1.1](https://github.com/iProov/ios/releases/tag/11.1.1).

### Android

* Upgraded SDK to [v9.1.1](https://github.com/iProov/android/releases/tag/v9.1.1).


## 4.0.2

iProov Biometrics Flutter SDK v4.0.2 includes the following changes
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Add the following to your project's `pubspec.yaml` file:

```yaml
dependencies:
iproov_flutter: ^4.0.2
iproov_flutter: ^4.0.3
```
You can then install it with:
Expand Down Expand Up @@ -105,6 +105,25 @@ stream.listen((event) {
});
```

### UI Event (Optional)

This feature was implemented in response to a specific request, but its applicability to other users may not be useful.

To monitor iProov UI lifecycle of a claim and receive the result, you collect from the `IProov.uiEvent()`.

```dart
IProov.uiEvent().listen((uiEvent) {
if (uiEvent is IProovUIEventNotStarted) {
// Called before the iProov user interface is displayed.
} else if (uiEvent is IProovUIEventStarted) {
// Called when the iProov user interface is displayed.
} else if (uiEvent is IProovUIEventEnded) {
// Called when the iProov user interface is dismissed.
}
});
```

👉 You should now familiarize yourself with the following resources:

- [iProov Biometrics iOS SDK documentation](https://github.com/iProov/ios)
Expand Down
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'com.iproov.flutter_sdk_plugin'
version '4.0.2'
version '4.0.3'

buildscript {
ext.kotlin_version = '1.5.30'
Expand Down Expand Up @@ -41,5 +41,5 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "com.iproov.sdk:iproov:9.0.3"
implementation "com.iproov.sdk:iproov:9.1.1"
}
222 changes: 150 additions & 72 deletions android/src/main/kotlin/com/iproov/sdk/IProovSDKPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,90 +11,147 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.cancel
import kotlinx.coroutines.withContext


class IProovSDKPlugin: FlutterPlugin {
class IProovSDKPlugin : FlutterPlugin {

companion object {
val TAG = IProovSDKPlugin::class.simpleName
}

private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)

private lateinit var eventChannel: EventChannel
private lateinit var uiEventChannel: EventChannel
private lateinit var methodChannel: MethodChannel

private var eventSink: EventChannel.EventSink? = null
private var uiEventSink: EventChannel.EventSink? = null
private var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding? = null
private val launcher = IProovCallbackLauncher()
private val launcher = IProovFlowLauncher()
private var pendingException: IProovException? = null

private val iProovListener = object : IProovCallbackLauncher.Listener {
override fun onConnecting() {
eventSink?.success(hashMapOf(
"event" to "connecting"
))
}

override fun onConnected() {
eventSink?.success(hashMapOf(
"event" to "connected"
))
}

override fun onProcessing(progress: Double, message: String?) {
eventSink?.success(hashMapOf(
"event" to "processing",
"progress" to progress,
"message" to message
))
}
init {
sessionStateListener()
sessionUIStateListener()
}

override fun onSuccess(result: IProov.SuccessResult) {
val frameArray = result.frame?.let { bmp ->
val stream = ByteArrayOutputStream()
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray: ByteArray = stream.toByteArray()
bmp.recycle()
byteArray
private fun sessionStateListener() {
coroutineScope.launch {
launcher.sessionsStates.collect { sessionState ->
sessionState?.let {
sessionState.state.let { state ->
withContext(Dispatchers.Main) {
when (state) {
is IProov.IProovState.Connecting -> {
eventSink?.success(hashMapOf("event" to "connecting"))
}

is IProov.IProovState.Connected -> {
eventSink?.success(hashMapOf("event" to "connected"))
}

is IProov.IProovState.Processing -> {
eventSink?.success(
hashMapOf(
"event" to "processing",
"progress" to state.progress,
"message" to state.message
)
)
}

is IProov.IProovState.Success -> {
val frameArray = state.successResult.frame?.let { bmp ->
val stream = ByteArrayOutputStream()
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray: ByteArray = stream.toByteArray()
bmp.recycle()
byteArray
}

eventSink?.success(
hashMapOf(
"event" to "success",
"frame" to frameArray
)
)
eventSink?.endOfStream()
}

is IProov.IProovState.Failure -> {
val context = flutterPluginBinding!!.applicationContext

val frameArray = state.failureResult.frame?.let { bmp ->
val stream = ByteArrayOutputStream()
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray: ByteArray = stream.toByteArray()
bmp.recycle()
byteArray
}

eventSink?.success(
hashMapOf(
"event" to "failure",
"feedbackCode" to state.failureResult.reason.feedbackCode,
"reason" to context.getString(state.failureResult.reason.description),
"frame" to frameArray
)
)
eventSink?.endOfStream()
}

is IProov.IProovState.Error -> {
eventSink?.success(state.exception.serialize())
eventSink?.endOfStream()
}

is IProov.IProovState.Canceled -> {
eventSink?.success(
hashMapOf(
"event" to "canceled",
"canceler" to state.canceler.name.lowercase()
)
)
eventSink?.endOfStream()
}
}
}
}
}
}

eventSink?.success(hashMapOf(
"event" to "success",
"frame" to frameArray
))
eventSink?.endOfStream()
}
}

override fun onFailure(result: IProov.FailureResult) {
val context = flutterPluginBinding!!.applicationContext

val frameArray = result.frame?.let { bmp ->
val stream = ByteArrayOutputStream()
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray: ByteArray = stream.toByteArray()
bmp.recycle()
byteArray
private fun sessionUIStateListener() {
coroutineScope.launch {
launcher.sessionsUIStates.collect { sessionUIState ->
sessionUIState?.let {
withContext(Dispatchers.Main) {
when (sessionUIState.state) {
IProov.IProovUIState.NotStarted -> {
uiEventSink?.success(hashMapOf("uiEvent" to "not_started"))
}

IProov.IProovUIState.Started -> {
uiEventSink?.success(hashMapOf("uiEvent" to "started"))
}

IProov.IProovUIState.Ended -> {
uiEventSink?.success(hashMapOf("uiEvent" to "ended"))
uiEventSink?.endOfStream()
}
}
}
}
}

eventSink?.success(hashMapOf(
"event" to "failure",
"feedbackCode" to result.reason.feedbackCode,
"reason" to context.getString(result.reason.description),
"frame" to frameArray
))
eventSink?.endOfStream()
}

override fun onCanceled(canceler: IProov.Canceler) {
eventSink?.success(hashMapOf(
"event" to "canceled",
"canceler" to canceler.name.lowercase()
))
eventSink?.endOfStream()
}

override fun onError(exception: IProovException) {
eventSink?.success(exception.serialize())
eventSink?.endOfStream()
}
}

Expand All @@ -110,6 +167,7 @@ class IProovSDKPlugin: FlutterPlugin {
pendingException = e
}
}

"keyPair.sign" -> {
val data = call.arguments
if (data is ByteArray) {
Expand All @@ -119,6 +177,7 @@ class IProovSDKPlugin: FlutterPlugin {
result.error("INVALID", "Invalid argument passed", null)
}
}

"keyPair.publicKey.getPem" -> result.success(launcher.getKeyPair(context!!).publicKey.pem)
"keyPair.publicKey.getDer" -> result.success(launcher.getKeyPair(context!!).publicKey.der)
"cancel" -> cancelSession()
Expand All @@ -127,7 +186,9 @@ class IProovSDKPlugin: FlutterPlugin {
}

private fun cancelSession() {
launcher.currentSession()?.cancel()
coroutineScope.launch {
launcher.currentSession()?.cancel()
}
}

private fun handleLaunch(call: MethodCall) {
Expand All @@ -140,9 +201,11 @@ class IProovSDKPlugin: FlutterPlugin {
streamingUrl == null -> {
throw UnexpectedErrorException(context, "Flutter Error: No streaming URL was provided.")
}

token == null -> {
throw UnexpectedErrorException(context, "Flutter Error: No token was provided.")
}

else -> {
var options = IProov.Options()

Expand All @@ -156,14 +219,16 @@ class IProovSDKPlugin: FlutterPlugin {
}

if (options.font != null) { // Remap custom font paths to assets path
if (options.font is IProov.Options.Font.PathFont){
if (options.font is IProov.Options.Font.PathFont) {
options.font = IProov.Options.Font.PathFont(
getFontPath((options.font as IProov.Options.Font.PathFont).path)
)
}
}

launcher.launch(context, streamingUrl, token, options)
coroutineScope.launch {
launcher.launch(context, streamingUrl, token, options)
}
}
}
}
Expand All @@ -189,18 +254,31 @@ class IProovSDKPlugin: FlutterPlugin {
}

override fun onCancel(arguments: Any?) {
launcher.currentSession()?.cancel()
coroutineScope.launch {
launcher.currentSession()?.cancel()
}
eventSink = null
}
})

launcher.listener = iProovListener
uiEventChannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.iproov.sdk.uiListener")
uiEventChannel.setStreamHandler(object : EventChannel.StreamHandler {

override fun onListen(arguments: Any?, sink: EventChannel.EventSink?) {
uiEventSink = sink
}

override fun onCancel(arguments: Any?) {
uiEventSink = null
}
})
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
launcher.listener = null
methodChannel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
uiEventChannel.setStreamHandler(null)
coroutineScope.cancel()
this.flutterPluginBinding = null
}

Expand All @@ -211,7 +289,7 @@ class IProovSDKPlugin: FlutterPlugin {
}

fun IProovException.serialize(): HashMap<String, String?> {
val exceptionName = when(this) {
val exceptionName = when (this) {
is CaptureAlreadyActiveException -> "capture_already_active"
is NetworkException -> "network"
is CameraPermissionException -> "camera_permission"
Expand Down
Loading

0 comments on commit 74c8434

Please sign in to comment.