diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e9dc58d3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..e26d7d03a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# 3.0.0 +Flutter plugin that provides a solution for biometric verification by leveraging the power of Regula Face SDK Web Service. Biometric verification is the quickest and most reliable way to confirm any user’s identity and protect your business and your clients from fraud. + +Features included: +* Face Matching: Check the likelihood that two faces belong to the same person. +* Face Recognition: Automatically capture a photo with a person's face on it. +* Liveness Detection: Find out if the person on camera is alive. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..8cce25eda --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Regula Forensics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 890213a16..91be17802 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# flutter_face_api \ No newline at end of file +# Regula Face API (Flutter version) +Face API is a framework that is used for face matching, recognition and liveness detection. + +# Contents +* [How to build the demo application](#how-to-build-the-demo-application) +* [Documentation](#documentation) +* [Additional information](#additional-information) + +## How to build the demo application +1. Download or the clone current repository using the command `git clone https://github.com/regulaforensics/flutter_face_api.git.git`. +2. Run the following commands within the root directory: +```bash +$ cd example +# Install packages +$ flutter pub get +# Check that supported devices are running +$ flutter devices +# Run the app +$ flutter run +``` + +## Documentation +You can find documentation on API [here](https://docs.regulaforensics.com/flutter-face). + +## Additional information +If you have any technical questions, feel free to [contact](mailto:support@regulaforensics.com) us or create issues [here](https://github.com/regulaforensics/flutter_face_api/issues). diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 000000000..29e709151 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,7 @@ +*.iml +.gradle +/local.properties +.DS_Store +/build +/captures +/.idea \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 000000000..ba1d26786 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,52 @@ +group 'io.flutter.plugins.regula.faceapi.flutter_face_api' +version '1.0' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.0.0' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + maven { + url "http://maven.regulaforensics.com/RegulaDocumentReader" + allowInsecureProtocol true + } + maven { + url "http://maven.regulaforensics.com/RegulaDocumentReader/Beta" + allowInsecureProtocol true + } + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 18 + } + lintOptions { + disable 'InvalidPackage' + } + compileOptions { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } +} + +dependencies { + //noinspection GradleDependency + implementation('com.regula.face:api:3.0.1234'){ + transitive = true + } +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 000000000..38c8d4544 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..84337ad35 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 000000000..14d1b82dc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_face_api' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8cede8271 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api/FlutterFaceApiPlugin.java b/android/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api/FlutterFaceApiPlugin.java new file mode 100644 index 000000000..2b804f0ac --- /dev/null +++ b/android/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api/FlutterFaceApiPlugin.java @@ -0,0 +1,189 @@ +package io.flutter.plugins.regula.faceapi.flutter_face_api; + +import android.content.Context; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import com.regula.facesdk.configuration.FaceCaptureConfiguration; +import com.regula.facesdk.configuration.LivenessConfiguration; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +import static com.regula.facesdk.FaceSDK.Instance; + +@SuppressWarnings({"unchecked", "NullableProblems", "ConstantConditions", "RedundantSuppression"}) +public class FlutterFaceApiPlugin implements FlutterPlugin, MethodChannel.MethodCallHandler { + private ArrayList args; + private Context context; + + public FlutterFaceApiPlugin() { + } + + private Context getContext() { + return context; + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) { + context = flutterPluginBinding.getApplicationContext(); + new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_face_api/method").setMethodCallHandler(this); + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + } + + @SuppressWarnings("unused") + private interface Callback { + void success(Object o); + + void error(String s); + + default void success() { + success(""); + } + } + + private JSONArray arrayListToJSONArray(ArrayList list) { + JSONArray result = new JSONArray(); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getClass().equals(java.util.HashMap.class)) + result.put(hashMapToJSONObject((HashMap) list.get(i))); + else if (list.get(i).getClass().equals(java.util.ArrayList.class)) + result.put(arrayListToJSONArray((ArrayList) list.get(i))); + else + result.put(list.get(i)); + } + + return result; + } + + private JSONObject hashMapToJSONObject(HashMap map) { + JSONObject result = new JSONObject(); + try { + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().getClass().equals(java.util.HashMap.class)) + result.put(entry.getKey(), hashMapToJSONObject((HashMap) entry.getValue())); + else if (entry.getValue().getClass().equals(java.util.ArrayList.class)) + result.put(entry.getKey(), arrayListToJSONArray((ArrayList) entry.getValue())); + else + result.put(entry.getKey(), entry.getValue()); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + private T args(@SuppressWarnings("SameParameterValue") int index) { + if (args.get(index).getClass().equals(java.util.HashMap.class)) + return (T) hashMapToJSONObject((HashMap) args.get(index)); + if (args.get(index).getClass().equals(java.util.ArrayList.class)) + return (T) arrayListToJSONArray((ArrayList) args.get(index)); + return (T) args.get(index); + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + String action = call.method; + args = (ArrayList) call.arguments; + Callback callback = new Callback() { + @Override + public void success(Object o) { + result.success(o); + } + + @Override + public void error(String s) { + result.error("", s, null); + } + }; + + try { + switch (action) { + case "getServiceUrl": + getServiceUrl(callback); + break; + case "startLiveness": + startLiveness(callback); + break; + case "getFaceSdkVersion": + getFaceSdkVersion(callback); + break; + case "presentFaceCaptureActivity": + presentFaceCaptureActivity(callback); + break; + case "stopFaceCaptureActivity": + stopFaceCaptureActivity(callback); + break; + case "stopLivenessProcessing": + stopLivenessProcessing(callback); + break; + case "presentFaceCaptureActivityByCameraId": + presentFaceCaptureActivityByCameraId(callback, args(0)); + break; + case "startLivenessByCameraId": + startLivenessByCameraId(callback, args(0)); + break; + case "setServiceUrl": + setServiceUrl(callback, args(0)); + break; + case "matchFaces": + matchFaces(callback, args(0)); + break; + } + } catch (Exception ignored) { + } + } + + private void getServiceUrl(Callback callback) { + callback.success(Instance().getServiceUrl()); + } + + private void startLiveness(Callback callback) { + Instance().startLiveness(getContext(), (response) -> callback.success(JSONConstructor.generateLivenessResponse(response).toString())); + } + + private void getFaceSdkVersion(Callback callback) { + callback.success(Instance().getFaceSdkVersion()); + } + + private void presentFaceCaptureActivity(Callback callback) { + Instance().presentFaceCaptureActivity(getContext(), (response) -> callback.success(JSONConstructor.generateFaceCaptureResponse(response).toString())); + } + + private void stopFaceCaptureActivity(Callback callback) { + Instance().stopFaceCaptureActivity(getContext()); + callback.success(); + } + + private void stopLivenessProcessing(Callback callback) { + Instance().stopLivenessProcessing(getContext()); + callback.success(); + } + + private void presentFaceCaptureActivityByCameraId(Callback callback, int cameraID) { + Instance().presentFaceCaptureActivity(getContext(), new FaceCaptureConfiguration.Builder().setCameraId(cameraID).build(), (response) -> callback.success(JSONConstructor.generateFaceCaptureResponse(response).toString())); + } + + private void startLivenessByCameraId(Callback callback, int cameraID) { + Instance().startLiveness(getContext(), new LivenessConfiguration.Builder().setCameraId(cameraID).build(), (response) -> callback.success(JSONConstructor.generateLivenessResponse(response).toString())); + } + + private void setServiceUrl(Callback callback, String url) { + Instance().setServiceUrl(url); + callback.success(); + } + + private void matchFaces(Callback callback, String request) throws JSONException { + Instance().matchFaces(JSONConstructor.MatchFacesRequestFromJSON(new JSONObject(request)), (response) -> callback.success(JSONConstructor.generateMatchFacesResponse(response).toString())); + } +} \ No newline at end of file diff --git a/android/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api/JSONConstructor.java b/android/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api/JSONConstructor.java new file mode 100644 index 000000000..4d2df7038 --- /dev/null +++ b/android/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api/JSONConstructor.java @@ -0,0 +1,400 @@ +package io.flutter.plugins.regula.faceapi.flutter_face_api; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Base64; + +import com.regula.facesdk.exception.ComparedFacesPairException; +import com.regula.facesdk.exception.FaceCaptureException; +import com.regula.facesdk.exception.LivenessErrorException; +import com.regula.facesdk.exception.MatchFacesException; +import com.regula.facesdk.model.Image; +import com.regula.facesdk.model.results.ComparedFace; +import com.regula.facesdk.model.results.ComparedFacesPair; +import com.regula.facesdk.model.results.FaceCaptureResponse; +import com.regula.facesdk.model.results.LivenessResponse; +import com.regula.facesdk.model.results.MatchFacesResponse; +import com.regula.facesdk.request.MatchFacesRequest; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"ConstantConditions", "unused", "RedundantSuppression"}) +class JSONConstructor { + interface JSONObjectGeneratorWithContext { + JSONObject generateJSONObject(T param, Context context) throws JSONException; + } + + interface JSONObjectGenerator { + JSONObject generateJSONObject(T param) throws JSONException; + } + + interface StringGenerator { + String generateString(T param); + } + + static JSONArray generateList(List list) { + JSONArray result = new JSONArray(); + if(list == null) return result; + for (T t : list) + if (t != null) + result.put(t); + + return result; + } + + static JSONArray generateList(List list, JSONObjectGenerator generator) throws JSONException { + JSONArray result = new JSONArray(); + if(list == null) return result; + for (T t : list) + if (t != null) + result.put(generator.generateJSONObject(t)); + + return result; + } + + static JSONArray generateList(List list, StringGenerator generator) { + JSONArray result = new JSONArray(); + if(list == null) return result; + for (T t : list) + if (t != null) + result.put(generator.generateString(t)); + + return result; + } + + static JSONArray generateList(List list, JSONObjectGeneratorWithContext generator, Context context) throws JSONException { + JSONArray result = new JSONArray(); + if(list == null) return result; + for (T t : list) + if (t != null) + result.put(generator.generateJSONObject(t, context)); + + return result; + } + + static JSONArray generateArray(T[] array) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, array[i]); + + return result; + } + + static JSONArray generateArray(T[] array, JSONObjectGenerator generator) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, generator.generateJSONObject(array[i])); + + return result; + } + + static JSONArray generateArray(T[] array, StringGenerator generator) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, generator.generateString(array[i])); + + return result; + } + + static JSONArray generateArray(T[] array, JSONObjectGeneratorWithContext generator, Context context) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, generator.generateJSONObject(array[i], context)); + + return result; + } + + static JSONObject generateMap(Map map) throws JSONException { + JSONObject result = new JSONObject(); + if(map == null) return result; + for (Map.Entry entry : map.entrySet()) + if (entry != null) + result.put(entry.getKey().toString(), entry.getValue()); + return result; + } + + static JSONObject generateMap(Map map, JSONObjectGenerator generator) throws JSONException { + JSONObject result = new JSONObject(); + if(map == null) return result; + for (Map.Entry entry : map.entrySet()) + if (entry != null) + result.put(entry.getKey().toString(), generator.generateJSONObject(entry.getValue())); + return result; + } + + static JSONObject generateMap(Map map, StringGenerator generator) throws JSONException { + JSONObject result = new JSONObject(); + if(map == null) return result; + for (Map.Entry entry : map.entrySet()) + if (entry != null) + result.put(entry.getKey().toString(), generator.generateString(entry.getValue())); + return result; + } + + static JSONObject generateMap(Map map, JSONObjectGeneratorWithContext generator, Context context) throws JSONException { + JSONObject result = new JSONObject(); + if(map == null) return result; + for (Map.Entry entry : map.entrySet()) + if (entry != null) + result.put(entry.getKey().toString(), generator.generateJSONObject(entry.getValue(), context)); + return result; + } + + static JSONArray generateIntArray(int[] array) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, array[i]); + + return result; + } + + static JSONArray generateBooleanArray(boolean[] array) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, array[i]); + + return result; + } + + static JSONArray generateDoubleArray(double[] array) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, array[i]); + + return result; + } + + static JSONArray generateByteArray(byte[] array) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, array[i]); + + return result; + } + + static JSONArray generateLongArray(long[] array) throws JSONException { + JSONArray result = new JSONArray(); + if(array == null) return result; + for (int i = 0; i < array.length; i++) + result.put(i, array[i]); + + return result; + } + + static String generateBitmap(Bitmap input) { + if (input == null) return ""; + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + input.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream); + byte[] byteArray = byteArrayOutputStream.toByteArray(); + + return Base64.encodeToString(byteArray, Base64.DEFAULT); + } + + static Bitmap BitmapFromJSON(String input) { + byte[] decodedString = Base64.decode(input, Base64.DEFAULT); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.RGB_565; + Bitmap result = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length, options); + int sizeMultiplier = result.getByteCount() / 5000000; + if (result.getByteCount() > 5000000) + result = Bitmap.createScaledBitmap(result, result.getWidth() / (int) Math.sqrt(sizeMultiplier), result.getHeight() / (int) Math.sqrt(sizeMultiplier), false); + return result; + } + + static MatchFacesRequest MatchFacesRequestFromJSON(JSONObject input) { + try { + MatchFacesRequest result; + List images = new ArrayList<>(); + if (input.has("images")) { + JSONArray jsonArray_images = input.getJSONArray("images"); + for (int i = 0; i < jsonArray_images.length(); i++) + images.add(ImageFromJSON(jsonArray_images.getJSONObject(i))); + } + if (input.has("similarityThreshold")) { + float similarityThreshold = (float) input.getDouble("similarityThreshold"); + result = new MatchFacesRequest(images, similarityThreshold); + } else + result = new MatchFacesRequest(images); + if (input.has("customMetadata")) + result.setCustomMetadata(new JSONObject(input.getString("customMetadata"))); + return result; + } catch (JSONException ignored) { + } + return null; + } + + // To JSON + + static JSONObject generateFaceCaptureException(FaceCaptureException input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("errorCode", input.getErrorCode()); + result.put("message", input.getMessage()); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateLivenessErrorException(LivenessErrorException input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("errorCode", input.getErrorCode()); + result.put("message", input.getMessage()); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateMatchFacesException(MatchFacesException input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("errorCode", input.getErrorCode()); + result.put("message", input.getMessage()); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateComparedFacesPairException(ComparedFacesPairException input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("errorCode", input.getErrorCode()); + result.put("message", input.getMessage()); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateComparedFace(ComparedFace input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("tag", input.getTag()); + result.put("imageType", input.getImageType()); + result.put("position", input.getPosition()); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateComparedFacesPair(ComparedFacesPair input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("first", generateComparedFace(input.getFirst())); + result.put("second", generateComparedFace(input.getSecond())); + result.put("similarity", input.getSimilarity()); + result.put("exception", generateComparedFacesPairException(input.getException())); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateFaceCaptureResponse(FaceCaptureResponse input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("exception", generateFaceCaptureException(input.getException())); + result.put("image", generateImage(input.getImage())); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateLivenessResponse(LivenessResponse input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("bitmap", generateBitmap(input.getBitmap())); + result.put("liveness", input.getLiveness()); + result.put("exception", generateLivenessErrorException(input.getException())); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateMatchFacesResponse(MatchFacesResponse input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("exception", generateMatchFacesException(input.getException())); + result.put("matchedFaces", generateList(input.getMatchedFaces(), JSONConstructor::generateComparedFacesPair)); + result.put("unmatchedFaces", generateList(input.getUnmatchedFaces(), JSONConstructor::generateComparedFacesPair)); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateImage(Image input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("imageType", input.getImageType()); + result.put("tag", input.getTag()); + result.put("bitmap", generateBitmap(input.getBitmap())); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + static JSONObject generateMatchFacesRequest(MatchFacesRequest input) { + JSONObject result = new JSONObject(); + if (input == null) return result; + try { + result.put("similarityThreshold", input.getSimilarityThreshold()); + result.put("images", generateList(input.getImages(), JSONConstructor::generateImage)); + result.put("customMetadata", input.getCustomMetadata()); + } catch (JSONException e) { + e.printStackTrace(); + } + return result; + } + + // From JSON + + static Image ImageFromJSON(JSONObject input) { + try { + int imageType = input.getInt("imageType"); + Bitmap bitmap = null; + if (input.has("bitmap")) + bitmap = BitmapFromJSON(input.getString("bitmap")); + Image result = new Image(imageType, bitmap); + if (input.has("tag")) + result.setTag(input.getString("tag")); + return result; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } +} \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 000000000..1ba9c339e --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 000000000..bc2100d8f --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 000000000..ec184ed3a --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,54 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.regula.face.api" + minSdkVersion 19 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..508a92a72 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..b6b705a8c --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api_example/MainActivity.java b/example/android/app/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api_example/MainActivity.java new file mode 100644 index 000000000..bb9bb3ffd --- /dev/null +++ b/example/android/app/src/main/java/io/flutter/plugins/regula/faceapi/flutter_face_api_example/MainActivity.java @@ -0,0 +1,6 @@ +package io.flutter.plugins.regula.faceapi.flutter_face_api_example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..1f83a33fd --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..508a92a72 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 000000000..205da3d3c --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 000000000..38c8d4544 --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..ca31ae4a5 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri May 29 19:17:11 MSK 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 000000000..5a2f14fb1 --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/example/assets/images/portrait.png b/example/assets/images/portrait.png new file mode 100644 index 000000000..7e9aa776e Binary files /dev/null and b/example/assets/images/portrait.png differ diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 000000000..e96ef602b --- /dev/null +++ b/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..6b4c0f78a --- /dev/null +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..a74f845a9 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,518 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C3E7CA4524D1B0D4008FBAB2 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + C3E7CA4524D1B0D4008FBAB2 /* Runner.entitlements */, + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = regula.FaceSDKSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = regula.FaceSDKSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = ""; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = regula.FaceSDKSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..a28140cfd --- /dev/null +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.h b/example/ios/Runner/AppDelegate.h new file mode 100644 index 000000000..36e21bbf9 --- /dev/null +++ b/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/example/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m new file mode 100644 index 000000000..70e83933d --- /dev/null +++ b/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 000000000..0de66a822 --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,61 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_face_api_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NFCReaderUsageDescription + To use NFC + NSCameraUsageDescription + To use camera + NSPhotoLibraryUsageDescription + To use gallery + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + com.apple.developer.nfc.readersession.iso7816.select-identifiers + + A0000002471001 + E80704007F00070302 + A000000167455349474E + A0000002480100 + A0000002480200 + A0000002480300 + A00000045645444C2D3031 + + + diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements new file mode 100644 index 000000000..91c987219 --- /dev/null +++ b/example/ios/Runner/Runner.entitlements @@ -0,0 +1,11 @@ + + + + + com.apple.developer.nfc.readersession.formats + + NDEF + TAG + + + diff --git a/example/ios/Runner/main.m b/example/ios/Runner/main.m new file mode 100644 index 000000000..dff6597e4 --- /dev/null +++ b/example/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 000000000..88189bfbc --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,168 @@ +import 'dart:convert'; +import 'dart:io' as io; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'dart:async'; +import 'package:flutter_face_api/face_api.dart' as Regula; +import 'package:image_picker/image_picker.dart'; + +void main() => runApp(new MaterialApp(home: new MyApp())); + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + var image1 = new Regula.Image(); + var image2 = new Regula.Image(); + var img1 = Image.asset('assets/images/portrait.png'); + var img2 = Image.asset('assets/images/portrait.png'); + String _similarity = "nil"; + String _liveness = "nil"; + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + Future initPlatformState() async {} + + showAlertDialog(BuildContext context, bool first) => showDialog( + context: context, + builder: (BuildContext context) => + AlertDialog(title: Text("Select option"), actions: [ + // ignore: deprecated_member_use + FlatButton( + child: Text("Use gallery"), + onPressed: () { + ImagePicker().getImage(source: ImageSource.gallery).then( + (value) => setImage( + first, + io.File(value.path).readAsBytesSync(), + Regula.ImageType.IMAGE_TYPE_PRINTED)); + Navigator.pop(context); + }), + // ignore: deprecated_member_use + FlatButton( + child: Text("Use camera"), + onPressed: () { + Regula.FaceSDK.presentFaceCaptureActivity().then((result) => + setImage( + first, + base64Decode(Regula.FaceCaptureResponse.fromJson( + json.decode(result)) + .image + .bitmap + .replaceAll("\n", "")), + Regula.ImageType.IMAGE_TYPE_LIVE)); + Navigator.pop(context); + }) + ])); + + setImage(bool first, List imageFile, int type) { + if (imageFile == null) return; + setState(() => _similarity = "nil"); + if (first) { + image1.bitmap = base64Encode(imageFile); + image1.imageType = type; + setState(() { + img1 = Image.memory(imageFile); + _liveness = "nil"; + }); + } else { + image2.bitmap = base64Encode(imageFile); + image2.imageType = type; + setState(() => img2 = Image.memory(imageFile)); + } + } + + clearResults() { + setState(() { + img1 = Image.asset('assets/images/portrait.png'); + img2 = Image.asset('assets/images/portrait.png'); + _similarity = "nil"; + _liveness = "nil"; + }); + image1 = new Regula.Image(); + image2 = new Regula.Image(); + } + + matchFaces() { + if (image1 == null || + image1.bitmap == null || + image1.bitmap == "" || + image2 == null || + image2.bitmap == null || + image2.bitmap == "") return; + setState(() => _similarity = "Processing..."); + var request = new Regula.MatchFacesRequest(); + request.images = [image1, image2]; + Regula.FaceSDK.matchFaces(jsonEncode(request)).then((value) { + var response = Regula.MatchFacesResponse.fromJson(json.decode(value)); + var matchedFaces = response.matchedFaces; + setState(() => _similarity = matchedFaces.length > 0 + ? ((matchedFaces[0].similarity * 100).toStringAsFixed(2) + "%") + : "error"); + }); + } + + liveness() => Regula.FaceSDK.startLiveness().then((value) { + var result = Regula.LivenessResponse.fromJson(json.decode(value)); + setImage(true, base64Decode(result.bitmap.replaceAll("\n", "")), + Regula.ImageType.IMAGE_TYPE_LIVE); + setState( + () => _liveness = result.liveness == 0 ? "passed" : "unknown"); + }); + + Widget createButton(String text, VoidCallback onPress) => Container( + // ignore: deprecated_member_use + child: FlatButton( + color: Color.fromARGB(50, 10, 10, 10), + onPressed: onPress, + child: Text(text)), + width: 250, + ); + + Widget createImage(image, VoidCallback onPress) => Material( + child: InkWell( + onTap: onPress, + child: Container( + child: ClipRRect( + borderRadius: BorderRadius.circular(20.0), + child: Image(height: 150, width: 150, image: image), + ), + ), + )); + + @override + Widget build(BuildContext context) => Scaffold( + body: Container( + margin: EdgeInsets.fromLTRB(0, 0, 0, 100), + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + createImage(img1.image, () => showAlertDialog(context, true)), + createImage( + img2.image, () => showAlertDialog(context, false)), + Container(margin: EdgeInsets.fromLTRB(0, 0, 0, 15)), + createButton("Match", () => matchFaces()), + createButton("Liveness", () => liveness()), + createButton("Clear", () => clearResults()), + Container( + margin: EdgeInsets.fromLTRB(0, 15, 0, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Similarity: " + _similarity, + style: TextStyle(fontSize: 18)), + Container(margin: EdgeInsets.fromLTRB(20, 0, 0, 0)), + Text("Liveness: " + _liveness, + style: TextStyle(fontSize: 18)) + ], + )) + ])), + ); +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 000000000..6b58af5ac --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,25 @@ +name: flutter_face_api_example +description: Demonstrates how to use the flutter_face_api plugin. + +publish_to: 'none' + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + path_provider: ^1.6.5 + image_picker: ^0.6.4 + flutter: + sdk: flutter + flutter_face_api: + path: ../ + cupertino_icons: ^0.1.3 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + assets: + - assets/images/ + uses-material-design: true diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 000000000..7280b137d --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flutter_face_api_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/flutter_face_api.iml b/flutter_face_api.iml new file mode 100644 index 000000000..d5b16a310 --- /dev/null +++ b/flutter_face_api.iml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 000000000..aa479fd3c --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/ios/Classes/FlutterFaceApiPlugin.h b/ios/Classes/FlutterFaceApiPlugin.h new file mode 100644 index 000000000..4faa9245b --- /dev/null +++ b/ios/Classes/FlutterFaceApiPlugin.h @@ -0,0 +1,7 @@ +#import +@import FaceSDK; +#import "RFSWJSONConstructor.h" + +@interface FlutterFaceApiPlugin : NSObject + +@end diff --git a/ios/Classes/FlutterFaceApiPlugin.m b/ios/Classes/FlutterFaceApiPlugin.m new file mode 100644 index 000000000..47f79b86f --- /dev/null +++ b/ios/Classes/FlutterFaceApiPlugin.m @@ -0,0 +1,117 @@ +#import "FlutterFaceApiPlugin.h" + +@implementation FlutterFaceApiPlugin + +typedef void (^Callback)(NSString* response); + +- (void) result:(id _Nullable)message :(Callback)callback { + callback(message); +} + ++ (void)registerWithRegistrar:(NSObject*)registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"flutter_face_api/method" binaryMessenger:[registrar messenger]]; + FlutterFaceApiPlugin* instance = [FlutterFaceApiPlugin new]; + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + NSString* action = call.method; + NSMutableArray* args = call.arguments; + + Callback successCallback = ^(id _Nullable response){ + result(response); + }; + Callback errorCallback = ^(NSString* error){ + result([FlutterError errorWithCode:@"error" message:error details:nil]); + }; + + if([action isEqualToString:@"getServiceUrl"]) + [self getServiceUrl :successCallback :errorCallback]; + else if([action isEqualToString:@"startLiveness"]) + [self startLiveness :successCallback :errorCallback]; + else if([action isEqualToString:@"getFaceSdkVersion"]) + [self getFaceSdkVersion :successCallback :errorCallback]; + else if([action isEqualToString:@"presentFaceCaptureActivity"]) + [self presentFaceCaptureActivity :successCallback :errorCallback]; + else if([action isEqualToString:@"stopFaceCaptureActivity"]) + [self stopFaceCaptureActivity :successCallback :errorCallback]; + else if([action isEqualToString:@"stopLivenessProcessing"]) + [self stopLivenessProcessing :successCallback :errorCallback]; + else if([action isEqualToString:@"presentFaceCaptureActivityByCameraId"]) + [self presentFaceCaptureActivityByCameraId :[args objectAtIndex:0] :successCallback :errorCallback]; + else if([action isEqualToString:@"startLivenessByCameraId"]) + [self startLivenessByCameraId :[args objectAtIndex:0] :successCallback :errorCallback]; + else if([action isEqualToString:@"setServiceUrl"]) + [self setServiceUrl :[args objectAtIndex:0] :successCallback :errorCallback]; + else if([action isEqualToString:@"matchFaces"]) + [self matchFaces :[args objectAtIndex:0] :successCallback :errorCallback]; + else + [self result:[NSString stringWithFormat:@"%@/%@", @"method not implemented: ", action] :errorCallback]; +} + +- (void) getServiceUrl:(Callback)successCallback :(Callback)errorCallback{ + [self result:[RFSFaceSDK.service serviceURL] :successCallback]; +} + +- (void) startLiveness:(Callback)successCallback :(Callback)errorCallback{ + dispatch_async(dispatch_get_main_queue(), ^{ + [RFSFaceSDK.service startLivenessFrom:[[[UIApplication sharedApplication] keyWindow] rootViewController] animated:true onLiveness:[self getLivenessCompletion:successCallback :errorCallback] completion:nil]; + }); +} + +- (void) getFaceSdkVersion:(Callback)successCallback :(Callback)errorCallback{ + [self result:[RFSFaceSDK.service version] :successCallback]; +} + +- (void) presentFaceCaptureActivity:(Callback)successCallback :(Callback)errorCallback{ + dispatch_async(dispatch_get_main_queue(), ^{ + [RFSFaceSDK.service presentFaceCaptureViewControllerFrom:[[[UIApplication sharedApplication] keyWindow] rootViewController] animated:true onCapture:[self getFaceCaptureCompletion:successCallback :errorCallback] completion:nil]; + }); +} + +- (void) stopFaceCaptureActivity:(Callback)successCallback :(Callback)errorCallback{ + [RFSFaceSDK.service stopFaceCaptureViewController]; + [self result:@"" :successCallback]; +} + +- (void) stopLivenessProcessing:(Callback)successCallback :(Callback)errorCallback{ + [RFSFaceSDK.service stopLivenessProcessing]; + [self result:@"" :successCallback]; +} + +- (void) presentFaceCaptureActivityByCameraId:(NSNumber*)cameraId : (Callback)successCallback :(Callback)errorCallback{ + [self result:@"this is an android-only method" :errorCallback]; +} + +- (void) startLivenessByCameraId:(NSNumber*)cameraId : (Callback)successCallback :(Callback)errorCallback{ + [self result:@"this is an android-only method" :errorCallback]; +} + +- (void) setServiceUrl:(NSString*)url : (Callback)successCallback :(Callback)errorCallback{ + [RFSFaceSDK.service setServiceURL:url]; + [self result:@"" :successCallback]; +} + +- (void) matchFaces:(NSString*)requestString : (Callback)successCallback :(Callback)errorCallback{ + [RFSFaceSDK.service matchFaces:[RFSWJSONConstructor RFSMatchFacesRequestFromJSON:[NSJSONSerialization JSONObjectWithData:[requestString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:NULL]] completion:[self getMatchFacesCompletion:successCallback :errorCallback]]; +} + +- (void (^)(RFSLivenessResponse * _Nonnull)) getLivenessCompletion:(Callback)successCallback :(Callback)errorCallback { + return ^(RFSLivenessResponse* response) { + [self result:[RFSWJSONConstructor dictToString:[RFSWJSONConstructor generateRFSLivenessResponse:response]] :successCallback]; + }; +} + +- (void (^)(RFSFaceCaptureResponse * _Nonnull)) getFaceCaptureCompletion:(Callback)successCallback :(Callback)errorCallback { + return ^(RFSFaceCaptureResponse* response) { + [self result:[RFSWJSONConstructor dictToString:[RFSWJSONConstructor generateRFSFaceCaptureResponse:response]] :successCallback]; + }; +} + +- (void (^)(RFSMatchFacesResponse * _Nonnull)) getMatchFacesCompletion:(Callback)successCallback :(Callback)errorCallback { + return ^(RFSMatchFacesResponse* response) { + [self result:[RFSWJSONConstructor dictToString:[RFSWJSONConstructor generateRFSMatchFacesResponse:response]] :successCallback]; + }; +} + +@end diff --git a/ios/Classes/RFSWJSONConstructor.h b/ios/Classes/RFSWJSONConstructor.h new file mode 100644 index 000000000..e3a7375bf --- /dev/null +++ b/ios/Classes/RFSWJSONConstructor.h @@ -0,0 +1,19 @@ +#ifndef RFSWJSONConstructor_h +#define RFSWJSONConstructor_h + +@import UIKit; +@import FaceSDK; + +@interface RFSWJSONConstructor : NSObject + ++(NSString* _Nonnull)dictToString:(NSMutableDictionary* _Nonnull)input; ++(RFSMatchFacesRequest* _Nonnull)RFSMatchFacesRequestFromJSON:(NSDictionary* _Nonnull)input; ++(NSMutableDictionary* _Nonnull)generateRFSLivenessResponse:(RFSLivenessResponse* _Nullable)input; ++(NSMutableDictionary* _Nonnull)generateRFSFaceCaptureResponse:(RFSFaceCaptureResponse* _Nullable)input; ++(NSMutableDictionary* _Nonnull)generateRFSImage:(RFSImage* _Nullable)input; ++(NSMutableDictionary* _Nonnull)generateRFSMatchFacesResponse:(RFSMatchFacesResponse* _Nullable)input; ++(NSMutableDictionary* _Nonnull)generateRFSComparedFacesPair:(RFSComparedFacesPair* _Nullable)input; ++(NSMutableDictionary* _Nonnull)generateRFSComparedFace:(RFSComparedFace* _Nullable)input; + +@end +#endif \ No newline at end of file diff --git a/ios/Classes/RFSWJSONConstructor.m b/ios/Classes/RFSWJSONConstructor.m new file mode 100644 index 000000000..2f7f0e9f7 --- /dev/null +++ b/ios/Classes/RFSWJSONConstructor.m @@ -0,0 +1,232 @@ +#import "RFSWJSONConstructor.h" + +@implementation RFSWJSONConstructor + ++(NSString*)dictToString:(NSMutableDictionary*)input { + return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:input options:NSJSONWritingPrettyPrinted error:nil] encoding:NSUTF8StringEncoding]; +} + +// From JSON + ++(RFSMatchFacesRequest*)RFSMatchFacesRequestFromJSON:(NSDictionary*)input { + RFSMatchFacesRequest* result = [[RFSMatchFacesRequest alloc] initWithImages:[RFSWJSONConstructor NSArrayRFSImageFromJSON:[input valueForKey:@"images"]]]; + + if([input valueForKey:@"customMetadata"] != nil) + result.customMetadata = [input valueForKey:@"customMetadata"]; + if([input valueForKey:@"similarityThreshold"] != nil) + result.similarityThreshold = [input valueForKey:@"similarityThreshold"]; + + return result; +} + ++(RFSImage*)RFSImageFromJSON:(NSDictionary*)input { + RFSImage* result = [[RFSImage alloc] initWithImage:[RFSWJSONConstructor UIImageFromJSON:[input valueForKey:@"bitmap"]] type:[[input valueForKey:@"imageType"] integerValue]]; + + if([input valueForKey:@"tag"] != nil) + result.tag = [input valueForKey:@"tag"]; + + return result; +} + ++(UIImage*)UIImageFromJSON:(NSString*)input { + return [UIImage imageWithData:[[NSData alloc]initWithBase64EncodedString:input options:NSDataBase64DecodingIgnoreUnknownCharacters]]; +} + ++(NSMutableArray*)NSArrayRFSImageFromJSON:(NSArray*)input { + NSMutableArray* result = [[NSMutableArray alloc] init]; + for(NSDictionary* item in input) + [result addObject:[RFSWJSONConstructor RFSImageFromJSON:item]]; + + return result; +} + ++(NSMutableDictionary* _Nonnull)generateRFSLivenessError:(NSError* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"errorCode"] = [NSNumber numberWithInteger:[self NSIntegerWithRFSLivenessError:input.code]]; + result[@"message"] = input.localizedDescription; + + return result; +} + ++(NSInteger)NSIntegerWithRFSLivenessError:(RFSLivenessError)value { + if(value == RFSLivenessErrorCancelled) + return (NSInteger)5; + else if(value == RFSLivenessErrorProcessingTimeout) + return (NSInteger)6; + else if(value == RFSLivenessErrorProcessingFailed) + return (NSInteger)8; + else if(value == RFSLivenessErrorAPICallFailed) + return (NSInteger)7; + else if(value == RFSLivenessErrorProcessingAttemptsEnded) + return (NSInteger)9; + else if(value == RFSLivenessErrorNoLicense) + return (NSInteger)4; + + return 0; +} + ++(NSMutableDictionary* _Nonnull)generateRFSFaceCaptureError:(NSError* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"errorCode"] = [NSNumber numberWithInteger:[self NSIntegerWithRFSFaceCaptureError:input.code]]; + result[@"message"] = input.localizedDescription; + + return result; +} + ++(NSInteger)NSIntegerWithRFSFaceCaptureError:(RFSFaceCaptureError)value { + if(value == RFSFaceCaptureErrorCancelled) + return (NSInteger)1; + + return 0; +} + ++(NSMutableDictionary* _Nonnull)generateRFSComparedFacesPairError:(NSError* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"errorCode"] = [NSNumber numberWithInteger:[self NSIntegerWithRFSComparedFacesPairError:input.code]]; + result[@"message"] = input.localizedDescription; + + return result; +} + ++(NSInteger)NSIntegerWithRFSComparedFacesPairError:(RFSComparedFacesPairError)value { + if(value == RFSComparedFacesPairErrorImageEmpty) + return (NSInteger)1; + else if(value == RFSComparedFacesPairErrorFaceNotDetected) + return (NSInteger)2; + else if(value == RFSComparedFacesPairErrorLandmarksNotDetected) + return (NSInteger)3; + else if(value == RFSComparedFacesPairErrorFaceAlignerFailed) + return (NSInteger)4; + else if(value == RFSComparedFacesPairErrorDescriptorExtractorError) + return (NSInteger)5; + else if(value == RFSComparedFacesPairErrorAPICallFailed) + return (NSInteger)6; + + return 0; +} + ++(NSMutableDictionary* _Nonnull)generateRFSMatchFacesError:(NSError* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"errorCode"] = [NSNumber numberWithInteger:[self NSIntegerWithRFSMatchFacesError:input.code]]; + result[@"message"] = input.localizedDescription; + + return result; +} + ++(NSInteger)NSIntegerWithRFSMatchFacesError:(RFSMatchFacesError)value { + if(value == RFSComparedFacesPairErrorImageEmpty) + return (NSInteger)1; + else if(value == RFSMatchFacesErrorFaceNotDetected) + return (NSInteger)2; + else if(value == RFSMatchFacesErrorLandmarksNotDetected) + return (NSInteger)3; + else if(value == RFSMatchFacesErrorFaceAlignerFailed) + return (NSInteger)4; + else if(value == RFSMatchFacesErrorDescriptorExtractorError) + return (NSInteger)5; + else if(value == RFSMatchFacesErrorNoLicense) + return (NSInteger)6; + else if(value == RFSMatchFacesErrorNotInitialized) + return (NSInteger)7; + else if(value == RFSMatchFacesErrorCommandNotSupported) + return (NSInteger)8; + else if(value == RFSMatchFacesErrorCommandParamsReadError) + return (NSInteger)9; + else if(value == RFSMatchFacesErrorAPICallFailed) + return (NSInteger)10; + else if(value == RFSMatchFacesErrorProcessingFailed) + return (NSInteger)11; + + return 0; +} + + // To JSON + ++(NSMutableDictionary* _Nonnull)generateRFSLivenessResponse:(RFSLivenessResponse* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"bitmap"] = [UIImageJPEGRepresentation(input.image, 1.0) base64EncodedStringWithOptions:0]; + result[@"liveness"] = @(input.liveness); + result[@"exception"] = [self generateRFSLivenessError:input.error]; + + return result; +} + ++(NSMutableDictionary* _Nonnull)generateRFSFaceCaptureResponse:(RFSFaceCaptureResponse* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"image"] = [self generateRFSImage:input.image]; + result[@"exception"] = [self generateRFSFaceCaptureError:input.error]; + + return result; +} + ++(NSMutableDictionary* _Nonnull)generateRFSImage:(RFSImage* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"identifier"] = input.identifier; + result[@"tag"] = input.tag; + result[@"imageType"] = @(input.imageType); + result[@"bitmap"] = [UIImageJPEGRepresentation(input.image, 1.0) base64EncodedStringWithOptions:0]; + + return result; +} + ++(NSMutableDictionary* _Nonnull)generateRFSMatchFacesResponse:(RFSMatchFacesResponse* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"exception"] = [self generateRFSMatchFacesError:input.error]; + if(input.matchedFaces != nil){ + NSMutableArray *array = [NSMutableArray new]; + for(RFSComparedFacesPair* item in input.matchedFaces) + if(item != nil) + [array addObject:[self generateRFSComparedFacesPair:item]]; + result[@"matchedFaces"] = array; + } + if(input.unmatchedFaces != nil){ + NSMutableArray *array = [NSMutableArray new]; + for(RFSComparedFacesPair* item in input.unmatchedFaces) + if(item != nil) + [array addObject:[self generateRFSComparedFacesPair:item]]; + result[@"unmatchedFaces"] = array; + } + + return result; +} + ++(NSMutableDictionary* _Nonnull)generateRFSComparedFacesPair:(RFSComparedFacesPair* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"first"] = [self generateRFSComparedFace:input.first]; + result[@"second"] = [self generateRFSComparedFace:input.second]; + result[@"similarity"] = input.similarity; + result[@"exception"] = [self generateRFSComparedFacesPairError:input.error]; + + return result; +} + ++(NSMutableDictionary* _Nonnull)generateRFSComparedFace:(RFSComparedFace* _Nullable)input { + NSMutableDictionary *result = [NSMutableDictionary new]; + if(input == nil) return result; + + result[@"tag"] = input.tag; + result[@"imageType"] = @(input.imageType); + result[@"position"] = input.position; + + return result; +} + +@end \ No newline at end of file diff --git a/ios/flutter_face_api.podspec b/ios/flutter_face_api.podspec new file mode 100644 index 000000000..cc715a767 --- /dev/null +++ b/ios/flutter_face_api.podspec @@ -0,0 +1,18 @@ +Pod::Spec.new do |s| + s.name = 'flutter_face_api' + s.version = '3.0.0' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.platform = :ios, '9.0' + s.dependency 'FaceSDK', '3.0.798' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } +end diff --git a/lib/face_api.dart b/lib/face_api.dart new file mode 100644 index 000000000..308cb0711 --- /dev/null +++ b/lib/face_api.dart @@ -0,0 +1,396 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; + +// Classes + +class FaceCaptureException { + int? errorCode; + String? message; + + static FaceCaptureException? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new FaceCaptureException(); + + result.errorCode = jsonObject["errorCode"]; + result.message = jsonObject["message"]; + + return result; + } + + Map toJson(){ + Map result = {}; + + if (errorCode != null) result.addAll({"errorCode": errorCode}); + if (message != null) result.addAll({"message": message}); + + return result; + } +} + +class LivenessErrorException { + int? errorCode; + String? message; + + static LivenessErrorException? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new LivenessErrorException(); + + result.errorCode = jsonObject["errorCode"]; + result.message = jsonObject["message"]; + + return result; + } + + Map toJson(){ + Map result = {}; + + if (errorCode != null) result.addAll({"errorCode": errorCode}); + if (message != null) result.addAll({"message": message}); + + return result; + } +} + +class MatchFacesException { + int? errorCode; + String? message; + + static MatchFacesException? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new MatchFacesException(); + + result.errorCode = jsonObject["errorCode"]; + result.message = jsonObject["message"]; + + return result; + } + + Map toJson(){ + Map result = {}; + + if (errorCode != null) result.addAll({"errorCode": errorCode}); + if (message != null) result.addAll({"message": message}); + + return result; + } +} + +class ComparedFacesPairException { + int? errorCode; + String? message; + + static ComparedFacesPairException? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new ComparedFacesPairException(); + + result.errorCode = jsonObject["errorCode"]; + result.message = jsonObject["message"]; + + return result; + } + + Map toJson(){ + Map result = {}; + + if (errorCode != null) result.addAll({"errorCode": errorCode}); + if (message != null) result.addAll({"message": message}); + + return result; + } +} + +class ComparedFace { + String? tag; + int? imageType; + int? position; + + static ComparedFace? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new ComparedFace(); + + result.tag = jsonObject["tag"]; + result.imageType = jsonObject["imageType"]; + result.position = jsonObject["position"]; + + return result; + } + + Map toJson(){ + Map result = {}; + + if (tag != null) result.addAll({"tag": tag}); + if (imageType != null) result.addAll({"imageType": imageType}); + if (position != null) result.addAll({"position": position}); + + return result; + } +} + +class ComparedFacesPair { + ComparedFace? first; + ComparedFace? second; + double? similarity; + ComparedFacesPairException? exception; + + static ComparedFacesPair? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new ComparedFacesPair(); + + result.first = ComparedFace.fromJson(jsonObject["first"]); + result.second = ComparedFace.fromJson(jsonObject["second"]); + result.similarity = jsonObject["similarity"] == null ? null : jsonObject["similarity"].toDouble(); + result.exception = ComparedFacesPairException.fromJson(jsonObject["exception"]); + + return result; + } + + Map toJson(){ + Map result = {}; + + if (first != null) result.addAll({"first": first}); + if (second != null) result.addAll({"second": second}); + if (similarity != null) result.addAll({"similarity": similarity}); + if (exception != null) result.addAll({"exception": exception}); + + return result; + } +} + +class FaceCaptureResponse { + FaceCaptureException? exception; + Image? image; + + static FaceCaptureResponse? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new FaceCaptureResponse(); + + result.exception = FaceCaptureException.fromJson(jsonObject["exception"]); + result.image = Image.fromJson(jsonObject["image"]); + + return result; + } + + Map toJson(){ + Map result = {}; + + if (exception != null) result.addAll({"exception": exception}); + if (image != null) result.addAll({"image": image}); + + return result; + } +} + +class LivenessResponse { + String? bitmap; + int? liveness; + LivenessErrorException? exception; + + static LivenessResponse? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new LivenessResponse(); + + result.bitmap = jsonObject["bitmap"]; + result.liveness = jsonObject["liveness"]; + result.exception = LivenessErrorException.fromJson(jsonObject["exception"]); + + return result; + } + + Map toJson(){ + Map result = {}; + + if (bitmap != null) result.addAll({"bitmap": bitmap}); + if (liveness != null) result.addAll({"liveness": liveness}); + if (exception != null) result.addAll({"exception": exception}); + + return result; + } +} + +class MatchFacesResponse { + MatchFacesException? exception; + List matchedFaces = []; + List unmatchedFaces = []; + + static MatchFacesResponse? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new MatchFacesResponse(); + + result.exception = MatchFacesException.fromJson(jsonObject["exception"]); + if (jsonObject["matchedFaces"] != null) + for (var item in jsonObject["matchedFaces"]) + result.matchedFaces.add(ComparedFacesPair.fromJson(item)); + if (jsonObject["unmatchedFaces"] != null) + for (var item in jsonObject["unmatchedFaces"]) + result.unmatchedFaces.add(ComparedFacesPair.fromJson(item)); + + return result; + } + + Map toJson(){ + Map result = {}; + + if (exception != null) result.addAll({"exception": exception}); + if (matchedFaces != null) result.addAll({"matchedFaces": matchedFaces}); + if (unmatchedFaces != null) result.addAll({"unmatchedFaces": unmatchedFaces}); + + return result; + } +} + +class Image { + int? imageType; + String? tag; + String? bitmap; + + static Image? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new Image(); + + result.imageType = jsonObject["imageType"]; + result.tag = jsonObject["tag"]; + result.bitmap = jsonObject["bitmap"]; + + return result; + } + + Map toJson(){ + Map result = {}; + + if (imageType != null) result.addAll({"imageType": imageType}); + if (tag != null) result.addAll({"tag": tag}); + if (bitmap != null) result.addAll({"bitmap": bitmap}); + + return result; + } +} + +class MatchFacesRequest { + double? similarityThreshold; + List images = []; + dynamic? customMetadata; + + static MatchFacesRequest? fromJson(jsonObject) { + if (jsonObject == null) return null; + var result = new MatchFacesRequest(); + + result.similarityThreshold = jsonObject["similarityThreshold"] == null ? null : jsonObject["similarityThreshold"].toDouble(); + if (jsonObject["images"] != null) + for (var item in jsonObject["images"]) + result.images.add(Image.fromJson(item)); + result.customMetadata = jsonObject["customMetadata"]; + + return result; + } + + Map toJson(){ + Map result = {}; + + if (similarityThreshold != null) result.addAll({"similarityThreshold": similarityThreshold}); + if (images != null) result.addAll({"images": images}); + if (customMetadata != null) result.addAll({"customMetadata": customMetadata}); + + return result; + } +} + +// Enum + +class ComparedFacesPairErrorCodes { + static const int IMAGE_EMPTY = 1; + static const int FACE_NOT_DETECTED = 2; + static const int LANDMARKS_NOT_DETECTED = 3; + static const int FACE_ALIGNER_FAILED = 4; + static const int DESCRIPTOR_EXTRACTOR_ERROR = 5; + static const int API_CALL_FAILED = 6; +} + +class FaceCaptureResultCodes { + static const int CANCEL = 1; + static const int CAMERA_NOT_AVAILABLE = 2; + static const int CAMERA_NO_PERMISSION = 3; + static const int IN_PROGRESS_ALREADY = 4; + static const int CONTEXT_IS_NULL = 5; +} + +class ImageType { + static const int IMAGE_TYPE_PRINTED = 1; + static const int IMAGE_TYPE_RFID = 2; + static const int IMAGE_TYPE_LIVE = 3; + static const int IMAGE_TYPE_LIVE_WITH_DOC = 4; +} + +class LivenessErrorCode { + static const int CONTEXT_IS_NULL = 1; + static const int IN_PROGRESS_ALREADY = 2; + static const int ZOOM_NOT_SUPPORTED = 3; + static const int NO_LICENSE = 4; + static const int CANCELLED = 5; + static const int PROCESSING_TIMEOUT = 6; + static const int API_CALL_FAILED = 7; + static const int PROCESSING_FAILED = 8; + static const int PROCESSING_ATTEMPTS_ENDED = 9; +} + +class LivenessStatus { + static const int PASSED = 0; + static const int UNKNOWN = 1; +} + +class MatchFacesErrorCodes { + static const int IMAGE_EMPTY = 1; + static const int FACE_NOT_DETECTED = 2; + static const int LANDMARKS_NOT_DETECTED = 3; + static const int FACE_ALIGNER_FAILED = 4; + static const int DESCRIPTOR_EXTRACTOR_ERROR = 5; + static const int NO_LICENSE = 6; + static const int NOT_INITIALIZED = 7; + static const int COMMAND_IS_NOT_SUPPORTED = 8; + static const int COMMAND_PARAMS_READ_ERROR = 9; + static const int API_CALL_FAILED = 10; + static const int PROCESSING_FAILED = 11; +} + +class FaceSDK { + static const MethodChannel _channel = const MethodChannel('flutter_face_api/method'); + + static Future getServiceUrl() async { + return await _channel.invokeMethod("getServiceUrl", []); + } + + static Future startLiveness() async { + return await _channel.invokeMethod("startLiveness", []); + } + + static Future getFaceSdkVersion() async { + return await _channel.invokeMethod("getFaceSdkVersion", []); + } + + static Future presentFaceCaptureActivity() async { + return await _channel.invokeMethod("presentFaceCaptureActivity", []); + } + + static Future stopFaceCaptureActivity() async { + return await _channel.invokeMethod("stopFaceCaptureActivity", []); + } + + static Future stopLivenessProcessing() async { + return await _channel.invokeMethod("stopLivenessProcessing", []); + } + + static Future presentFaceCaptureActivityByCameraId(cameraId) async { + return await _channel.invokeMethod("presentFaceCaptureActivityByCameraId", [cameraId]); + } + + static Future startLivenessByCameraId(cameraId) async { + return await _channel.invokeMethod("startLivenessByCameraId", [cameraId]); + } + + static Future setServiceUrl(url) async { + return await _channel.invokeMethod("setServiceUrl", [url]); + } + + static Future matchFaces(request) async { + return await _channel.invokeMethod("matchFaces", [request]); + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000..1b81a7953 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,26 @@ +name: flutter_face_api +description: + This is a flutter module for compairing faces using phone`s camera. +version: 3.0.0 +homepage: "https://github.com/regulaforensics/flutter_face_api" + +environment: + sdk: '>=2.12.0 <3.0.0' + flutter: ">=1.10.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + plugin: + platforms: + android: + package: io.flutter.plugins.regula.faceapi.flutter_face_api + pluginClass: FlutterFaceApiPlugin + ios: + pluginClass: FlutterFaceApiPlugin \ No newline at end of file diff --git a/test/face_api_test.dart b/test/face_api_test.dart new file mode 100644 index 000000000..0a4d5c2df --- /dev/null +++ b/test/face_api_test.dart @@ -0,0 +1,18 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const MethodChannel channel = MethodChannel('flutter_face_api/method'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); +}