diff --git a/.github/workflows/dotnet-maui.yml b/.github/workflows/dotnet-maui.yml index aa7e157..8d4a5ba 100644 --- a/.github/workflows/dotnet-maui.yml +++ b/.github/workflows/dotnet-maui.yml @@ -11,6 +11,9 @@ jobs: runs-on: macos-13 steps: - uses: actions/checkout@v1 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.1' - name: Build demo run: | cd dotnet-maui diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 146794d..bd28898 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -8,24 +8,22 @@ on: jobs: build-custom-demo: - runs-on: macos-12 + runs-on: macos-13 defaults: run: working-directory: ios/GSSDKCustomDemo steps: - uses: actions/checkout@v2 - - run: pod install - run: xcodebuild build -scheme GSSDKCustomDemo -workspace GSSDKCustomDemo.xcworkspace -configuration Debug -sdk iphoneos \ ONLY_ACTIVE_ARCH=YES \ CODE_SIGNING_ALLOWED="NO" build-simple-demo: - runs-on: macos-12 + runs-on: macos-13 defaults: run: working-directory: ios/GSSDKSimpleDemo steps: - uses: actions/checkout@v2 - - run: pod install - run: xcodebuild build -scheme GSSDKSimpleDemo -workspace GSSDKSimpleDemo.xcworkspace -configuration Debug -sdk iphoneos \ ONLY_ACTIVE_ARCH=YES \ CODE_SIGNING_ALLOWED="NO" diff --git a/android/.gitignore b/android/.gitignore index 797713a..0f03048 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -49,8 +49,7 @@ fastlane/README.md google-credentials.json -# Exclude model files as they are copied from root assets -gssdk-core/src/main/assets/*.png - -# Exclude language files as they are copied from root assets +# Exclude these assets as they are copied from root assets +gssdk/src/main/assets/*.png +gssdk/src/main/res/raw/ocr_languages.csv *.traineddata diff --git a/android/build.gradle b/android/build.gradle index a44193e..bbdb0a8 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,7 +16,7 @@ buildscript { } } maven { - url 'https://s3.amazonaws.com/tgl.maven' + url 'https://repo.gradle.org/gradle/libs-releases' content { includeModule('com.twilio', 'apkscale') } @@ -24,11 +24,11 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.1.2' - // Patch until apkscale is fixed: https://github.com/twilio/apkscale/issues/10 - classpath "com.twilio:apkscale:0.0.101" + classpath "com.twilio:apkscale:0.1.6" classpath "androidx.benchmark:benchmark-gradle-plugin:1.2.0" classpath "firebase.test.lab:plugin:2.6.2" classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21' + classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.9.10' } } @@ -47,7 +47,7 @@ allprojects { } ext { - ndkVersion = '25.1.8937393' + ndkVersion = '26.2.11394342' compileSdkVersion = 34 targetSdkVersion = 34 minSdkVersion = 21 diff --git a/android/demo-custom/build.gradle b/android/demo-custom/build.gradle index 8fa2288..1c97d59 100644 --- a/android/demo-custom/build.gradle +++ b/android/demo-custom/build.gradle @@ -24,16 +24,8 @@ android { } dependencies { - implementation 'com.geniusscansdk:gssdk-core:4.21.0' - implementation 'com.geniusscansdk:gssdk-ocr:4.21.0' + implementation 'com.geniusscansdk:gssdk:5.0.0-beta9' implementation 'androidx.fragment:fragment:1.6.2' implementation 'androidx.appcompat:appcompat:1.6.1' } - -task copyLanguageFile(type: Copy) { - from "../../assets/eng.traineddata" - into "src/main/res/raw" -} - -preBuild.dependsOn copyLanguageFile diff --git a/android/demo-custom/src/main/java/com/geniusscansdk/demo/enhance/PdfGenerationTask.java b/android/demo-custom/src/main/java/com/geniusscansdk/demo/enhance/PdfGenerationTask.java index 0ee1a07..aea43ef 100644 --- a/android/demo-custom/src/main/java/com/geniusscansdk/demo/enhance/PdfGenerationTask.java +++ b/android/demo-custom/src/main/java/com/geniusscansdk/demo/enhance/PdfGenerationTask.java @@ -4,33 +4,21 @@ import android.content.Context; import android.os.AsyncTask; -import com.geniusscansdk.pdf.DocumentGenerator; import com.geniusscansdk.core.TextLayout; -import com.geniusscansdk.demo.R; import com.geniusscansdk.demo.model.Page; -import com.geniusscansdk.ocr.OCREngineProgressListener; import com.geniusscansdk.ocr.OcrConfiguration; import com.geniusscansdk.ocr.OcrProcessor; import com.geniusscansdk.ocr.OcrResult; +import com.geniusscansdk.pdf.DocumentGenerator; import com.geniusscansdk.pdf.PDFDocument; -import com.geniusscansdk.pdf.PDFImageProcessor; import com.geniusscansdk.pdf.PDFPage; import com.geniusscansdk.pdf.PDFSize; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.List; -/** - * Created by guillaume on 25/10/16. - */ - public class PdfGenerationTask extends AsyncTask { public interface OnPdfGeneratedListener { @@ -75,17 +63,9 @@ protected Exception doInBackground(Void... params) { OcrProcessor ocrProcessor = null; if (isOCREnabled) { - try { - copyTessdataFiles(); - } catch (IOException e) { - return new IOException("Cannot copy tessdata", e); - } - OcrConfiguration ocrConfiguration = new OcrConfiguration(Arrays.asList("eng"), getTessdataDirectory()); - ocrProcessor = new OcrProcessor(context, ocrConfiguration, new OCREngineProgressListener() { - @Override - public void updateProgress(int progress) { - publishProgress(pageProgress + progress / pages.size()); - } + OcrConfiguration ocrConfiguration = new OcrConfiguration(Arrays.asList("en-US")); + ocrProcessor = new OcrProcessor(context, ocrConfiguration, progress -> { + publishProgress(pageProgress + progress / pages.size()); }); } @@ -106,12 +86,12 @@ public void updateProgress(int progress) { } // Export all pages in A4 - pdfPages.add(new PDFPage(image.getAbsolutePath(), A4_SIZE, textLayout)); + pdfPages.add(new PDFPage(image, A4_SIZE, textLayout)); pageIndex++; } // Here we don't protect the PDF document with a password - PDFDocument pdfDocument = new PDFDocument("test", null, null, new Date(), new Date(), pdfPages); + PDFDocument pdfDocument = new PDFDocument(pdfPages, /* title = */ "test"); try { DocumentGenerator.Configuration configuration = new DocumentGenerator.Configuration(outputFile); new DocumentGenerator(context).generatePDFDocument(pdfDocument, configuration); @@ -136,36 +116,4 @@ protected void onProgressUpdate(Integer... values) { progressDialog.setProgress(values[0]); } } - - private void copyTessdataFiles() throws IOException { - File tessdataDir = getTessdataDirectory(); - - if (tessdataDir.exists()) { - return; - } - - tessdataDir.mkdir(); - - InputStream in = context.getResources().openRawResource(R.raw.eng); - File engFile = new File(tessdataDir, "eng.traineddata"); - OutputStream out = new FileOutputStream(engFile); - - byte[] buffer = new byte[1024]; - int len = in.read(buffer); - while (len != -1) { - out.write(buffer, 0, len); - len = in.read(buffer); - } - } - - private File getTessdataDirectory() { - return new File(context.getExternalFilesDir(null), "tessdata"); - } - - private class PDFNoopImageProcessor extends PDFImageProcessor { - @Override - public String process(String inputFilePath) { - return inputFilePath; - } - } } diff --git a/android/demo-custom/src/main/res/raw/eng.traineddata b/android/demo-custom/src/main/res/raw/eng.traineddata deleted file mode 100644 index bbef467..0000000 Binary files a/android/demo-custom/src/main/res/raw/eng.traineddata and /dev/null differ diff --git a/android/demo-simple/build.gradle b/android/demo-simple/build.gradle index 9d500ff..038f8da 100644 --- a/android/demo-simple/build.gradle +++ b/android/demo-simple/build.gradle @@ -25,17 +25,8 @@ android { } dependencies { - implementation 'com.geniusscansdk:gssdk-core:4.21.0' - implementation 'com.geniusscansdk:gssdk-ocr:4.21.0' - implementation 'com.geniusscansdk:gssdk-scanflow:4.21.0' + implementation 'com.geniusscansdk:gssdk:5.0.0-beta9' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.10.0' } - -task copyLanguageFile(type: Copy) { - from "../../assets/eng.traineddata" - into "src/main/res/raw" -} - -preBuild.dependsOn copyLanguageFile diff --git a/android/demo-simple/src/main/java/com/geniusscansdk/simpledemo/MainActivity.java b/android/demo-simple/src/main/java/com/geniusscansdk/simpledemo/MainActivity.java index f1f0015..fb2685f 100644 --- a/android/demo-simple/src/main/java/com/geniusscansdk/simpledemo/MainActivity.java +++ b/android/demo-simple/src/main/java/com/geniusscansdk/simpledemo/MainActivity.java @@ -9,7 +9,13 @@ import android.util.Log; import android.widget.TextView; -import com.geniusscansdk.core.GeniusScanSDK; +import androidx.annotation.Nullable; +import androidx.annotation.RawRes; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; + import com.geniusscansdk.core.LicenseException; import com.geniusscansdk.scanflow.ScanConfiguration; import com.geniusscansdk.scanflow.ScanFlow; @@ -21,13 +27,6 @@ import java.io.InputStream; import java.util.Arrays; -import androidx.annotation.Nullable; -import androidx.annotation.RawRes; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; - public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); @@ -68,11 +67,9 @@ private ScanConfiguration createBaseConfiguration() { scanConfiguration.highlightColor = ContextCompat.getColor(this, R.color.colorAccent); ScanConfiguration.OcrConfiguration ocrConfiguration = new ScanConfiguration.OcrConfiguration(); - ocrConfiguration.languages = Arrays.asList("eng"); - ocrConfiguration.languagesDirectory = getTessdataDirectory(); + ocrConfiguration.languages = Arrays.asList("en-US"); scanConfiguration.ocrConfiguration = ocrConfiguration; - copyFileFromResource(R.raw.eng, new File(getTessdataDirectory(), "eng.traineddata")); return scanConfiguration; } @@ -121,12 +118,6 @@ private void copyFileFromResource(@RawRes int fileResId, File destinationFile) { } } - private File getTessdataDirectory() { - File directory = new File(getExternalFilesDir(null), "tessdata"); - directory.mkdirs(); - return directory; - } - @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == ScanFlow.SCAN_REQUEST && resultCode == Activity.RESULT_OK && data != null) { @@ -146,12 +137,14 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten // The license key is invalid or expired, either ask the user to update the app or provide a fallback new AlertDialog.Builder(this) .setMessage("Please update to the latest version.") + .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {}) .show(); } } catch (Exception e) { Log.e(TAG, "Error during scan flow", e); new AlertDialog.Builder(this) .setMessage("An error occurred: " + e.getMessage()) + .setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {}) .show(); } } else { diff --git a/android/demo-simple/src/main/res/raw/eng.traineddata b/android/demo-simple/src/main/res/raw/eng.traineddata deleted file mode 100644 index bbef467..0000000 Binary files a/android/demo-simple/src/main/res/raw/eng.traineddata and /dev/null differ diff --git a/cordova-plugin-genius-scan-demo/config.xml b/cordova-plugin-genius-scan-demo/config.xml index 5456cab..2f45124 100644 --- a/cordova-plugin-genius-scan-demo/config.xml +++ b/cordova-plugin-genius-scan-demo/config.xml @@ -38,7 +38,7 @@ - + diff --git a/cordova-plugin-genius-scan-demo/www/eng.traineddata b/cordova-plugin-genius-scan-demo/www/eng.traineddata deleted file mode 100644 index bbef467..0000000 Binary files a/cordova-plugin-genius-scan-demo/www/eng.traineddata and /dev/null differ diff --git a/cordova-plugin-genius-scan-demo/www/js/index.js b/cordova-plugin-genius-scan-demo/www/js/index.js index 7f24d56..4975665 100644 --- a/cordova-plugin-genius-scan-demo/www/js/index.js +++ b/cordova-plugin-genius-scan-demo/www/js/index.js @@ -2,23 +2,6 @@ function onError(error) { alert("Error: " + JSON.stringify(error)); } -function copy(filepath, toDirectory, filename, callback) { - window.resolveLocalFileSystemURL(filepath, function(fileEntry) { - window.resolveLocalFileSystemURL(toDirectory, function(dirEntry) { - dirEntry.getFile(filename, { create: true, exclusive: false }, function(targetFileEntry) { - fileEntry.file(function(file) { - targetFileEntry.createWriter(function(fileWriter) { - fileWriter.onwriteend = function() { - callback(); - }; - fileWriter.write(file); - }); - }); - }, onError); - }, onError); - }, onError); -} - var app = { initialize: function() { document.addEventListener("deviceready", this.onDeviceReady.bind(this), false); @@ -55,18 +38,13 @@ var app = { }; function startScanFlow() { - var assetLanguageUri = `${cordova.file.applicationDirectory}www/eng.traineddata` - var appFolder = window.cordova.platformId == 'android' ? cordova.file.externalDataDirectory : cordova.file.dataDirectory; - copy(assetLanguageUri, appFolder, 'eng.traineddata', function() { - var configuration = { - source: 'camera', - ocrConfiguration: { - languages: ['eng'], - languagesDirectoryUrl: appFolder - } - }; - cordova.plugins.GeniusScan.scanWithConfiguration(configuration, onScanFlowResult, onError); - }); + var configuration = { + source: 'camera', + ocrConfiguration: { + languages: ['en-US'] + } + }; + cordova.plugins.GeniusScan.scanWithConfiguration(configuration, onScanFlowResult, onError); } function onScanFlowResult(result) { diff --git a/dotnet-maui/MainPage.xaml.cs b/dotnet-maui/MainPage.xaml.cs index 23773e3..ef99cec 100644 --- a/dotnet-maui/MainPage.xaml.cs +++ b/dotnet-maui/MainPage.xaml.cs @@ -19,15 +19,7 @@ async void StartScanning(object sender, EventArgs args) { try { - var appFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - var languageFilePath = Path.Combine(appFolder, "eng.traineddata"); - Console.WriteLine(languageFilePath); - if (!File.Exists(languageFilePath)) - { - await DownloadFileAsync("https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata", languageFilePath); - } - - var documentUrl = await scanFlowService.StartScanning("file://" + appFolder); + var documentUrl = await scanFlowService.StartScanning(); await Launcher.OpenAsync(new OpenFileRequest { @@ -40,17 +32,4 @@ await Launcher.OpenAsync(new OpenFileRequest await DisplayAlert("Alert", "Error: " + e.Message, "OK"); } } - - private async Task DownloadFileAsync(string fileUrl, string downloadedFilePath) - { - var client = new HttpClient(); - - var downloadStream = await client.GetStreamAsync(fileUrl); - - var fileStream = File.Create(downloadedFilePath); - - await downloadStream.CopyToAsync(fileStream); - } } - - diff --git a/dotnet-maui/Platforms/Android/MainActivity.cs b/dotnet-maui/Platforms/Android/MainActivity.cs index 12449f8..212c77c 100644 --- a/dotnet-maui/Platforms/Android/MainActivity.cs +++ b/dotnet-maui/Platforms/Android/MainActivity.cs @@ -10,12 +10,11 @@ public class MainActivity : MauiAppCompatActivity { private static TaskCompletionSource currentTask = null; - public Task StartScanning(string languagesDirectoryUrl) + public Task StartScanning() { var ocrConfiguration = new ScanConfiguration.OcrConfiguration { - Languages = new List { "eng" }, - LanguagesDirectory = new Java.IO.File(Android.Net.Uri.Parse(languagesDirectoryUrl).Path) + Languages = new List { "en-US" }, }; var configuration = new ScanConfiguration diff --git a/dotnet-maui/Platforms/Android/ScanFlowService.cs b/dotnet-maui/Platforms/Android/ScanFlowService.cs index 3407469..43b947b 100644 --- a/dotnet-maui/Platforms/Android/ScanFlowService.cs +++ b/dotnet-maui/Platforms/Android/ScanFlowService.cs @@ -9,10 +9,10 @@ public partial void SetLicenseKey(string licenseKey) ScanFlow.SetLicenseKey(Platform.CurrentActivity, licenseKey, /* autoRefresh = */ true); } - public partial Task StartScanning(string languagesDirectoryUrl) + public partial Task StartScanning() { var mainActivity = (MainActivity)Platform.CurrentActivity; - return mainActivity.StartScanning(languagesDirectoryUrl); + return mainActivity.StartScanning(); } } } diff --git a/dotnet-maui/Platforms/iOS/ScanFlowService.cs b/dotnet-maui/Platforms/iOS/ScanFlowService.cs index 9b9284c..7f1f23c 100644 --- a/dotnet-maui/Platforms/iOS/ScanFlowService.cs +++ b/dotnet-maui/Platforms/iOS/ScanFlowService.cs @@ -12,12 +12,11 @@ public partial void SetLicenseKey(string licenseKey) GSK.SetLicenseKey(licenseKey, /* autoRefresh = */ true); } - public partial Task StartScanning(string languagesDirectoryUrl) + public partial Task StartScanning() { var ocrConfiguration = new NSMutableDictionary { - { new NSString("languages"), NSArray.FromStrings(new string[] { "eng" }) }, - { new NSString("languagesDirectoryUrl"), new NSString(languagesDirectoryUrl) } + { new NSString("languages"), NSArray.FromStrings(new string[] { "en-US" }) } }; var configuration = new NSMutableDictionary @@ -34,7 +33,7 @@ private Task StartScanning(NSDictionary configurationDictionary) var taskCompletionSource = new TaskCompletionSource(); var outError = new NSError(); - var configuration = GSKScanFlowConfiguration_Dictionary.ConfigurationWithDictionary(new GSKScanFlowConfiguration(), configurationDictionary, out outError); + var configuration = GSKScanFlowConfiguration.ConfigurationWithDictionary(configurationDictionary, out outError); var scanFlow = GSKScanFlow.ScanFlowWithConfiguration(configuration); var viewController = UIApplication.SharedApplication.Delegate.GetWindow().RootViewController; scanFlow.StartFromViewController(viewController, diff --git a/dotnet-maui/ScanFlowService.cs b/dotnet-maui/ScanFlowService.cs index 059017f..5b7cf58 100644 --- a/dotnet-maui/ScanFlowService.cs +++ b/dotnet-maui/ScanFlowService.cs @@ -4,7 +4,7 @@ public partial class ScanFlowService { public partial void SetLicenseKey(string licenseKey); - public partial Task StartScanning(string languagesDirectoryUrl); + public partial Task StartScanning(); } } diff --git a/dotnet-maui/SimpleDemo.csproj b/dotnet-maui/SimpleDemo.csproj index 113cc1d..3fc04e2 100644 --- a/dotnet-maui/SimpleDemo.csproj +++ b/dotnet-maui/SimpleDemo.csproj @@ -1,7 +1,7 @@  - net7.0-android;net7.0-ios + net8.0-android;net8.0-ios Exe SimpleDemo true @@ -19,11 +19,11 @@ 1.0 1 - 11.0 + 13.0 21.0 - + false @@ -59,10 +59,10 @@ - + - + diff --git a/flutter-plugin-genius-scan-demo/assets/eng.traineddata b/flutter-plugin-genius-scan-demo/assets/eng.traineddata deleted file mode 100644 index bbef467..0000000 Binary files a/flutter-plugin-genius-scan-demo/assets/eng.traineddata and /dev/null differ diff --git a/flutter-plugin-genius-scan-demo/ios/Flutter/AppFrameworkInfo.plist b/flutter-plugin-genius-scan-demo/ios/Flutter/AppFrameworkInfo.plist index 9625e10..1dc6cf7 100644 --- a/flutter-plugin-genius-scan-demo/ios/Flutter/AppFrameworkInfo.plist +++ b/flutter-plugin-genius-scan-demo/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 13.0 diff --git a/flutter-plugin-genius-scan-demo/ios/Podfile b/flutter-plugin-genius-scan-demo/ios/Podfile index 4adb921..ae5bacc 100644 --- a/flutter-plugin-genius-scan-demo/ios/Podfile +++ b/flutter-plugin-genius-scan-demo/ios/Podfile @@ -1,4 +1,4 @@ -platform :ios, '11.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/flutter-plugin-genius-scan-demo/ios/Podfile.lock b/flutter-plugin-genius-scan-demo/ios/Podfile.lock index 24fa15f..32dac9c 100644 --- a/flutter-plugin-genius-scan-demo/ios/Podfile.lock +++ b/flutter-plugin-genius-scan-demo/ios/Podfile.lock @@ -25,11 +25,11 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" SPEC CHECKSUMS: - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_genius_scan: d21c98195765aafff37ddf54c6cdc64913bbb3db + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_genius_scan: cfd11a652f735f92fedfd257fedee150af48c8c9 open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 -PODFILE CHECKSUM: f70cbe32e71f16c77469a29c52f47705f5041b7c +PODFILE CHECKSUM: 3ce3a3b82c5cf6a622cb22850b0afa4ee279bea6 COCOAPODS: 1.12.1 diff --git a/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/project.pbxproj b/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/project.pbxproj index 31e12a7..0deec6c 100644 --- a/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/project.pbxproj +++ b/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/project.pbxproj @@ -167,7 +167,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -218,15 +218,11 @@ ); inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/flutter_genius_scan/GSSDKCore.framework/GSSDKCore", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/flutter_genius_scan/GSSDKScanFlow.framework/GSSDKScanFlow", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/flutter_genius_scan/GSSDKOCR.framework/GSSDKOCR", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/flutter_genius_scan/GSSDK.framework/GSSDK", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GSSDKCore.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GSSDKScanFlow.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GSSDKOCR.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GSSDK.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -360,7 +356,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -381,7 +377,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -441,7 +437,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -488,7 +484,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -509,7 +505,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -537,7 +533,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 9997cfd..a00db7a 100644 --- a/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/flutter-plugin-genius-scan-demo/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ copyLanguageFile() async { - Directory languageFolder = await getApplicationSupportDirectory(); - File languageFile = File(languageFolder.path + "/eng.traineddata"); - if (!languageFile.existsSync()) { - ByteData data = await rootBundle.load("assets/eng.traineddata"); - List bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); - await languageFile.writeAsBytes(bytes); - } - return languageFolder; - } - void displayError(BuildContext context, PlatformException error) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(error.message!))); } diff --git a/flutter-plugin-genius-scan-demo/pubspec.lock b/flutter-plugin-genius-scan-demo/pubspec.lock index 6534344..83f69a0 100644 --- a/flutter-plugin-genius-scan-demo/pubspec.lock +++ b/flutter-plugin-genius-scan-demo/pubspec.lock @@ -84,7 +84,7 @@ packages: name: flutter_genius_scan url: "https://pub.dartlang.org" source: hosted - version: "4.21.0" + version: "5.0.0-beta9" flutter_test: dependency: "direct dev" description: flutter diff --git a/flutter-plugin-genius-scan-demo/pubspec.yaml b/flutter-plugin-genius-scan-demo/pubspec.yaml index fdc3b86..a9e3efc 100644 --- a/flutter-plugin-genius-scan-demo/pubspec.yaml +++ b/flutter-plugin-genius-scan-demo/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: open_filex: ^4.3.4 flutter_genius_scan: - version: 4.21.0 + version: 5.0.0-beta9 path_provider: ^2.1.1 dev_dependencies: @@ -32,6 +32,3 @@ flutter: # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true - - assets: - - assets/eng.traineddata diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo.xcodeproj/project.pbxproj b/ios/GSSDKCustomDemo/GSSDKCustomDemo.xcodeproj/project.pbxproj index 6da8381..e3d35b7 100644 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo.xcodeproj/project.pbxproj +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo.xcodeproj/project.pbxproj @@ -8,17 +8,21 @@ /* Begin PBXBuildFile section */ 011C5D8D4D5264B8B7BEBFF7 /* TuistAssets+GSSDKCustomDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65987A8054338986F8D1DFC4 /* TuistAssets+GSSDKCustomDemo.swift */; }; + 2103EF6108AC2AB656A8EE76 /* GSSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 757CB8252E9230816D48949F /* GSSDK */; }; 23C928FD96400455B3282506 /* EditFrameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B2336451E8ABC03C6C45DC9 /* EditFrameViewController.swift */; }; + 2B066D96696E1EC520862EA2 /* StructuredData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622354F6DDA7028954AAF48C /* StructuredData.swift */; }; + 44EF3046DB07CFBF275F65E8 /* PageGenerationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = E0E481D4C86C0DACB40276F8 /* PageGenerationResult.swift */; }; 4659278AD634AAFBAFA97085 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BBD0F1A1BD45B4F77E001F1D /* Images.xcassets */; }; 488961074694470F56BE4FB8 /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAF59C71ECBEFAFDD89775FD /* CameraViewController.swift */; }; 5BA92C7DCC90BD577868FC45 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759990AC6F73BB9A5408BB50 /* AppDelegate.swift */; }; + 6C9444DE0B0035773FE55167 /* StructuredDataResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1631A575CC1BFD919B02B1BD /* StructuredDataResultsView.swift */; }; 734DBB2DF8CFDDA561559FB6 /* TuistBundle+GSSDKCustomDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97AF056FBD86CF23C776AA92 /* TuistBundle+GSSDKCustomDemo.swift */; }; 748EB10FA927326A5DB68005 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8048CD53CD1C215ACF2DC28 /* Storage.swift */; }; AE0716229DB4DF5242AE050F /* PDFViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5743144979A0BD2F8FA8E14D /* PDFViewController.swift */; }; - BC50B46F4BED217510953817 /* tessdata in Resources */ = {isa = PBXBuildFile; fileRef = 569A467E712923206483FCAB /* tessdata */; }; C1E488DA4207F1D4E4DBE56D /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = A799A45B63B71F03B50134F7 /* LaunchScreen.xib */; }; C5BC5FC168E832AF832E6916 /* PDFViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 976987E6BF3B9D0190972025 /* PDFViewController.xib */; }; C6FC26776D2F42AC1DA4E4CA /* PostProcessingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE65189FC9388DD19ADAF39 /* PostProcessingViewController.swift */; }; + ECD2177E6AF1472F3F823BC0 /* ScanResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BCC3268D4BF0B7FB31ED1CA9 /* ScanResultsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -35,10 +39,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 1631A575CC1BFD919B02B1BD /* StructuredDataResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StructuredDataResultsView.swift; sourceTree = ""; }; 1B2336451E8ABC03C6C45DC9 /* EditFrameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFrameViewController.swift; sourceTree = ""; }; 3AE65189FC9388DD19ADAF39 /* PostProcessingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostProcessingViewController.swift; sourceTree = ""; }; - 569A467E712923206483FCAB /* tessdata */ = {isa = PBXFileReference; path = tessdata; sourceTree = ""; }; 5743144979A0BD2F8FA8E14D /* PDFViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFViewController.swift; sourceTree = ""; }; + 622354F6DDA7028954AAF48C /* StructuredData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StructuredData.swift; sourceTree = ""; }; 65987A8054338986F8D1DFC4 /* TuistAssets+GSSDKCustomDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistAssets+GSSDKCustomDemo.swift"; sourceTree = ""; }; 759990AC6F73BB9A5408BB50 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 976987E6BF3B9D0190972025 /* PDFViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PDFViewController.xib; sourceTree = ""; }; @@ -48,7 +53,9 @@ AE36E25358A0A3034086F28F /* GSSDKCustomDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GSSDKCustomDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; BAF59C71ECBEFAFDD89775FD /* CameraViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = ""; }; BBD0F1A1BD45B4F77E001F1D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + BCC3268D4BF0B7FB31ED1CA9 /* ScanResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanResultsView.swift; sourceTree = ""; }; C505F34E7478C46513F9A959 /* GSSDKCustomDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GSSDKCustomDemo-Info.plist"; sourceTree = ""; }; + E0E481D4C86C0DACB40276F8 /* PageGenerationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageGenerationResult.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -56,6 +63,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2103EF6108AC2AB656A8EE76 /* GSSDK in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -68,9 +76,13 @@ 759990AC6F73BB9A5408BB50 /* AppDelegate.swift */, BAF59C71ECBEFAFDD89775FD /* CameraViewController.swift */, 1B2336451E8ABC03C6C45DC9 /* EditFrameViewController.swift */, + E0E481D4C86C0DACB40276F8 /* PageGenerationResult.swift */, 5743144979A0BD2F8FA8E14D /* PDFViewController.swift */, 3AE65189FC9388DD19ADAF39 /* PostProcessingViewController.swift */, + BCC3268D4BF0B7FB31ED1CA9 /* ScanResultsView.swift */, A8048CD53CD1C215ACF2DC28 /* Storage.swift */, + 622354F6DDA7028954AAF48C /* StructuredData.swift */, + 1631A575CC1BFD919B02B1BD /* StructuredDataResultsView.swift */, ); path = Sources; sourceTree = ""; @@ -114,21 +126,12 @@ 9369322A90A048AB0D599E8F /* GSSDKCustomDemo */ = { isa = PBXGroup; children = ( - AE348958324DA1F8E923ECC1 /* ResourceBundles */, 17B50AE61912A6C693F0DE00 /* Resources */, 1081880C2668C04EF6007AA9 /* Sources */, ); path = GSSDKCustomDemo; sourceTree = ""; }; - AE348958324DA1F8E923ECC1 /* ResourceBundles */ = { - isa = PBXGroup; - children = ( - 569A467E712923206483FCAB /* tessdata */, - ); - path = ResourceBundles; - sourceTree = ""; - }; BC7FF8569C7A90E69A89591C /* Project */ = { isa = PBXGroup; children = ( @@ -179,6 +182,9 @@ dependencies = ( ); name = GSSDKCustomDemo; + packageProductDependencies = ( + 757CB8252E9230816D48949F /* GSSDK */, + ); productName = GSSDKCustomDemo; productReference = AE36E25358A0A3034086F28F /* GSSDKCustomDemo.app */; productType = "com.apple.product-type.application"; @@ -203,6 +209,9 @@ en, ); mainGroup = C96CB49B46D39FD74EDB3B3D; + packageReferences = ( + 49294DD01D4FD28862D6CFE5 /* XCRemoteSwiftPackageReference "geniusscan-sdk-spm" */, + ); productRefGroup = CF38E1BE1A331EA70717E25E /* Products */; projectDirPath = ""; projectRoot = ""; @@ -217,7 +226,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - BC50B46F4BED217510953817 /* tessdata in Resources */, 4659278AD634AAFBAFA97085 /* Images.xcassets in Resources */, C1E488DA4207F1D4E4DBE56D /* LaunchScreen.xib in Resources */, C5BC5FC168E832AF832E6916 /* PDFViewController.xib in Resources */, @@ -237,8 +245,12 @@ 488961074694470F56BE4FB8 /* CameraViewController.swift in Sources */, 23C928FD96400455B3282506 /* EditFrameViewController.swift in Sources */, AE0716229DB4DF5242AE050F /* PDFViewController.swift in Sources */, + 44EF3046DB07CFBF275F65E8 /* PageGenerationResult.swift in Sources */, C6FC26776D2F42AC1DA4E4CA /* PostProcessingViewController.swift in Sources */, + ECD2177E6AF1472F3F823BC0 /* ScanResultsView.swift in Sources */, 748EB10FA927326A5DB68005 /* Storage.swift in Sources */, + 2B066D96696E1EC520862EA2 /* StructuredData.swift in Sources */, + 6C9444DE0B0035773FE55167 /* StructuredDataResultsView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -430,6 +442,24 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 49294DD01D4FD28862D6CFE5 /* XCRemoteSwiftPackageReference "geniusscan-sdk-spm" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/thegrizzlylabs/geniusscan-sdk-spm"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = "5.0.0-beta9"; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 757CB8252E9230816D48949F /* GSSDK */ = { + isa = XCSwiftPackageProductDependency; + productName = GSSDK; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = DF2A81FEF4AF47B5C02E628D /* Project object */; } diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/GSSDKCustomDemo/GSSDKCustomDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/ResourceBundles/tessdata/eng.traineddata b/ios/GSSDKCustomDemo/GSSDKCustomDemo/ResourceBundles/tessdata/eng.traineddata deleted file mode 100644 index bbef467..0000000 Binary files a/ios/GSSDKCustomDemo/GSSDKCustomDemo/ResourceBundles/tessdata/eng.traineddata and /dev/null differ diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/AppDelegate.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/AppDelegate.swift index 31873f4..0860ee2 100644 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/AppDelegate.swift +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/AppDelegate.swift @@ -7,10 +7,8 @@ // sdk@thegrizzlylabs.com // - - import UIKit -import GSSDKCore +import GSSDK @main class AppDelegate: UIResponder, UIApplicationDelegate { diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/CameraViewController.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/CameraViewController.swift index 156b506..0e51748 100644 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/CameraViewController.swift +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/CameraViewController.swift @@ -7,10 +7,8 @@ // sdk@thegrizzlylabs.com // - - import UIKit -import GSSDKCore +import GSSDK /** A very simple camera view. @@ -92,21 +90,6 @@ final class CameraViewController: GSKCameraViewController { } } - // MARK: - Camera Session Auto Trigger Related Callbacks - - // - // The following callbacks are part of the document identification. - // You can rely on them to indicate to the user that a document has been detected or - // that the photo is about to be snapped automatically. - // - // This is just a question of UX. The photo will be taken automatically unless you disable - // the auto trigger by setting `autoTrigger = NO` on the camera session. - // - - override func cameraSessionIsSearchingQuadrangle(_ cameraSession: GSKCameraSession) { - super.cameraSessionIsSearchingQuadrangle(cameraSession) - } - override func cameraSessionFailed(toFindQuadrangle cameraSession: GSKCameraSession) { super.cameraSessionFailed(toFindQuadrangle: cameraSession) @@ -165,7 +148,6 @@ final class CameraViewController: GSKCameraViewController { } } - private func setupConstraints() { cameraView.translatesAutoresizingMaskIntoConstraints = false diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/EditFrameViewController.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/EditFrameViewController.swift index faba85e..e24883f 100644 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/EditFrameViewController.swift +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/EditFrameViewController.swift @@ -7,10 +7,8 @@ // sdk@thegrizzlylabs.com // - - import UIKit -import GSSDKCore +import GSSDK final class EditFrameViewController: GSKEditFrameViewController { @@ -56,7 +54,7 @@ final class EditFrameViewController: GSKEditFrameViewController { // We update the display quadrangle if let quadrangle = result?.quadrangle, !quadrangle.isEmpty() { self.quadrangle = quadrangle - } else { + } else { self.quadrangle = GSKQuadrangle.full() } } diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PDFViewController.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PDFViewController.swift index f1011fc..e653da7 100644 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PDFViewController.swift +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PDFViewController.swift @@ -7,11 +7,9 @@ // sdk@thegrizzlylabs.com // - - +import GSSDK +import SwiftUI import UIKit -import GSSDKCore -import GSSDKOCR final class PDFViewController: UIViewController { @@ -34,14 +32,26 @@ final class PDFViewController: UIViewController { } @IBAction func share(_ sender: Any) { - guard let url = generatePDF(nil) else { - print("Cannot generate url") - return + Task { + let result = try await generatePDF() + + present( + UINavigationController(rootViewController: UIHostingController( + rootView: ScanResultsView( + pageResults: result.pages, + onShowPDF: { [weak self] in + self?.dismiss(animated: true) { + self?.showPreviewController(forFileURL: result.fileURL) + } + }, + onDismiss: { [weak self] in + self?.dismiss(animated: true) + } + ) + )), + animated: true + ) } - - previewController = UIDocumentInteractionController(url: url) - previewController?.delegate = self - previewController?.presentPreview(animated: true) } // MARK: - Private @@ -49,28 +59,20 @@ final class PDFViewController: UIViewController { /// This is where the PDF generation happens /// - First, create the information to generate the PDF document /// - Then generate the PDF document - private func generatePDF(_ ocrResult: GSKOCRResult?) -> URL? { - var textLayouts = [String: GSKTextLayout]() + private func generatePDF() async throws -> GenerationResult { + var ocrResults = [String: GSKOCRResult]() // Perform OCR if requested if ocrSwitch.isOn { - let ocrConfiguration = GSKOCRConfiguration() - - // Indicate where the tessdata/ folder is located. This contains - // trained language data. - ocrConfiguration.trainedDataPath = (Bundle.main.resourcePath! as NSString).appendingPathComponent("tessdata") - - // Which language your want to OCR. There must be the corresponding - // .traineddata in the tessdata/ folder. - ocrConfiguration.languageCodes = ["eng"] + let ocrConfiguration = GSKOCRConfiguration.configuration(languageTags: ["en-US"]) for filePath in Storage.shared.filePaths { do { - let result = try GSKOCR.recognizeTextForImage(atPath: filePath, ocrConfiguration: ocrConfiguration, onProgress: { progress in - print("OCR engine progress: %f", progress); + let result = try await GSKOCR().recognizeText(forImageAtPath: filePath, ocrConfiguration: ocrConfiguration, onProgress: { progress in + print("OCR engine progress: %f", progress) }) - textLayouts[filePath] = result.textLayout + ocrResults[filePath] = result } catch { print("Error while OCR'ing page: \(error)") } @@ -82,8 +84,11 @@ final class PDFViewController: UIViewController { // First we generate a list of pages. let pages = Storage.shared.filePaths.map { filePath -> GSKPDFPage in // For each page, we specify the document and a size in inches. - let page = GSKPDFPage(filePath: filePath, inchesSize: GSKPDFSize(width: 8.27, height: 11.69), textLayout: textLayouts[filePath]) - return page + GSKPDFPage( + filePath: filePath, + inchesSize: GSKPDFSize(width: 8.27, height: 11.69), + textLayout: ocrResults[filePath]?.textLayout + ) } // We then create a GSKPDFDocument which holds the general information about the PDF document to generate @@ -96,22 +101,33 @@ final class PDFViewController: UIViewController { let documentsDirectory = paths[0] as NSString let outputFilePath = documentsDirectory.appendingPathComponent("output.pdf") - do { - // Remove before creating. - try FileManager.default.removeItem(atPath: outputFilePath) - } catch { - // Swallow error - } + // Remove before creating. + try? FileManager.default.removeItem(atPath: outputFilePath) do { try GSKPDF.generate(document, toPath: outputFilePath) - return URL(fileURLWithPath: outputFilePath) + + var result = GenerationResult(fileURL: URL(fileURLWithPath: outputFilePath)) + + for filePath in Storage.shared.filePaths { + guard let previewImage = UIImage(contentsOfFile: filePath) else { + continue + } + + try await result.pages.append(PageGenerationResult( + id: filePath, + title: "Page \(result.pages.count + 1)", + previewImage: previewImage, + structuredData: structuredData(fromOCRResult: ocrResults[filePath]) + )) + } + + return result } catch { print("Error while generating the PDF document: \(error)") - return nil + throw error } } - } extension PDFViewController: UIDocumentInteractionControllerDelegate { @@ -128,5 +144,29 @@ extension PDFViewController: UITextFieldDelegate { textField.resignFirstResponder() return true } +} + +private extension PDFViewController { + struct GenerationResult { + var fileURL: URL + var pages = [PageGenerationResult]() + } + func structuredData(fromOCRResult ocrResult: GSKOCRResult?) async throws -> StructuredData? { + guard let ocrResult else { return nil } + + let dataExtractor = GSKStructuredDataExtractor() + + return try await StructuredData( + bankDetails: dataExtractor.bankDetailsFromOCRResult(ocrResult), + businessCardContact: dataExtractor.businessCardContactFromOCRResult(ocrResult), + receipt: dataExtractor.receiptFromOCRResult(ocrResult) + ) + } + + func showPreviewController(forFileURL fileURL: URL) { + previewController = UIDocumentInteractionController(url: fileURL) + previewController?.delegate = self + previewController?.presentPreview(animated: true) + } } diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PageGenerationResult.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PageGenerationResult.swift new file mode 100644 index 0000000..b46262b --- /dev/null +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PageGenerationResult.swift @@ -0,0 +1,14 @@ +import GSSDK + +/// The result of generating a page within a PDF Document. +struct PageGenerationResult: Identifiable { + /// The identifier of the page. Unique within the context of a + /// single instance of the app. + let id: String + /// A displayable title that represents the scanned page. + var title: String + /// An image that can be used to show a preview of the scanned page. + var previewImage: UIImage + /// Any structured data that was extracted from the scanned page. + var structuredData: StructuredData? +} diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PostProcessingViewController.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PostProcessingViewController.swift index 51590ed..8859658 100644 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PostProcessingViewController.swift +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/PostProcessingViewController.swift @@ -7,10 +7,8 @@ // sdk@thegrizzlylabs.com // - - import UIKit -import GSSDKCore +import GSSDK final class PostProcessingViewController: UIViewController { @@ -55,7 +53,7 @@ final class PostProcessingViewController: UIViewController { imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), imageView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor), - imageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), + imageView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) ]) enhancementBarButtonItem = UIBarButtonItem(title: "Filter", style: .plain, target: self, action: #selector(edit)) @@ -83,27 +81,27 @@ final class PostProcessingViewController: UIViewController { let alertController = UIAlertController(title: NSLocalizedString("Choose a filter to apply", comment: ""), message: nil, preferredStyle: .actionSheet) alertController.popoverPresentationController?.barButtonItem = enhancementBarButtonItem - alertController.addAction(UIAlertAction(title: NSLocalizedString("None", comment: ""), style: .default, handler: { action in + alertController.addAction(UIAlertAction(title: NSLocalizedString("None", comment: ""), style: .default, handler: { _ in self.filterType = GSKFilterType.none self.processImage(autodetect: false) })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Monochrome", comment: ""), style: .default, handler: { action in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Monochrome", comment: ""), style: .default, handler: { _ in self.filterType = GSKFilterType.monochrome self.processImage(autodetect: false) })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Black & White", comment: ""), style: .default, handler: { action in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Black & White", comment: ""), style: .default, handler: { _ in self.filterType = GSKFilterType.blackAndWhite self.processImage(autodetect: false) })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Color", comment: ""), style: .default, handler: { action in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Color", comment: ""), style: .default, handler: { _ in self.filterType = GSKFilterType.color self.processImage(autodetect: false) })) - alertController.addAction(UIAlertAction(title: NSLocalizedString("Photo", comment: ""), style: .default, handler: { action in + alertController.addAction(UIAlertAction(title: NSLocalizedString("Photo", comment: ""), style: .default, handler: { _ in self.filterType = GSKFilterType.photo self.processImage(autodetect: false) })) diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/ScanResultsView.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/ScanResultsView.swift new file mode 100644 index 0000000..ba3fc31 --- /dev/null +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/ScanResultsView.swift @@ -0,0 +1,57 @@ +import Contacts +import Foundation +import GSSDK +import SwiftUI + +/// View that shows the results of a scanning session. +struct ScanResultsView: View { + /// The results for each page that was scanned. + var pageResults: [PageGenerationResult] + /// A closure that's run when the user asked to be shown the full + /// generated PDF (including all pages). + var onShowPDF: () -> Void + /// A closure that's run when the user dismissed the view. + var onDismiss: () -> Void + + var body: some View { + List { + Section(content: { + ForEach(pageResults) { pageResult in + if let structuredData = pageResult.structuredData { + NavigationLink(destination: { + StructuredDataResultsView(data: structuredData) + }, label: { + Row(pageResult: pageResult) + }) + } else { + Row(pageResult: pageResult) + } + } + }, header: { + Text("Pages") + }) + } + .navigationBarItems( + leading: Button("Show PDF", action: onShowPDF), + trailing: Button("Done", action: onDismiss) + ) + .navigationBarTitle("Scan results") + } +} + +private extension ScanResultsView { + struct Row: View { + var pageResult: PageGenerationResult + + var body: some View { + HStack(spacing: 12) { + Image(uiImage: pageResult.previewImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxHeight: 44) + + Text(pageResult.title) + } + } + } +} diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/Storage.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/Storage.swift index 05d3f0a..e15f977 100644 --- a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/Storage.swift +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/Storage.swift @@ -7,8 +7,6 @@ // sdk@thegrizzlylabs.com // - - import Foundation final class Storage { diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/StructuredData.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/StructuredData.swift new file mode 100644 index 0000000..ad228a0 --- /dev/null +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/StructuredData.swift @@ -0,0 +1,12 @@ +import GSSDK + +/// Type that encapsulates all structured data that was +/// extracted from a scanned document. +struct StructuredData { + /// Any bank details that were extracted. + var bankDetails: GSKStructuredDataBankDetails? + /// Any contact that was extracted from a business card. + var businessCardContact: GSKStructuredDataContact? + /// Any receipt/invoice info that was extracted. + var receipt: GSKStructuredDataReceipt? +} diff --git a/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/StructuredDataResultsView.swift b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/StructuredDataResultsView.swift new file mode 100644 index 0000000..b89e000 --- /dev/null +++ b/ios/GSSDKCustomDemo/GSSDKCustomDemo/Sources/StructuredDataResultsView.swift @@ -0,0 +1,114 @@ +import GSSDK +import SwiftUI + +/// View that shows any structured data that was extracted +/// from a scanned document. +struct StructuredDataResultsView: View { + var data: StructuredData + + var body: some View { + List { + Section(title: "Bank details") { + if let bankDetails = data.bankDetails { + Row(label: "IBAN", value: bankDetails.iban) + Row(label: "BIC", value: bankDetails.bic) + } else { + ContentUnavailableLabel(text: "No bank details detected") + } + } + + Section(title: "Business card contact") { + if let contact = data.businessCardContact { + Row(label: "Name", value: contact.name) + Row(label: "Phone number", value: contact.phoneNumbers.first?.number) + Row(label: "Email", value: contact.emailAddresses.first) + } else { + ContentUnavailableLabel(text: "No business card contact detected") + } + } + + Section(title: "Receipt") { + if let receipt = data.receipt { + Row(label: "Locale", value: receipt.locale?.identifier) + Row(label: "Merchant", value: receipt.merchant) + Row(label: "Amount", value: receipt.amount.flatMap(Self.numberFormatter.string)) + Row(label: "Currency", value: receipt.currency) + Row(label: "Date", value: receipt.date.flatMap(Self.dateFormatter.string)) + Row(label: "Category", value: receipt.category?.description.capitalized) + } else { + ContentUnavailableLabel(text: "No receipt detected") + } + } + } + .navigationBarTitle("Structured data") + } +} + +private extension StructuredDataResultsView { + struct Section: View { + var title: String + @ViewBuilder var content: () -> Content + + var body: some View { + SwiftUI.Section(content: content, header: { + Text(title) + }) + } + } + + struct Row: View { + var label: String + var value: String? + + var body: some View { + HStack { + Text(label).fontWeight(.medium) + + if let value { + Text(value).frame(maxWidth: .infinity, alignment: .trailing) + } else { + ContentUnavailableLabel( + text: emptyViewLabel, + alignment: .trailing + ) + } + } + } + + private var emptyViewLabel: String { + let subject = if label.allSatisfy(\.isUppercase) { + label + } else { + label.lowercased() + } + + return "No \(subject) detected" + } + } + + struct ContentUnavailableLabel: View { + var text: String + var alignment = Alignment.center + + var body: some View { + Text(text) + .frame(maxWidth: .infinity, alignment: alignment) + .foregroundColor(.secondary) + } + } + + static let numberFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 2 + formatter.alwaysShowsDecimalSeparator = true + return formatter + }() + + static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + return formatter + }() +} diff --git a/ios/GSSDKCustomDemo/Podfile b/ios/GSSDKCustomDemo/Podfile deleted file mode 100644 index 55db74a..0000000 --- a/ios/GSSDKCustomDemo/Podfile +++ /dev/null @@ -1,8 +0,0 @@ -platform :ios, '13.0' - -target 'GSSDKCustomDemo' do - - pod 'GSSDK/Core', :podspec => 'https://s3.amazonaws.com/tgl.geniusscan.sdk/GSSDK-4.21.0.podspec' - pod 'GSSDK/OCR', :podspec => 'https://s3.amazonaws.com/tgl.geniusscan.sdk/GSSDK-4.21.0.podspec' - -end diff --git a/ios/GSSDKCustomDemo/Project.swift b/ios/GSSDKCustomDemo/Project.swift deleted file mode 100644 index c9fa0b1..0000000 --- a/ios/GSSDKCustomDemo/Project.swift +++ /dev/null @@ -1,36 +0,0 @@ -import ProjectDescription - -let project = Project( - name: "GSSDKCustomDemo", - organizationName: "The Grizzly Labs", - targets: [ - Target( - name: "GSSDKCustomDemo", - platform: .iOS, - product: .app, - productName: "GSSDKCustomDemo", - bundleId: "com.thegrizzlylabs.$(PRODUCT_NAME:rfc1034identifier)", - deploymentTarget: .iOS(targetVersion: "13.0", devices: [.ipad, .iphone]), - infoPlist: .extendingDefault(with: - [ - "NSCameraUsageDescription": "The Genius Scan SDK Simple Demo uses the camera for scanning documents.", - "NSLibraryUsageDescription": "The Genius Scan SDK Custom Demo uses the photo library for importing documents.", - "UILaunchStoryboardName": "LaunchScreen" - ] - ), - sources: [ - "GSSDKCustomDemo/Sources/**" - ], - resources: [ - "GSSDKCustomDemo/Resources/**", - .folderReference(path: "GSSDKCustomDemo/ResourceBundles/tessdata") - ] - ) - ], - schemes: [ - Scheme( - name: "GSSDKCustomDemo", - buildAction: .buildAction(targets: ["GSSDKCustomDemo"]) - ) - ] -) diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo.xcodeproj/project.pbxproj b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo.xcodeproj/project.pbxproj index 1115aec..04b467f 100644 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo.xcodeproj/project.pbxproj +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo.xcodeproj/project.pbxproj @@ -8,21 +8,25 @@ /* Begin PBXBuildFile section */ 01E25D56CD2C1338F0CC4C1F /* DocumentScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC643C8DABF3A3535512FCA8 /* DocumentScanningView.swift */; }; + 077619EFCCA6CB81C233DDCD /* DocumentScanningConfigurationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184255C73C44BA77E7B7822E /* DocumentScanningConfigurationViewModel.swift */; }; 0A6C17149369BDB603F34730 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B3BF42E97960DC68CF95707C /* Images.xcassets */; }; 0B9CBB00DC9404778DD240E2 /* TuistAssets+GSSDKSimpleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 870CE4724ECFA4F6A6F20E0F /* TuistAssets+GSSDKSimpleDemo.swift */; }; + 0CBAF2DA059AFE32B0F62D03 /* CustomDocumentScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6345984E9436898218ECCDA4 /* CustomDocumentScanningView.swift */; }; 0DC6E49F947F5411CB3B8BE8 /* image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6BB2F9F74C7BF72AA8DB479D /* image.jpg */; }; 134D5FE28DA30523445C5D0C /* TuistStrings+GSSDKSimpleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCFF3DB59E733E7284379A4 /* TuistStrings+GSSDKSimpleDemo.swift */; }; 386A751C92D7002B39011B7A /* UIApplication+TopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F5530B2CEAB59F2B85CC74 /* UIApplication+TopViewController.swift */; }; - 412FCEE1096C707F3079811B /* ResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE96A239EB93C2D5DBA98512 /* ResultView.swift */; }; 510D7171B193B7B6BC72D1E7 /* TuistBundle+GSSDKSimpleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FB28EC5B6B64022328511C8 /* TuistBundle+GSSDKSimpleDemo.swift */; }; 51E533825C95FF0AD1044E0B /* StructuredDataScanningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79A9FC06A1EB4593A90E07BB /* StructuredDataScanningView.swift */; }; 5273282D4E1E64E040379343 /* bank-identity-document.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 569C5E08719F2B0DDA7B3A48 /* bank-identity-document.jpg */; }; - 539095FA740250434D91BF43 /* tessdata in Resources */ = {isa = PBXBuildFile; fileRef = CB1AFDBE110DB72354424916 /* tessdata */; }; + 53254D0AE0632DBB5DAF659B /* image-2.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 8C973478885A44879F20E8A2 /* image-2.jpg */; }; 57C5DC280360BAB3D9290769 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 268CF675925FBC65F14D7341 /* LaunchScreen.xib */; }; 5DF026B3D191DEDD348DF992 /* DocumentScanningViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9B62238444DCC510A6AFED1 /* DocumentScanningViewController.swift */; }; 850761A76B6F363AB1A58AA2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DC97AD698336C7CEB29563E8 /* Localizable.strings */; }; + 887A913B5767CE572C4A7230 /* GSSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B525BC1A500E8B66A37DF425 /* GSSDK */; }; 9F690F5166A0626689867D02 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63053890F5889186041B68A0 /* MainView.swift */; }; + CE4D3D98107A762391BC32F5 /* StructuredDataResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 695B2A0710043579CEE25045 /* StructuredDataResultsView.swift */; }; E96867F8A4D2BD789606C10B /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C133A7E624B6779BFB5DE516 /* App.swift */; }; + F0698068F5587C8ECE5B0E9B /* DocumentScanningConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7406FC75659CD9AA46A22265 /* DocumentScanningConfigurationView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -39,6 +43,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 184255C73C44BA77E7B7822E /* DocumentScanningConfigurationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanningConfigurationViewModel.swift; sourceTree = ""; }; 268CF675925FBC65F14D7341 /* LaunchScreen.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; 3457F076C2305900F7CFB57C /* GSSDKSimpleDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "GSSDKSimpleDemo-Info.plist"; sourceTree = ""; }; 363602DFFCADF5F6ECE2A6FB /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; @@ -47,12 +52,16 @@ 58CA63038A460FDD9F50ED73 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 5FB28EC5B6B64022328511C8 /* TuistBundle+GSSDKSimpleDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistBundle+GSSDKSimpleDemo.swift"; sourceTree = ""; }; 63053890F5889186041B68A0 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 6345984E9436898218ECCDA4 /* CustomDocumentScanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDocumentScanningView.swift; sourceTree = ""; }; 65FEAE748794BB2A5899BE70 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; 69052FA5E4F67D335F446A3B /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; + 695B2A0710043579CEE25045 /* StructuredDataResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StructuredDataResultsView.swift; sourceTree = ""; }; 6BB2F9F74C7BF72AA8DB479D /* image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = image.jpg; sourceTree = ""; }; + 7406FC75659CD9AA46A22265 /* DocumentScanningConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanningConfigurationView.swift; sourceTree = ""; }; 79A9FC06A1EB4593A90E07BB /* StructuredDataScanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StructuredDataScanningView.swift; sourceTree = ""; }; 81235439599CB95FEEF1BB0D /* GSSDKSimpleDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GSSDKSimpleDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 870CE4724ECFA4F6A6F20E0F /* TuistAssets+GSSDKSimpleDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TuistAssets+GSSDKSimpleDemo.swift"; sourceTree = ""; }; + 8C973478885A44879F20E8A2 /* image-2.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "image-2.jpg"; sourceTree = ""; }; 995119CB28285242FE55E523 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; 99683A20ADC97017330520C1 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; 9E1676537CAAB50A9795B624 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; @@ -60,7 +69,6 @@ A8F5530B2CEAB59F2B85CC74 /* UIApplication+TopViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+TopViewController.swift"; sourceTree = ""; }; A9B62238444DCC510A6AFED1 /* DocumentScanningViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanningViewController.swift; sourceTree = ""; }; ABCCF0E80167BF5D141D6345 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; - AE96A239EB93C2D5DBA98512 /* ResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultView.swift; sourceTree = ""; }; B244AC51FE47CF9C0919307E /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = ""; }; B3BF42E97960DC68CF95707C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; B811891A290F878EE9756EF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -69,7 +77,6 @@ C133A7E624B6779BFB5DE516 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C7DE7A97144495067A582965 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = ""; }; CA944F538970CB7915E8E651 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; - CB1AFDBE110DB72354424916 /* tessdata */ = {isa = PBXFileReference; path = tessdata; sourceTree = ""; }; D547086910C4CFB6F7095332 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = ""; }; DC643C8DABF3A3535512FCA8 /* DocumentScanningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentScanningView.swift; sourceTree = ""; }; DCD108A5770F912CDB2C64D0 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; @@ -85,6 +92,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 887A913B5767CE572C4A7230 /* GSSDK in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -95,10 +103,13 @@ isa = PBXGroup; children = ( C133A7E624B6779BFB5DE516 /* App.swift */, + 6345984E9436898218ECCDA4 /* CustomDocumentScanningView.swift */, + 7406FC75659CD9AA46A22265 /* DocumentScanningConfigurationView.swift */, + 184255C73C44BA77E7B7822E /* DocumentScanningConfigurationViewModel.swift */, DC643C8DABF3A3535512FCA8 /* DocumentScanningView.swift */, A9B62238444DCC510A6AFED1 /* DocumentScanningViewController.swift */, 63053890F5889186041B68A0 /* MainView.swift */, - AE96A239EB93C2D5DBA98512 /* ResultView.swift */, + 695B2A0710043579CEE25045 /* StructuredDataResultsView.swift */, 79A9FC06A1EB4593A90E07BB /* StructuredDataScanningView.swift */, A8F5530B2CEAB59F2B85CC74 /* UIApplication+TopViewController.swift */, ); @@ -108,7 +119,6 @@ 86990752452927AB74BBF23A /* GSSDKSimpleDemo */ = { isa = PBXGroup; children = ( - 9B84625F91C1873081BC724A /* ResourceBundles */, E82A30F9FB7410B604DF38E0 /* Resources */, 51A4AAB265659D17B0D1D349 /* Sources */, ); @@ -122,14 +132,6 @@ name = Frameworks; sourceTree = ""; }; - 9B84625F91C1873081BC724A /* ResourceBundles */ = { - isa = PBXGroup; - children = ( - CB1AFDBE110DB72354424916 /* tessdata */, - ); - path = ResourceBundles; - sourceTree = ""; - }; AEFA0EEB94E4B4A7A2504575 = { isa = PBXGroup; children = ( @@ -160,6 +162,7 @@ children = ( DC97AD698336C7CEB29563E8 /* Localizable.strings */, 569C5E08719F2B0DDA7B3A48 /* bank-identity-document.jpg */, + 8C973478885A44879F20E8A2 /* image-2.jpg */, 6BB2F9F74C7BF72AA8DB479D /* image.jpg */, B3BF42E97960DC68CF95707C /* Images.xcassets */, 268CF675925FBC65F14D7341 /* LaunchScreen.xib */, @@ -212,6 +215,9 @@ dependencies = ( ); name = GSSDKSimpleDemo; + packageProductDependencies = ( + B525BC1A500E8B66A37DF425 /* GSSDK */, + ); productName = GSSDKSimpleDemo; productReference = 81235439599CB95FEEF1BB0D /* GSSDKSimpleDemo.app */; productType = "com.apple.product-type.application"; @@ -257,6 +263,9 @@ "zh-Hant", ); mainGroup = AEFA0EEB94E4B4A7A2504575; + packageReferences = ( + A3B41AE94C68FF7FE45D07F0 /* XCRemoteSwiftPackageReference "geniusscan-sdk-spm" */, + ); productRefGroup = D34D1866954E4B5E6ECE14D9 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -271,11 +280,11 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 539095FA740250434D91BF43 /* tessdata in Resources */, 0A6C17149369BDB603F34730 /* Images.xcassets in Resources */, 57C5DC280360BAB3D9290769 /* LaunchScreen.xib in Resources */, 850761A76B6F363AB1A58AA2 /* Localizable.strings in Resources */, 5273282D4E1E64E040379343 /* bank-identity-document.jpg in Resources */, + 53254D0AE0632DBB5DAF659B /* image-2.jpg in Resources */, 0DC6E49F947F5411CB3B8BE8 /* image.jpg in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -291,10 +300,13 @@ 510D7171B193B7B6BC72D1E7 /* TuistBundle+GSSDKSimpleDemo.swift in Sources */, 134D5FE28DA30523445C5D0C /* TuistStrings+GSSDKSimpleDemo.swift in Sources */, E96867F8A4D2BD789606C10B /* App.swift in Sources */, + 0CBAF2DA059AFE32B0F62D03 /* CustomDocumentScanningView.swift in Sources */, + F0698068F5587C8ECE5B0E9B /* DocumentScanningConfigurationView.swift in Sources */, + 077619EFCCA6CB81C233DDCD /* DocumentScanningConfigurationViewModel.swift in Sources */, 01E25D56CD2C1338F0CC4C1F /* DocumentScanningView.swift in Sources */, 5DF026B3D191DEDD348DF992 /* DocumentScanningViewController.swift in Sources */, 9F690F5166A0626689867D02 /* MainView.swift in Sources */, - 412FCEE1096C707F3079811B /* ResultView.swift in Sources */, + CE4D3D98107A762391BC32F5 /* StructuredDataResultsView.swift in Sources */, 51E533825C95FF0AD1044E0B /* StructuredDataScanningView.swift in Sources */, 386A751C92D7002B39011B7A /* UIApplication+TopViewController.swift in Sources */, ); @@ -520,6 +532,24 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + A3B41AE94C68FF7FE45D07F0 /* XCRemoteSwiftPackageReference "geniusscan-sdk-spm" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/thegrizzlylabs/geniusscan-sdk-spm"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = "5.0.0-beta9"; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + B525BC1A500E8B66A37DF425 /* GSSDK */ = { + isa = XCSwiftPackageProductDependency; + productName = GSSDK; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = AF930071A425450AEC267751 /* Project object */; } diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/ResourceBundles/tessdata/eng.traineddata b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/ResourceBundles/tessdata/eng.traineddata deleted file mode 100644 index bbef467..0000000 Binary files a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/ResourceBundles/tessdata/eng.traineddata and /dev/null differ diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Resources/image-2.jpg b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Resources/image-2.jpg new file mode 100644 index 0000000..84137f7 Binary files /dev/null and b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Resources/image-2.jpg differ diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Resources/image.jpg b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Resources/image.jpg index c91ce21..68052a2 100644 Binary files a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Resources/image.jpg and b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Resources/image.jpg differ diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/App.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/App.swift index 6f90f21..1081973 100644 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/App.swift +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/App.swift @@ -4,7 +4,7 @@ // import Foundation -import GSSDKScanFlow +import GSSDK import UIKit import SwiftUI @@ -23,7 +23,7 @@ struct GSKSDKStructuredDemoApp: App { class AppDelegate: NSObject, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // Set the SDK license key as early as possible to give it a chance to refresh // the license key in case it's expired (auto-refresh behavior can also be disabled with diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/CustomDocumentScanningView.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/CustomDocumentScanningView.swift new file mode 100644 index 0000000..71e1b44 --- /dev/null +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/CustomDocumentScanningView.swift @@ -0,0 +1,123 @@ +// +// Genius Scan SDK +// +// Copyright 2010-2023 The Grizzly Labs +// +// Subject to the Genius Scan SDK Licensing Agreement +// sdk@thegrizzlylabs.com +// + +import Foundation +import GSSDK +import UIKit +import SwiftUI + +// - Demonstrates how to start a scan flow by directly calling `GSKScanFlow`. +// - Demonstrates the possible scan flow customizations. +struct CustomDocumentScanningView: View { + @State private var scanFlow: GSKScanFlow? + @State private var showAlert = false + @State private var error: Error? + @StateObject private var viewModel = DocumentScanningViewModel() + + // We use the document interaction controller to show the result of the scanning + // but you likely don't need that in your app. + @State private var documentInteractionControllerDelegate = DocumentInteractionControllerDelegate() + + var body: some View { + NavigationView { + Form { + Text( + """ + This view demonstrates the various customizations of the scan flow. + + Check the GSKScanFlowConfiguration documentation for all of the possible customizations. + """ + ) + .listRowBackground(Color.clear) + .font(.footnote) + .foregroundColor(.gray) + + DocumentScanningConfigurationView(viewModel: viewModel) + } + } + .navigationBarItems(trailing: Button("Scan", action: scan)) + .alert( + error?.localizedDescription ?? "", + isPresented: $showAlert, + actions: { + Button("OK") { error = nil } + }, + message: { + if let suggestion = (error as NSError?)?.localizedRecoverySuggestion { + Text(suggestion) + } + } + ) + } + + private func scan() { + guard let topViewController = UIApplication.shared.topViewController else { + fatalError("No view controller to start scan flow from") + } + + // Start the scan flow with a configuration. Here we retrieve the configuration + // from the view model, but you probably want to hardcode it in your code. + // Notice how we need to keep a strong reference on the scan flow. + // You'd call the scan flow in the exact same way from UIKit. + scanFlow = GSKScanFlow(configuration: viewModel.configuration) + scanFlow?.start(from: topViewController, onSuccess: { result in + /* + Do what you need with the PDF. As an example, we display it: + */ + if let multiPageDocumentURL = result.multiPageDocumentURL { + print("Here is the document: \(multiPageDocumentURL)") + let previewController = UIDocumentInteractionController(url: multiPageDocumentURL) + previewController.delegate = documentInteractionControllerDelegate + previewController.presentPreview(animated: true) + } + + /* + Alternatively, you could access the individual JPEG scans as follows: + ``` + results.scans + ``` + */ + }, failure: { error in + print("An error happened: \(error)") + + self.showAlert = true + self.error = error + }) + } +} + +extension CustomDocumentScanningView { + private enum AlertError { + case noError + case error(String) + + var isError: Bool { + switch self { + case .noError: false + case .error: true + } + } + + var message: String? { + switch self { + case .noError: nil + case .error(let message): message + } + } + } +} + +private class DocumentInteractionControllerDelegate: NSObject, UIDocumentInteractionControllerDelegate { + func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController { + guard let topViewController = UIApplication.shared.topViewController else { + fatalError("No view controller to present results from") + } + return topViewController + } +} diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningConfigurationView.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningConfigurationView.swift new file mode 100644 index 0000000..1b2e675 --- /dev/null +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningConfigurationView.swift @@ -0,0 +1,108 @@ +// +// GSSDKSimpleDemo +// +// Created by Bruno Virlet on 23/01/2024. +// Copyright © 2024 The Grizzly Labs. All rights reserved. +// + +import Foundation +import SwiftUI +import GSSDK + +// A view that lets you toggle the scan flow configuration in the UI. +// +// You don't need this view when using the actual Scan Flow demo. +struct DocumentScanningConfigurationView: View { + @ObservedObject var viewModel: DocumentScanningViewModel + + var body: some View { + Section { + Picker("Source", selection: $viewModel.source) { + ForEach(GSKScanFlowSource.allCases, id: \.self) { source in + Text(source.description).tag(source) + } + + } + } + + Section("Camera screen") { + Toggle("Display flash button", isOn: Binding(get: { + !viewModel.flashButtonHidden + }, set: { displayed in + viewModel.flashButtonHidden = !displayed + })) + Picker("Default flash mode", selection: $viewModel.defaultFlashMode) { + ForEach(GSKScanFlowFlashMode.allCases, id: \.self) { mode in + Text(mode.description).tag(mode) + } + } + } + + Picker("Default filter", selection: $viewModel.defaultFilter) { + ForEach(GSKScanFlowFilterType.allCases, id: \.self) { type in + Text(type.description).tag(type) + } + + } + + Section("Enabled post-processing actions") { + Toggle("Change filter", isOn: viewModel.bindingForPostProcessingAction(.editFilter)) + Toggle("Rotate", isOn: viewModel.bindingForPostProcessingAction(.rotate)) + Toggle("Correct distortion", isOn: viewModel.bindingForPostProcessingAction(.distortionCorrection)) + } + + Section("Output") { + Toggle("Multipage", isOn: $viewModel.multiPage) + + Picker("Format", selection: $viewModel.multiPageFormat) { + Text("PDF").tag(GSKScanFlowMultiPageFormat.PDF) + Text("TIFF").tag(GSKScanFlowMultiPageFormat.TIFF) + } + + Toggle("Resize scans in PDF", isOn: viewModel.bindingForPDFMaxScanDimensionEnabled()) + + if viewModel.pdfMaxScanDimension != 0 { + HStack { + Text("Max size") + Slider(value: viewModel.bindingForPDFMaxScanDimension(), in: 0...10000.0) + Text("\(viewModel.pdfMaxScanDimension)") + } + } + + Picker("Page size", selection: $viewModel.pdfPageSize) { + Text("Fit").tag(GSKScanFlowPDFPageSize.fit) + Text("Letter").tag(GSKScanFlowPDFPageSize.letter) + Text("A4").tag(GSKScanFlowPDFPageSize.A4) + } + } + + Section("UI") { + ColorPicker("Foreground color", selection: $viewModel.foregroundColor.uiColor()) + ColorPicker("Background color", selection: $viewModel.backgroundColor.uiColor()) + ColorPicker("Highlight color", selection: $viewModel.highlightColor.uiColor()) + ColorPicker("Menu color", selection: $viewModel.menuColor.uiColor(withDefault: .tintColor)) + } + + Section("OCR") { + Toggle("OCR", isOn: viewModel.bindingForOCR()) + } + } +} + +private extension Binding where Value == UIColor { + func uiColor() -> Binding { + Binding( + get: { Color(uiColor: self.wrappedValue) }, + set: { self.wrappedValue = UIColor($0) } + ) + } +} + +private extension Binding where Value == UIColor? { + func uiColor(withDefault defaultColor: UIColor) -> Binding { + Binding( + get: { Color(uiColor: self.wrappedValue ?? defaultColor) }, + set: { self.wrappedValue = UIColor($0) } + ) + } +} diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningConfigurationViewModel.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningConfigurationViewModel.swift new file mode 100644 index 0000000..7677e4e --- /dev/null +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningConfigurationViewModel.swift @@ -0,0 +1,64 @@ +// +// GSSDKSimpleDemo +// +// Created by Bruno Virlet on 23/01/2024. +// Copyright © 2024 The Grizzly Labs. All rights reserved. +// + +import Foundation +import GSSDK +import SwiftUI + +@dynamicMemberLookup +final class DocumentScanningViewModel: ObservableObject { + @Published var configuration = GSKScanFlowConfiguration() + + subscript(dynamicMember keyPath: WritableKeyPath) -> T { + get { configuration[keyPath: keyPath] } + set { configuration[keyPath: keyPath] = newValue } + } + + func bindingForPostProcessingAction(_ action: GSKScanFlowPostProcessingActions) -> Binding { + Binding(get: { + self.postProcessingActions.contains(action) + }, set: { enabled in + var postProcessingActions = self.postProcessingActions + if enabled { + postProcessingActions.insert(action) + } else { + postProcessingActions.remove(action) + } + self.postProcessingActions = postProcessingActions + }) + } + + func bindingForPDFMaxScanDimensionEnabled() -> Binding { + Binding(get: { + self.pdfMaxScanDimension != 0 + }, set: { enabled in + self.pdfMaxScanDimension = enabled ? 1000 : 0 + }) + } + + func bindingForPDFMaxScanDimension() -> Binding { + Binding(get: { + Double(self.pdfMaxScanDimension) + }, set: { value in + self.pdfMaxScanDimension = Int(value) + }) + } + + func bindingForOCR() -> Binding { + Binding(get: { + self.ocrConfiguration != nil + }, set: { enabled in + if enabled { + let ocrConfiguration = GSKScanFlowOCRConfiguration() + ocrConfiguration.languageTags = ["en-US"] + self.ocrConfiguration = ocrConfiguration + } else { + self.ocrConfiguration = nil + } + }) + } +} diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningView.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningView.swift index 0ff1c78..731234f 100644 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningView.swift +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningView.swift @@ -1,8 +1,8 @@ import Foundation -import GSSDKScanFlow +import GSSDK import SwiftUI -// This view is just a bridge between SwiftUI and UIKit. +// This view is just a bridge between SwiftUI and UIKit. // // If you are implementing the ScanFlow with UIKit, you can simply look at // `DocumentScanningViewController` which is self-contained. @@ -10,7 +10,7 @@ struct DocumentScanningView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> DocumentScanningViewController { return DocumentScanningViewController() } - + func updateUIViewController(_ uiViewController: DocumentScanningViewController, context: Context) { } } diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningViewController.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningViewController.swift index 04b7225..39fac29 100644 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningViewController.swift +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/DocumentScanningViewController.swift @@ -8,7 +8,7 @@ // import Foundation -import GSSDKScanFlow +import GSSDK import UIKit final class DocumentScanningViewController: UITableViewController, UIDocumentInteractionControllerDelegate { @@ -23,12 +23,12 @@ final class DocumentScanningViewController: UITableViewController, UIDocumentInt init() { super.init(style: .insetGrouped) } - + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() @@ -85,8 +85,7 @@ final class DocumentScanningViewController: UITableViewController, UIDocumentInt configuration.foregroundColor = .red configuration.multiPageFormat = .PDF let ocrConfiguration = GSKScanFlowOCRConfiguration() - ocrConfiguration.languageCodes = ["eng"] - ocrConfiguration.trainedDataPath = (Bundle.main.resourcePath! as NSString).appendingPathComponent("tessdata") + ocrConfiguration.languageTags = ["en-US"] configuration.ocrConfiguration = ocrConfiguration // Instantiate a scan flow with the configuration @@ -111,7 +110,11 @@ final class DocumentScanningViewController: UITableViewController, UIDocumentInt }, failure: { [weak self] error in print("An error happened: \(error)") - let alert = UIAlertController(title: error.localizedDescription, message: nil, preferredStyle: .alert) + let alert = UIAlertController( + title: error.localizedDescription, + message: (error as NSError).localizedRecoverySuggestion, + preferredStyle: .alert + ) alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) self?.present(alert, animated: true, completion: nil) }) diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/MainView.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/MainView.swift index 79b4d83..c4e6a9a 100644 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/MainView.swift +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/MainView.swift @@ -8,7 +8,7 @@ // import Foundation -import GSSDKScanFlow +import GSSDK import SwiftUI /** @@ -22,26 +22,56 @@ struct MainView: View { var body: some View { NavigationView { List { - NavigationLink { + Row( + title: "Document scanning", + subtitle: "Default scan flow", + imageName: "doc.viewfinder" + ) { DocumentScanningView() - .edgesIgnoringSafeArea(.all) - .navigationBarTitle("Document scanning") - } label: { - HStack { - Image(systemName: "doc.viewfinder") - Text("Document scanning") - } } - NavigationLink { + Row( + title: "Document scanning", + subtitle: "Customizable scan flow", + imageName: "doc.viewfinder.fill" + ) { + CustomDocumentScanningView() + } + + Row( + title: "Structured data scanning", + imageName: "creditcard.viewfinder" + ) { StructuredDataScanningView() - .navigationBarTitle("Structured data scanning") - } label: { - Image(systemName: "creditcard.viewfinder") - Text("Structured data scanning") } } .navigationBarTitle("Genius Scan SDK Simple Demo", displayMode: .inline) } } + + private struct Row: View { + var title: String + var subtitle: String? + var imageName: String + + @ViewBuilder var content: DetailView + + var body: some View { + NavigationLink { + content + .navigationBarTitle(title) + } label: { + HStack { + Image(systemName: imageName) + VStack(alignment: .leading) { + Text(title) + if let subtitle { + Text(subtitle).foregroundStyle(.gray).font(.caption) + } + } + + } + } + } + } } diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/ResultView.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/ResultView.swift deleted file mode 100644 index ef0208c..0000000 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/ResultView.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Genius Scan SDK -// -// Copyright 2010-2023 The Grizzly Labs -// -// Subject to the Genius Scan SDK Licensing Agreement -// sdk@thegrizzlylabs.com -// - -import Foundation -import GSSDKScanFlow -import SwiftUI - -/** - This ResultView shows how you can display the results of a structured data extraction from ScanFlow. - */ -struct ResultView: View { - var scan: GSKScanFlowScan - var onRetry: () -> Void - - var body: some View { - List { - Section { - if let image = UIImage(contentsOfFile: scan.enhancedFilePath) { - Image(uiImage: image).resizable().aspectRatio(contentMode: .fit) - } else { - Text("Couldn't find enhanced image") - } - } - - Section("Structured data") { - HStack { - Text("IBAN").fontWeight(.medium) - Text(scan.structuredDataResult?.bankDetails?.iban ?? "No IBAN detected") - .frame(maxWidth: .infinity, alignment: .trailing) - } - - HStack { - Text("BICs").fontWeight(.medium) - Text(scan.structuredDataResult?.bankDetails?.bic ?? "No BIC detected") - .frame(maxWidth: .infinity, alignment: .trailing) - } - } - - Section { - Button("Retry", action: onRetry) - } - } - } -} diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/StructuredDataResultsView.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/StructuredDataResultsView.swift new file mode 100644 index 0000000..df867d9 --- /dev/null +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/StructuredDataResultsView.swift @@ -0,0 +1,129 @@ +// +// Genius Scan SDK +// +// Copyright 2010-2023 The Grizzly Labs +// +// Subject to the Genius Scan SDK Licensing Agreement +// sdk@thegrizzlylabs.com +// + +import Contacts +import Foundation +import GSSDK +import SwiftUI + +/** + This view shows how you can display the results of a structured data extraction from ScanFlow. + */ +struct StructuredDataResultsView: View { + var scan: GSKScanFlowScan + var onRetry: () -> Void + + var body: some View { + List { + Section { + if let image = UIImage(contentsOfFile: scan.enhancedFilePath) { + Image(uiImage: image).resizable().aspectRatio(contentMode: .fit) + } else { + Text("Couldn't find enhanced image") + } + } + + if let result = scan.structuredDataResult { + Section("Bank details") { + if let bankDetails = result.bankDetails { + Row(label: "IBAN", value: bankDetails.iban) + Row(label: "BIC", value: bankDetails.bic) + } else { + ContentUnavailableLabel(text: "No bank details detected") + } + } + + Section("Business card contact") { + if let contact = result.businessCardContact { + Row(label: "Name", value: contact.name) + Row(label: "Phone number", value: contact.phoneNumbers.first?.number) + Row(label: "Email", value: contact.emailAddresses.first) + } else { + ContentUnavailableLabel(text: "No business card contact detected") + } + } + + Section("Receipt") { + if let receipt = result.receipt { + Row(label: "Locale", value: receipt.locale?.identifier) + Row(label: "Merchant", value: receipt.merchant) + Row(label: "Amount", value: receipt.amount.flatMap(Self.numberFormatter.string)) + Row(label: "Currency", value: receipt.currency) + Row(label: "Date", value: receipt.date.flatMap(Self.dateFormatter.string)) + Row(label: "Category", value: receipt.category?.description.capitalized) + } else { + ContentUnavailableLabel(text: "No receipt detected") + } + } + } else { + ContentUnavailableLabel(text: "No structured data detected") + } + + Button("Retry", action: onRetry) + } + } +} + +private extension StructuredDataResultsView { + struct Row: View { + var label: String + var value: String? + + var body: some View { + HStack { + Text(label).fontWeight(.medium) + + if let value { + Text(value).frame(maxWidth: .infinity, alignment: .trailing) + } else { + ContentUnavailableLabel( + text: emptyViewLabel, + alignment: .trailing + ) + } + } + } + + private var emptyViewLabel: String { + let subject = if label.allSatisfy(\.isUppercase) { + label + } else { + label.lowercased() + } + + return "No \(subject) detected" + } + } + + struct ContentUnavailableLabel: View { + var text: String + var alignment = Alignment.center + + var body: some View { + Text(text) + .frame(maxWidth: .infinity, alignment: alignment) + .foregroundStyle(.secondary) + } + } + + static let numberFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + formatter.minimumFractionDigits = 2 + formatter.maximumFractionDigits = 2 + formatter.alwaysShowsDecimalSeparator = true + return formatter + }() + + static let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateStyle = .medium + return formatter + }() +} diff --git a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/StructuredDataScanningView.swift b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/StructuredDataScanningView.swift index 958156b..cbc4532 100644 --- a/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/StructuredDataScanningView.swift +++ b/ios/GSSDKSimpleDemo/GSSDKSimpleDemo/Sources/StructuredDataScanningView.swift @@ -1,7 +1,9 @@ import Foundation -import GSSDKScanFlow +import GSSDK import SwiftUI +/// - Demonstrates a configuration for structured data scanning. +/// - Demonstrates how to present the scan flow from SwiftUI with `GSKScanFlowButton`. struct StructuredDataScanningView: View { @State private var scanFlowResult: Result? @@ -9,7 +11,7 @@ struct StructuredDataScanningView: View { if let result = scanFlowResult { switch result { case .failure(let error): - List { + List { Section { Text(error.localizedDescription) } @@ -21,12 +23,12 @@ struct StructuredDataScanningView: View { } } case .success(let scan): - ResultView(scan: scan, onRetry: clear) + StructuredDataResultsView(scan: scan, onRetry: clear) } } else { List { Section( - footer: Text("This view demonstrates how to start a scan flow in structured data scanning extraction mode, and how to integrate the scan flow with SwiftUI.") + footer: Text("This view demonstrates how to start a scan flow in structured data scanning extraction mode, and how to integrate the scan flow with the SwiftUI view GSKScanFlowButton.") ) { GSKScanFlowButton( "Scan with camera", @@ -70,7 +72,7 @@ private extension StructuredDataScanningView { configuration.multiPage = false configuration.skipPostProcessingScreen = true - configuration.structuredData = .bankDetails + configuration.structuredData = [.bankDetails, .businessCard, .receipt] return configuration } diff --git a/ios/GSSDKSimpleDemo/Podfile b/ios/GSSDKSimpleDemo/Podfile deleted file mode 100644 index 4289a76..0000000 --- a/ios/GSSDKSimpleDemo/Podfile +++ /dev/null @@ -1,9 +0,0 @@ -platform :ios, '15.0' - -target 'GSSDKSimpleDemo' do - - pod 'GSSDK/Core', :podspec => 'https://s3.amazonaws.com/tgl.geniusscan.sdk/GSSDK-4.21.0.podspec' - pod 'GSSDK/ScanFlow', :podspec => 'https://s3.amazonaws.com/tgl.geniusscan.sdk/GSSDK-4.21.0.podspec' - pod 'GSSDK/OCR', :podspec => 'https://s3.amazonaws.com/tgl.geniusscan.sdk/GSSDK-4.21.0.podspec' - -end diff --git a/ios/GSSDKSimpleDemo/Project.swift b/ios/GSSDKSimpleDemo/Project.swift deleted file mode 100644 index 3d7d283..0000000 --- a/ios/GSSDKSimpleDemo/Project.swift +++ /dev/null @@ -1,37 +0,0 @@ -import ProjectDescription - -let project = Project( - name: "GSSDKSimpleDemo", - organizationName: "The Grizzly Labs", - targets: [ - Target( - name: "GSSDKSimpleDemo", - platform: .iOS, - product: .app, - productName: "GSSDKSimpleDemo", - bundleId: "com.thegrizzlylabs.$(PRODUCT_NAME:rfc1034identifier)", - deploymentTarget: .iOS(targetVersion: "15.0", devices: [.ipad, .iphone]), - infoPlist: .extendingDefault(with: - [ - "UIMainStoryboardFile": "Main", - "NSCameraUsageDescription": "The Genius Scan SDK Simple Demo uses the camera for scanning documents.", - "NSLibraryUsageDescription": "The Genius Scan SDK Simple Demo uses the photo library for importing documents.", - "UILaunchStoryboardName": "LaunchScreen" - ] - ), - sources: [ - "GSSDKSimpleDemo/Sources/**" - ], - resources: [ - "GSSDKSimpleDemo/Resources/**", - .folderReference(path: "GSSDKSimpleDemo/ResourceBundles/tessdata") - ] - ) - ], - schemes: [ - Scheme( - name: "GSSDKSimpleDemo", - buildAction: .buildAction(targets: ["GSSDKSimpleDemo"]) - ) - ] -) diff --git a/ios/README.md b/ios/README.md index 1b6df70..9fd1247 100644 --- a/ios/README.md +++ b/ios/README.md @@ -1,20 +1,14 @@ # Genius Scan SDK iOS demo apps -These are demo apps for the Genius Scan iOS SDK. Check out the [SDK documentation](https://geniusscansdk.com/docs/ios/) to see how to integrate it into your own application. +The demos apps available in our [demo repository](https://github.com/thegrizzlylabs/geniusscan-sdk-demo/tree/master/ios) demonstrate how to integrate the SDK. -## Requirements +## Simple demo -The demo apps support iOS 11 and above. +The simple demo demonstrates the ``GSKScanFlow`` integration that lets you integrate a scanning module in your app with just a few lines of code. Customization of the scan flow is possible with ``GSKScanFlowConfiguration``. -## Installation +## Custom demo -1. Open one of the demo projects. - -2. Run `pod install` to install the SDK frameworks. - -3. Run `xed .` to open the workspace in XCode. - -4. Build and run on a device or a simulator. +The custom demo demonstrates how to build an entirely custom scanning experience by relying on lower-level components such as our ``GSKCameraViewController`` (a view controller that lets you scan a document), ``GSKEditFrameViewController`` (a view controller that lets you edit the cropping of a document) and ``GSKScanProcessor`` which lets you process scans as you want. The custom demo also shows the usage of ``GSKOCR`` if you want to perform text recognition on your scans. ## Licensing diff --git a/react-native-genius-scan-demo/App.js b/react-native-genius-scan-demo/App.js index be3245b..39514d6 100644 --- a/react-native-genius-scan-demo/App.js +++ b/react-native-genius-scan-demo/App.js @@ -27,16 +27,13 @@ import { import FileViewer from 'react-native-file-viewer'; import RNGeniusScan from '@thegrizzlylabs/react-native-genius-scan'; -import RNFS from 'react-native-fs'; const App = () => { - const appFolder = Platform.OS === 'android' ? RNFS.ExternalDirectoryPath : RNFS.LibraryDirectoryPath; // Refer to the Genius Scan SDK demo README.md for a list of the available options const configuration = { source: 'camera', ocrConfiguration: { - languages: ['eng'], - languagesDirectoryUrl: appFolder + languages: ['en-US'] } } @@ -61,17 +58,7 @@ const App = () => {