diff --git a/.changes/config.json b/.changes/config.json index 655a946ac..146d1e321 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -39,6 +39,7 @@ "publish": false, "dependencies": [ "app", + "barcode-scanner", "log-plugin", "cli", "clipboard-manager", @@ -60,6 +61,7 @@ "publish": false, "dependencies": [ "app-js", + "barcode-scanner-js", "log-js", "cli-js", "clipboard-manager-js", @@ -112,6 +114,15 @@ "manager": "javascript" }, + "barcode-scanner": { + "path": "./plugins/barcode-scanner", + "manager": "rust" + }, + "barcode-scanner-js": { + "path": "./plugins/barcode-scanner", + "manager": "javascript" + }, + "cli": { "path": "./plugins/cli", "manager": "rust" diff --git a/.changes/scanner-initial-release.md b/.changes/scanner-initial-release.md new file mode 100644 index 000000000..5ea48e326 --- /dev/null +++ b/.changes/scanner-initial-release.md @@ -0,0 +1,6 @@ +--- +"barcode-scanner": major +"barcode-scanner-js": major +--- + +Initial release. diff --git a/.prettierignore b/.prettierignore index f49381af2..630daf67e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,4 +6,5 @@ pnpm-lock.yaml Cargo.lock .build build -api-iife.js \ No newline at end of file +api-iife.js +intermediates/ \ No newline at end of file diff --git a/.scripts/ci/check-license-header.js b/.scripts/ci/check-license-header.js index ee6ef3363..a322957d1 100644 --- a/.scripts/ci/check-license-header.js +++ b/.scripts/ci/check-license-header.js @@ -21,6 +21,7 @@ const ignore = [ "dist-js", ".svelte-kit", "api-iife.js", + ".build", ]; async function checkFile(file) { @@ -70,7 +71,9 @@ async function checkFile(file) { async function check(src) { const missingHeader = []; - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + for (const entry of fs.readdirSync(src, { + withFileTypes: true, + })) { const p = path.join(src, entry.name); if (entry.isSymbolicLink() || ignore.includes(entry.name)) { diff --git a/Cargo.lock b/Cargo.lock index 6390c7921..0df192b75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,7 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-app", + "tauri-plugin-barcode-scanner", "tauri-plugin-cli", "tauri-plugin-clipboard-manager", "tauri-plugin-dialog", @@ -5678,6 +5679,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "tauri-plugin-barcode-scanner" +version = "1.0.0" +dependencies = [ + "log", + "serde", + "serde_json", + "tauri", + "tauri-build", + "thiserror", +] + [[package]] name = "tauri-plugin-cli" version = "2.0.0-alpha.2" diff --git a/examples/api/package.json b/examples/api/package.json index b90a5adcf..ddb0594b5 100644 --- a/examples/api/package.json +++ b/examples/api/package.json @@ -11,6 +11,7 @@ "dependencies": { "@tauri-apps/api": "2.0.0-alpha.8", "@tauri-apps/plugin-app": "2.0.0-alpha.1", + "@tauri-apps/plugin-barcode-scanner": "1.0.0", "@tauri-apps/plugin-cli": "2.0.0-alpha.1", "@tauri-apps/plugin-clipboard-manager": "2.0.0-alpha.1", "@tauri-apps/plugin-dialog": "2.0.0-alpha.1", diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index b364dc900..4f9a62267 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -46,6 +46,9 @@ tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.0.0-alpha.2" } tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.0.0-alpha.2" } tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.0.0-alpha.2" } +[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies] +tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "1.0.0" } + [target."cfg(target_os = \"windows\")".dependencies] window-shadows = "0.2" diff --git a/examples/api/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/examples/api/src-tauri/gen/android/app/src/main/res/values-night/themes.xml index dc82b8d87..f03785271 100644 --- a/examples/api/src-tauri/gen/android/app/src/main/res/values-night/themes.xml +++ b/examples/api/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -1,16 +1,6 @@ - diff --git a/examples/api/src-tauri/gen/android/app/src/main/res/values/themes.xml b/examples/api/src-tauri/gen/android/app/src/main/res/values/themes.xml index 0f71fd44d..f03785271 100644 --- a/examples/api/src-tauri/gen/android/app/src/main/res/values/themes.xml +++ b/examples/api/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -1,16 +1,6 @@ - diff --git a/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt b/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt index f98748258..29b0f2afa 100644 --- a/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt +++ b/examples/api/src-tauri/gen/android/buildSrc/src/main/java/com/tauri/api/kotlin/BuildTask.kt @@ -16,7 +16,7 @@ open class BuildTask : DefaultTask() { @TaskAction fun assemble() { - val executable = """pnpm"""; + val executable = """node"""; try { runTauriCli(executable) } catch (e: Exception) { @@ -32,7 +32,7 @@ open class BuildTask : DefaultTask() { val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") val target = target ?: throw GradleException("target cannot be null") val release = release ?: throw GradleException("release cannot be null") - val args = listOf("tauri", "android", "android-studio-script"); + val args = listOf("/Users/lucas/projects/tauri/plugins-workspace/examples/api/./node_modules/.bin/../../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.14/node_modules/@tauri-apps/cli/tauri.js", "android", "android-studio-script"); project.exec { workingDir(File(project.projectDir, rootDirRel)) diff --git a/examples/api/src-tauri/gen/apple/.gitignore b/examples/api/src-tauri/gen/apple/.gitignore new file mode 100644 index 000000000..6726e2f89 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/.gitignore @@ -0,0 +1,3 @@ +xcuserdata/ +build/ +Externals/ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png new file mode 100644 index 000000000..f8b128e3c Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png new file mode 100644 index 000000000..6bbd9e3ce Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png new file mode 100644 index 000000000..6bbd9e3ce Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png new file mode 100644 index 000000000..f702cc04f Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png new file mode 100644 index 000000000..c5e92f781 Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png new file mode 100644 index 000000000..1c607d5c3 Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png new file mode 100644 index 000000000..1c607d5c3 Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png new file mode 100644 index 000000000..60e93a6a1 Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png new file mode 100644 index 000000000..6bbd9e3ce Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png new file mode 100644 index 000000000..819410f9f Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png new file mode 100644 index 000000000..819410f9f Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png new file mode 100644 index 000000000..e00ae5a6a Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png new file mode 100644 index 000000000..f5301f37d Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512x512@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512x512@2x.png new file mode 100644 index 000000000..5e9add731 Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-512x512@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png new file mode 100644 index 000000000..e00ae5a6a Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png new file mode 100644 index 000000000..3546ca10c Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png new file mode 100644 index 000000000..d83671014 Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png new file mode 100644 index 000000000..29925f2ab Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png new file mode 100644 index 000000000..dfd22619f Binary files /dev/null and b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png differ diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..dd3b8bcc5 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images": [ + { + "size": "20x20", + "idiom": "iphone", + "filename": "AppIcon-20x20@2x.png", + "scale": "2x" + }, + { + "size": "20x20", + "idiom": "iphone", + "filename": "AppIcon-20x20@3x.png", + "scale": "3x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "AppIcon-29x29@2x-1.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "AppIcon-29x29@3x.png", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "AppIcon-40x40@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "AppIcon-40x40@3x.png", + "scale": "3x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "AppIcon-60x60@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "AppIcon-60x60@3x.png", + "scale": "3x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "AppIcon-20x20@1x.png", + "scale": "1x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "AppIcon-20x20@2x-1.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "AppIcon-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "AppIcon-29x29@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "AppIcon-40x40@1x.png", + "scale": "1x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "AppIcon-40x40@2x-1.png", + "scale": "2x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "AppIcon-76x76@1x.png", + "scale": "1x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "AppIcon-76x76@2x.png", + "scale": "2x" + }, + { + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "AppIcon-83.5x83.5@2x.png", + "scale": "2x" + }, + { + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "AppIcon-512@2x.png", + "scale": "1x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/examples/api/src-tauri/gen/apple/Assets.xcassets/Contents.json b/examples/api/src-tauri/gen/apple/Assets.xcassets/Contents.json new file mode 100644 index 000000000..97a8662eb --- /dev/null +++ b/examples/api/src-tauri/gen/apple/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/examples/api/src-tauri/gen/apple/ExportOptions.plist b/examples/api/src-tauri/gen/apple/ExportOptions.plist new file mode 100644 index 000000000..b69cf1de8 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/ExportOptions.plist @@ -0,0 +1,8 @@ + + + + + method + development + + diff --git a/examples/api/src-tauri/gen/apple/Podfile b/examples/api/src-tauri/gen/apple/Podfile new file mode 100644 index 000000000..90e8885d5 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/Podfile @@ -0,0 +1,21 @@ +# Uncomment the next line to define a global platform for your project + +target 'api_iOS' do +platform :ios, '13.0' + # Pods for api_iOS +end + +target 'api_macOS' do +platform :osx, '11.0' + # Pods for api_macOS +end + +# Delete the deployment target for iOS and macOS, causing it to be inherited from the Podfile +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' + config.build_settings.delete 'MACOSX_DEPLOYMENT_TARGET' + end + end +end diff --git a/examples/api/src-tauri/gen/apple/Sources/api/bindings/bindings.h b/examples/api/src-tauri/gen/apple/Sources/api/bindings/bindings.h new file mode 100644 index 000000000..51522007b --- /dev/null +++ b/examples/api/src-tauri/gen/apple/Sources/api/bindings/bindings.h @@ -0,0 +1,8 @@ +#pragma once + +namespace ffi { + extern "C" { + void start_app(); + } +} + diff --git a/examples/api/src-tauri/gen/apple/Sources/api/main.mm b/examples/api/src-tauri/gen/apple/Sources/api/main.mm new file mode 100644 index 000000000..7793a9d5c --- /dev/null +++ b/examples/api/src-tauri/gen/apple/Sources/api/main.mm @@ -0,0 +1,6 @@ +#include "bindings/bindings.h" + +int main(int argc, char * argv[]) { + ffi::start_app(); + return 0; +} diff --git a/examples/api/src-tauri/gen/apple/api.xcodeproj/project.pbxproj b/examples/api/src-tauri/gen/apple/api.xcodeproj/project.pbxproj new file mode 100644 index 000000000..63f5ac216 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/api.xcodeproj/project.pbxproj @@ -0,0 +1,462 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 2ECFC1BC47D948875C8CEC41 /* libapi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FC53D4128D7F74E4E6338455 /* libapi.a */; }; + 3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */; }; + 328B4ADB3700C1873BEB7B10 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90D3B673AFAB8D8AB561F616 /* main.mm */; }; + 6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6B7E79E23E646BA7968B457C /* Assets.xcassets */; }; + 9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62601E25FA39E62BE119B74D /* Metal.framework */; }; + 9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6082E363D51372A7658C351 /* UIKit.framework */; }; + AFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384966E551417F94A02D2706 /* Security.framework */; }; + B60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC377692DC31A070A0188C9D /* QuartzCore.framework */; }; + C6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */; }; + DFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 338E66700FD330B99D434DD7 /* MetalKit.framework */; }; + F86717F05E27C72C9FA1FB27 /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 74A8FDFB350B966F5AAD4A24 /* assets */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0E96CE07CD20273DD46BF325 /* main.rs */ = {isa = PBXFileReference; path = main.rs; sourceTree = ""; }; + 1C1AB1B414CA2795AFBEDDB9 /* tray.rs */ = {isa = PBXFileReference; path = tray.rs; sourceTree = ""; }; + 2F63E2AA460089BB58D40C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 338E66700FD330B99D434DD7 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; }; + 384966E551417F94A02D2706 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; + 5AC703CEBA41A121596066F3 /* api_iOS.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = api_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 62601E25FA39E62BE119B74D /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; + 6B7E79E23E646BA7968B457C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 74A8FDFB350B966F5AAD4A24 /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = assets; sourceTree = SOURCE_ROOT; }; + 785D025E9542F7E098BF22B5 /* lib.rs */ = {isa = PBXFileReference; path = lib.rs; sourceTree = ""; }; + 879941AE3DAA14534BBC6391 /* api_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = api_iOS.entitlements; sourceTree = ""; }; + 90D3B673AFAB8D8AB561F616 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; + B6082E363D51372A7658C351 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + DC377692DC31A070A0188C9D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + EC8C7948C50C3C9B5D96CB61 /* bindings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bindings.h; sourceTree = ""; }; + F835F52713CE8F029D5D252C /* cmd.rs */ = {isa = PBXFileReference; path = cmd.rs; sourceTree = ""; }; + FC53D4128D7F74E4E6338455 /* libapi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapi.a; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 11E18DCDB3ADFE87C18915EF /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2ECFC1BC47D948875C8CEC41 /* libapi.a in Frameworks */, + 3043432501C9BC2DB6B4CB95 /* CoreGraphics.framework in Frameworks */, + 9AADB041D25772D04E543F15 /* Metal.framework in Frameworks */, + DFFF888045C8D9D9FB69E8FD /* MetalKit.framework in Frameworks */, + B60763BD194DFACA215EC7DA /* QuartzCore.framework in Frameworks */, + AFA0CA286325FD7A34968CA2 /* Security.framework in Frameworks */, + 9DDA3BE70DD0E4013973FE38 /* UIKit.framework in Frameworks */, + C6D80743F168BDF017B7769E /* WebKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0677CEAF1F282F38CBA0F140 = { + isa = PBXGroup; + children = ( + 74A8FDFB350B966F5AAD4A24 /* assets */, + 6B7E79E23E646BA7968B457C /* Assets.xcassets */, + F2116A6428EED18BE2A07E2B /* api_iOS */, + 86D903732E10FAC4D300E8DF /* Externals */, + 7A9A7AC155D9E22E54D6D847 /* Sources */, + CF9AA87D2F6E9C389B7AB70B /* src */, + 10C9FC3FA3E12D6A4A67999D /* Frameworks */, + 4AC51E67B71E27F15B02C5CD /* Products */, + ); + sourceTree = ""; + }; + 07051859D6E2D8109C8FB128 /* bindings */ = { + isa = PBXGroup; + children = ( + EC8C7948C50C3C9B5D96CB61 /* bindings.h */, + ); + path = bindings; + sourceTree = ""; + }; + 10C9FC3FA3E12D6A4A67999D /* Frameworks */ = { + isa = PBXGroup; + children = ( + 71EB788DE4662CFC0D97F567 /* CoreGraphics.framework */, + FC53D4128D7F74E4E6338455 /* libapi.a */, + 62601E25FA39E62BE119B74D /* Metal.framework */, + 338E66700FD330B99D434DD7 /* MetalKit.framework */, + DC377692DC31A070A0188C9D /* QuartzCore.framework */, + 384966E551417F94A02D2706 /* Security.framework */, + B6082E363D51372A7658C351 /* UIKit.framework */, + 59CFE20DCF760BE67D9CE3D6 /* WebKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 4AC51E67B71E27F15B02C5CD /* Products */ = { + isa = PBXGroup; + children = ( + 5AC703CEBA41A121596066F3 /* api_iOS.app */, + ); + name = Products; + sourceTree = ""; + }; + 7A9A7AC155D9E22E54D6D847 /* Sources */ = { + isa = PBXGroup; + children = ( + A3574F52DBC5463B9C3D043D /* api */, + ); + path = Sources; + sourceTree = ""; + }; + 86D903732E10FAC4D300E8DF /* Externals */ = { + isa = PBXGroup; + children = ( + ); + path = Externals; + sourceTree = ""; + }; + A3574F52DBC5463B9C3D043D /* api */ = { + isa = PBXGroup; + children = ( + 90D3B673AFAB8D8AB561F616 /* main.mm */, + 07051859D6E2D8109C8FB128 /* bindings */, + ); + path = api; + sourceTree = ""; + }; + CF9AA87D2F6E9C389B7AB70B /* src */ = { + isa = PBXGroup; + children = ( + F835F52713CE8F029D5D252C /* cmd.rs */, + 785D025E9542F7E098BF22B5 /* lib.rs */, + 0E96CE07CD20273DD46BF325 /* main.rs */, + 1C1AB1B414CA2795AFBEDDB9 /* tray.rs */, + ); + name = src; + path = ../../src; + sourceTree = ""; + }; + F2116A6428EED18BE2A07E2B /* api_iOS */ = { + isa = PBXGroup; + children = ( + 879941AE3DAA14534BBC6391 /* api_iOS.entitlements */, + 2F63E2AA460089BB58D40C79 /* Info.plist */, + ); + path = api_iOS; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 54DC6E273C78071F3BA12EF3 /* api_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget "api_iOS" */; + buildPhases = ( + FF948951157DE71465B5BD5F /* Build Rust Code */, + 71E73CC9AB5F1323EC1F6365 /* Sources */, + CA2BEC44B6EDA1F21B6155CD /* Resources */, + 11E18DCDB3ADFE87C18915EF /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = api_iOS; + productName = api_iOS; + productReference = 5AC703CEBA41A121596066F3 /* api_iOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 9BC88C3717DA5D4B78A51C15 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1200; + TargetAttributes = { + 54DC6E273C78071F3BA12EF3 = { + DevelopmentTeam = Q93MBH6S2F; + }; + }; + }; + buildConfigurationList = 8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject "api" */; + compatibilityVersion = "Xcode 11.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = 0677CEAF1F282F38CBA0F140; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 54DC6E273C78071F3BA12EF3 /* api_iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CA2BEC44B6EDA1F21B6155CD /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6F379F15DA085785BA2624D4 /* Assets.xcassets in Resources */, + F86717F05E27C72C9FA1FB27 /* assets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + FF948951157DE71465B5BD5F /* Build Rust Code */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Build Rust Code"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(SRCROOT)/target/aarch64-apple-ios/${CONFIGURATION}/deps/libapi.a", + "$(SRCROOT)/target/x86_64-apple-ios/${CONFIGURATION}/deps/libapi.a", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "node /Users/lucas/projects/tauri/plugins-workspace/examples/api/./node_modules/.bin/../../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.14/node_modules/@tauri-apps/cli/tauri.js ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths \"${FRAMEWORK_SEARCH_PATHS:?}\" --header-search-paths \"${HEADER_SEARCH_PATHS:?}\" --gcc-preprocessor-definitions \"${GCC_PREPROCESSOR_DEFINITIONS:-}\" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?}"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 71E73CC9AB5F1323EC1F6365 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 328B4ADB3700C1873BEB7B10 /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + A83F70B4C02DD0222038C7F1 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = release; + }; + B6AD77E490F315562F75D3D7 /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = debug; + }; + BF284FE6E7AE0C8DDCCE398B /* debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = ( + arm64, + "arm64-sim", + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = Q93MBH6S2F; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = api_iOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = "$(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.api; + PRODUCT_NAME = "Tauri API"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "arm64 arm64-sim"; + }; + name = debug; + }; + DB0E254D0FD84970B57F6410 /* release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + ARCHS = ( + arm64, + "arm64-sim", + ); + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = api_iOS/api_iOS.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer"; + DEVELOPMENT_TEAM = Q93MBH6S2F; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\".\"", + ); + INFOPLIST_FILE = api_iOS/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = "$(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=arm64]" = "$(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + "LIBRARY_SEARCH_PATHS[arch=x86_64]" = "$(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = com.tauri.api; + PRODUCT_NAME = "Tauri API"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALID_ARCHS = "arm64 arm64-sim"; + }; + name = release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 01CBC40275452376830D79B1 /* Build configuration list for PBXNativeTarget "api_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BF284FE6E7AE0C8DDCCE398B /* debug */, + DB0E254D0FD84970B57F6410 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; + 8FA67D0F928A09CD639137D1 /* Build configuration list for PBXProject "api" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B6AD77E490F315562F75D3D7 /* debug */, + A83F70B4C02DD0222038C7F1 /* release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 9BC88C3717DA5D4B78A51C15 /* Project object */; +} diff --git a/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..ac90d5ac7 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/api.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + BuildSystemType + Original + DisableBuildSystemDeprecationDiagnostic + + + diff --git a/examples/api/src-tauri/gen/apple/api.xcodeproj/xcshareddata/xcschemes/api_iOS.xcscheme b/examples/api/src-tauri/gen/apple/api.xcodeproj/xcshareddata/xcschemes/api_iOS.xcscheme new file mode 100644 index 000000000..3680d4052 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/api.xcodeproj/xcshareddata/xcschemes/api_iOS.xcscheme @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/api/src-tauri/gen/apple/api_iOS/Info.plist b/examples/api/src-tauri/gen/apple/api_iOS/Info.plist new file mode 100644 index 000000000..761158240 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/api_iOS/Info.plist @@ -0,0 +1,46 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 2.0.0 + CFBundleVersion + 2.0.0 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + metal + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + NSCameraUsageDescription + To be able to scan barcodes + + diff --git a/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements b/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements new file mode 100644 index 000000000..0c67376eb --- /dev/null +++ b/examples/api/src-tauri/gen/apple/api_iOS/api_iOS.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/examples/api/src-tauri/gen/apple/project.yml b/examples/api/src-tauri/gen/apple/project.yml new file mode 100644 index 000000000..53f53deb2 --- /dev/null +++ b/examples/api/src-tauri/gen/apple/project.yml @@ -0,0 +1,92 @@ +# Copyright 2019-2023 Tauri Programme within The Commons Conservancy +# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: MIT + +name: api +options: + bundleIdPrefix: com.tauri + deploymentTarget: + iOS: 13.0 +fileGroups: [../../src] +configs: + debug: debug + release: release +settingGroups: + app: + base: + PRODUCT_NAME: Tauri API + PRODUCT_BUNDLE_IDENTIFIER: com.tauri.api + DEVELOPMENT_TEAM: Q93MBH6S2F +targetTemplates: + app: + type: application + sources: + - path: Sources + scheme: + environmentVariables: + RUST_BACKTRACE: full + RUST_LOG: info + settings: + groups: [app] +targets: + api_iOS: + type: application + platform: iOS + sources: + - path: Sources + - path: Assets.xcassets + - path: Externals + - path: api_iOS + - path: assets + buildPhase: resources + type: folder + info: + path: api_iOS/Info.plist + properties: + LSRequiresIPhoneOS: true + UILaunchStoryboardName: LaunchScreen + UIRequiredDeviceCapabilities: [arm64, metal] + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + UISupportedInterfaceOrientations~ipad: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + CFBundleShortVersionString: 2.0.0 + CFBundleVersion: 2.0.0 + entitlements: + path: api_iOS/api_iOS.entitlements + scheme: + environmentVariables: + RUST_BACKTRACE: full + RUST_LOG: info + settings: + base: + ENABLE_BITCODE: false + ARCHS: [arm64, arm64-sim] + VALID_ARCHS: arm64 arm64-sim + LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) + LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) + LIBRARY_SEARCH_PATHS[arch=arm64-sim]: $(inherited) $(PROJECT_DIR)/Externals/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true + groups: [app] + dependencies: + - framework: libapi.a + embed: false + - sdk: CoreGraphics.framework + - sdk: Metal.framework + - sdk: MetalKit.framework + - sdk: QuartzCore.framework + - sdk: Security.framework + - sdk: UIKit.framework + - sdk: WebKit.framework + preBuildScripts: + - script: node /Users/lucas/projects/tauri/plugins-workspace/examples/api/./node_modules/.bin/../../../../node_modules/.pnpm/@tauri-apps+cli@2.0.0-alpha.14/node_modules/@tauri-apps/cli/tauri.js ios xcode-script -v --platform ${PLATFORM_DISPLAY_NAME:?} --sdk-root ${SDKROOT:?} --framework-search-paths "${FRAMEWORK_SEARCH_PATHS:?}" --header-search-paths "${HEADER_SEARCH_PATHS:?}" --gcc-preprocessor-definitions "${GCC_PREPROCESSOR_DEFINITIONS:-}" --configuration ${CONFIGURATION:?} ${FORCE_COLOR} ${ARCHS:?} + name: Build Rust Code + basedOnDependencyAnalysis: false + outputFiles: + - $(SRCROOT)/target/aarch64-apple-ios/${CONFIGURATION}/deps/libapi.a + - $(SRCROOT)/target/x86_64-apple-ios/${CONFIGURATION}/deps/libapi.a \ No newline at end of file diff --git a/examples/api/src-tauri/src/lib.rs b/examples/api/src-tauri/src/lib.rs index c3bd535eb..2e04967ad 100644 --- a/examples/api/src-tauri/src/lib.rs +++ b/examples/api/src-tauri/src/lib.rs @@ -51,6 +51,10 @@ pub fn run() { app.handle() .plugin(tauri_plugin_updater::Builder::new().build())?; } + #[cfg(mobile)] + { + app.handle().plugin(tauri_plugin_barcode_scanner::init())?; + } let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default()); #[cfg(desktop)] diff --git a/examples/api/src/App.svelte b/examples/api/src/App.svelte index 740ebeca2..9bf1574b5 100644 --- a/examples/api/src/App.svelte +++ b/examples/api/src/App.svelte @@ -17,6 +17,7 @@ import Updater from "./views/Updater.svelte"; import Clipboard from "./views/Clipboard.svelte"; import WebRTC from "./views/WebRTC.svelte"; + import Scanner from "./views/Scanner.svelte"; import App from "./views/App.svelte"; import { onMount } from "svelte"; @@ -113,6 +114,11 @@ component: WebRTC, icon: "i-ph-broadcast", }, + isMobile && { + label: "Scanner", + component: Scanner, + icon: "i-ph-scan", + }, ]; let selected = views[0]; @@ -444,6 +450,7 @@

{selected.label}

diff --git a/examples/api/src/app.css b/examples/api/src/app.css index 09ed66d1b..6987e423e 100644 --- a/examples/api/src/app.css +++ b/examples/api/src/app.css @@ -55,3 +55,7 @@ body { #console { padding-bottom: calc(env(safe-area-inset-bottom) + 24px); } + +.transparent { + background-color: transparent; +} diff --git a/examples/api/src/views/Scanner.svelte b/examples/api/src/views/Scanner.svelte new file mode 100644 index 000000000..57ff904c7 --- /dev/null +++ b/examples/api/src/views/Scanner.svelte @@ -0,0 +1,155 @@ + + +
+
+
+ + +
+
+ +
+ +
+
+
+ +
+
+
+
+

Aim your camera at a QR code

+ +
+
+
+
+
+
+
+
+
+
+ + diff --git a/plugins/barcode-scanner/.gitignore b/plugins/barcode-scanner/.gitignore new file mode 100644 index 000000000..1b0b469d0 --- /dev/null +++ b/plugins/barcode-scanner/.gitignore @@ -0,0 +1 @@ +/.tauri diff --git a/plugins/barcode-scanner/Cargo.toml b/plugins/barcode-scanner/Cargo.toml new file mode 100644 index 000000000..78a4631ee --- /dev/null +++ b/plugins/barcode-scanner/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tauri-plugin-barcode-scanner" +version = "1.0.0" +description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS" +edition = { workspace = true } +authors = { workspace = true } +license = { workspace = true } +links = "tauri-plugin-barcode-scanner" + +[build-dependencies] +tauri-build = { workspace = true } + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +tauri = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } diff --git a/plugins/barcode-scanner/LICENSE.spdx b/plugins/barcode-scanner/LICENSE.spdx new file mode 100644 index 000000000..cdd0df5ad --- /dev/null +++ b/plugins/barcode-scanner/LICENSE.spdx @@ -0,0 +1,20 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: tauri +DataFormat: SPDXRef-1 +PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy +PackageHomePage: https://tauri.app +PackageLicenseDeclared: Apache-2.0 +PackageLicenseDeclared: MIT +PackageCopyrightText: 2019-2022, The Tauri Programme in the Commons Conservancy +PackageSummary: Tauri is a rust project that enables developers to make secure +and small desktop applications using a web frontend. + +PackageComment: The package includes the following libraries; see +Relationship information. + +Created: 2019-05-20T09:00:00Z +PackageDownloadLocation: git://github.com/tauri-apps/tauri +PackageDownloadLocation: git+https://github.com/tauri-apps/tauri.git +PackageDownloadLocation: git+ssh://github.com/tauri-apps/tauri.git +Creator: Person: Daniel Thompson-Yvetot \ No newline at end of file diff --git a/plugins/barcode-scanner/LICENSE_APACHE-2.0 b/plugins/barcode-scanner/LICENSE_APACHE-2.0 new file mode 100644 index 000000000..4947287f7 --- /dev/null +++ b/plugins/barcode-scanner/LICENSE_APACHE-2.0 @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/plugins/barcode-scanner/LICENSE_MIT b/plugins/barcode-scanner/LICENSE_MIT new file mode 100644 index 000000000..4d7547256 --- /dev/null +++ b/plugins/barcode-scanner/LICENSE_MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 - Present Tauri Apps Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/barcode-scanner/README.md b/plugins/barcode-scanner/README.md new file mode 100644 index 000000000..30f6572f6 --- /dev/null +++ b/plugins/barcode-scanner/README.md @@ -0,0 +1,98 @@ +![Barcode Scanner](banner.png) + +Allows your mobile application to use the camera to scan QR codes, EAN-13 and other kinds of barcodes. + +## Install + +_This plugin requires a Rust version of at least **1.64**_ + +There are three general methods of installation that we can recommend. + +1. Use crates.io and npm (easiest, and requires you to trust that our publishing pipeline worked) +2. Pull sources directly from Github using git tags / revision hashes (most secure) +3. Git submodule install this repo in your tauri project and then use file protocol to ingest the source (most secure, but inconvenient to use) + +Install the Core plugin by adding the following to your `Cargo.toml` file: + +`src-tauri/Cargo.toml` + +```toml +[dependencies] +tauri-plugin-barcode-scanner = "2.0.0-alpha" +# alternatively with Git: +tauri-plugin-barcode-scanner = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" } +``` + +You can install the JavaScript Guest bindings using your preferred JavaScript package manager: + +> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use. + +```sh +pnpm add @tauri-apps/plugin-barcode-scanner +# or +npm add @tauri-apps/plugin-barcode-scanner +# or +yarn add @tauri-apps/plugin-barcode-scanner + +# alternatively with Git: +pnpm add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2 +# or +npm add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2 +# or +yarn add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2 +``` + +## Usage + +First you need to register the core plugin with Tauri: + +`src-tauri/src/main.rs` + +```rust +fn main() { + tauri::Builder::default() + .plugin(tauri_plugin_barcode_scanner::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +Afterwards all the plugin's APIs are available through the JavaScript guest bindings: + +```javascript +import { scan } from "@tauri-apps/plugin-barcode-scanner"; + +// `windowed: true` actually sets the webview to transparent +// instead of opening a separate view for the camera +// make sure your user interface is ready to show what is underneath with a transparent element +scan({ windowed: true, formats: [""] }) +``` + +## Contributing + +PRs accepted. Please make sure to read the Contributing Guide before making a pull request. + +## Contributed By + + + + + + + + +
+ + CrabNebula + + + + Impierce + +
+ +## License + +Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy. + +MIT or MIT/Apache 2.0 where applicable. diff --git a/plugins/barcode-scanner/android/.gitignore b/plugins/barcode-scanner/android/.gitignore new file mode 100644 index 000000000..c0f21ec2f --- /dev/null +++ b/plugins/barcode-scanner/android/.gitignore @@ -0,0 +1,2 @@ +/build +/.tauri diff --git a/plugins/barcode-scanner/android/build.gradle.kts b/plugins/barcode-scanner/android/build.gradle.kts new file mode 100644 index 000000000..8b060f643 --- /dev/null +++ b/plugins/barcode-scanner/android/build.gradle.kts @@ -0,0 +1,56 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "app.tauri.barcodescanner" + compileSdk = 32 + + defaultConfig { + minSdk = 24 + targetSdk = 32 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("com.google.android.material:material:1.7.0") + implementation("androidx.camera:camera-core:1.1.0") + implementation("androidx.camera:camera-view:1.1.0") + implementation("androidx.camera:camera-lifecycle:1.1.0") + implementation("androidx.camera:camera-camera2:1.1.0") + implementation("androidx.camera:camera-lifecycle:1.1.0") + implementation("androidx.camera:camera-view:1.1.0") + implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + implementation("com.journeyapps:zxing-android-embedded:4.3.0") { + isTransitive = false + } + implementation("com.google.zxing:core:3.3.0") + implementation(project(":tauri-android")) +} diff --git a/plugins/barcode-scanner/android/proguard-rules.pro b/plugins/barcode-scanner/android/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/plugins/barcode-scanner/android/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/plugins/barcode-scanner/android/settings.gradle b/plugins/barcode-scanner/android/settings.gradle new file mode 100644 index 000000000..14a752e43 --- /dev/null +++ b/plugins/barcode-scanner/android/settings.gradle @@ -0,0 +1,2 @@ +include ':tauri-android' +project(':tauri-android').projectDir = new File('./.tauri/tauri-api') diff --git a/plugins/barcode-scanner/android/src/androidTest/java/ExampleInstrumentedTest.kt b/plugins/barcode-scanner/android/src/androidTest/java/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..4ccd5f666 --- /dev/null +++ b/plugins/barcode-scanner/android/src/androidTest/java/ExampleInstrumentedTest.kt @@ -0,0 +1,28 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("app.tauri.barcodescanner", appContext.packageName) + } +} diff --git a/plugins/barcode-scanner/android/src/main/AndroidManifest.xml b/plugins/barcode-scanner/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..b4050b4f6 --- /dev/null +++ b/plugins/barcode-scanner/android/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/plugins/barcode-scanner/android/src/main/java/BarcodeScannerPlugin.kt b/plugins/barcode-scanner/android/src/main/java/BarcodeScannerPlugin.kt new file mode 100644 index 000000000..5d8a01545 --- /dev/null +++ b/plugins/barcode-scanner/android/src/main/java/BarcodeScannerPlugin.kt @@ -0,0 +1,438 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Context.MODE_PRIVATE +import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.net.Uri +import android.os.Build +import android.os.VibrationEffect +import android.os.Vibrator +import android.provider.Settings +import android.util.Size +import android.view.ViewGroup +import android.webkit.WebView +import android.widget.FrameLayout +import androidx.activity.result.ActivityResult +import androidx.camera.core.Camera +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.content.ContextCompat +import androidx.lifecycle.LifecycleOwner +import app.tauri.Logger +import app.tauri.PermissionState +import app.tauri.annotation.ActivityCallback +import app.tauri.annotation.Command +import app.tauri.annotation.Permission +import app.tauri.annotation.PermissionCallback +import app.tauri.annotation.TauriPlugin +import app.tauri.plugin.Invoke +import app.tauri.plugin.JSArray +import app.tauri.plugin.JSObject +import app.tauri.plugin.Plugin +import com.google.common.util.concurrent.ListenableFuture +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.common.InputImage +import org.json.JSONException +import java.util.Collections +import java.util.concurrent.ExecutionException + +private const val PERMISSION_ALIAS_CAMERA = "camera" +private const val PERMISSION_NAME = Manifest.permission.CAMERA +private const val PREFS_PERMISSION_FIRST_TIME_ASKING = "PREFS_PERMISSION_FIRST_TIME_ASKING" + +@TauriPlugin( + permissions = [ + Permission(strings = [Manifest.permission.CAMERA], alias = "camera") + ] +) +class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity), + ImageAnalysis.Analyzer { + private lateinit var webView: WebView + private var previewView: PreviewView? = null + private var cameraProviderFuture: ListenableFuture? = null + private var cameraProvider: ProcessCameraProvider? = null + private var graphicOverlay: GraphicOverlay? = null + private var camera: Camera? = null + private var vibrator: Vibrator? = null + + private var scannerOptions: BarcodeScannerOptions? = null + private var scanner: com.google.mlkit.vision.barcode.BarcodeScanner? = null + + private var requestPermissionResponse: JSObject? = null + private var windowed = false + + // declare a map constant for allowed barcode formats + private val supportedFormats = supportedFormats() + + private var savedInvoke: Invoke? = null + private var webViewBackground: Drawable? = null + + override fun load(webView: WebView) { + super.load(webView) + this.webView = webView + } + + private fun supportedFormats(): Map { + val map: MutableMap = HashMap() + map["UPC_A"] = Barcode.FORMAT_UPC_A + map["UPC_E"] = Barcode.FORMAT_UPC_E + map["EAN_8"] = Barcode.FORMAT_EAN_8 + map["EAN_13"] = Barcode.FORMAT_EAN_13 + map["CODE_39"] = Barcode.FORMAT_CODE_39 + map["CODE_93"] = Barcode.FORMAT_CODE_93 + map["CODE_128"] = Barcode.FORMAT_CODE_128 + map["CODABAR"] = Barcode.FORMAT_CODABAR + map["ITF"] = Barcode.FORMAT_ITF + map["AZTEC"] = Barcode.FORMAT_AZTEC + map["DATA_MATRIX"] = Barcode.FORMAT_DATA_MATRIX + map["PDF_417"] = Barcode.FORMAT_PDF417 + map["QR_CODE"] = Barcode.FORMAT_QR_CODE + return Collections.unmodifiableMap(map) + } + + private fun hasCamera(): Boolean { + return activity.packageManager + .hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) + } + + private fun setupCamera(cameraDirection: String, windowed: Boolean) { + activity + .runOnUiThread { + val previewView = PreviewView(activity) + previewView.layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + this.previewView = previewView + + val graphicOverlay = GraphicOverlay(activity) + graphicOverlay.layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + this.graphicOverlay = graphicOverlay + + val parent = webView.parent as ViewGroup + parent.addView(previewView) + parent.addView(graphicOverlay) + + this.windowed = windowed + if (windowed) { + webView.bringToFront() + webViewBackground = webView.background + webView.setBackgroundColor(Color.TRANSPARENT) + } + + val cameraProviderFuture = ProcessCameraProvider.getInstance(activity) + cameraProviderFuture.addListener( + { + try { + val cameraProvider = cameraProviderFuture.get() + bindPreview( + cameraProvider, + if (cameraDirection == "front") CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK + ) + this.cameraProvider = cameraProvider + } catch (e: InterruptedException) { + // ignored + } catch (_: ExecutionException) { + // ignored + } + }, + ContextCompat.getMainExecutor(activity) + ) + this.cameraProviderFuture = cameraProviderFuture + } + } + + private fun bindPreview(cameraProvider: ProcessCameraProvider, cameraDirection: Int) { + activity + .runOnUiThread { + val preview = Preview.Builder().build() + val cameraSelector = + CameraSelector.Builder().requireLensFacing(cameraDirection).build() + preview.setSurfaceProvider(previewView?.surfaceProvider) + val imageAnalysis = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .setTargetResolution(Size(1280, 720)) + .build() + imageAnalysis.setAnalyzer( + ContextCompat.getMainExecutor(activity), + this + ) + + try { + camera = cameraProvider.bindToLifecycle( + activity as LifecycleOwner, + cameraSelector, + preview, + imageAnalysis + ) + } catch (e: Exception) { + // TODO + } + } + } + + private fun dismantleCamera() { + activity + .runOnUiThread { + if (cameraProvider != null) { + cameraProvider?.unbindAll() + val parent = webView.parent as ViewGroup + parent.removeView(previewView) + parent.removeView(graphicOverlay) + camera = null + previewView = null + graphicOverlay = null + } + } + } + + private fun getFormats(invoke: Invoke): List { + val jsFormats = invoke.getArray("formats", JSArray()) + val formats = ArrayList() + for (i in 0 until jsFormats.length()) { + try { + val targetedFormat: String = jsFormats.getString(i) + val targetedBarcodeFormat = + supportedFormats[targetedFormat] + if (targetedBarcodeFormat != null) { + formats.add(targetedBarcodeFormat) + } + } catch (e: JSONException) { + e.printStackTrace() + } + } + return formats + } + + private fun prepare(direction: String, windowed: Boolean) { + dismantleCamera() + setupCamera(direction, windowed) + } + + private fun destroy() { + dismantleCamera() + savedInvoke = null + if (windowed) { + if (webViewBackground != null) { + webView.background = webViewBackground + webViewBackground = null + } else { + webView.setBackgroundColor(Color.WHITE) + } + } + } + + @Suppress("DEPRECATION") + private fun configureCamera(formats: List) { + activity + .runOnUiThread { + val vibrator = + activity.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + this.vibrator = vibrator + if (previewView == null) { + throw Exception("Something went wrong configuring the BarcodeScanner") + } + + if (formats.isNotEmpty()) { + val mappedFormats = mapFormats(formats) + val options = + BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE, *mappedFormats).build() + scannerOptions = options + scanner = BarcodeScanning.getClient(options) + } else { + val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS).build() + scannerOptions = options + scanner = BarcodeScanning.getClient(options) + } + } + } + + private fun mapFormats(integers: List): IntArray { + val ret = IntArray(integers.size) + for (i in ret.indices) { + if (integers[i] != Barcode.FORMAT_QR_CODE) ret[i] = integers[i] + } + return ret + } + + override fun analyze(image: ImageProxy) { + @SuppressLint("UnsafeOptInUsageError") val mediaImage = image.image + if (mediaImage != null) { + val inputImage = + InputImage.fromMediaImage(mediaImage, image.imageInfo.rotationDegrees) + scanner + ?.process(inputImage) + ?.addOnSuccessListener { barcodes -> + if (barcodes.isNotEmpty()) { + val barcode = barcodes[0] + val bounds = barcode.boundingBox + val rawValue = barcode.rawValue ?: "" + val rawFormat = barcode.format + var format: String? = null + + for (entry in supportedFormats.entries) { + if (entry.value == rawFormat) { + format = entry.key + break + } + } + + val s = bounds?.flattenToString() + val jsObject = JSObject() + jsObject.put("content", rawValue) + jsObject.put("format", format) + jsObject.put("bounds", s) + + savedInvoke?.resolve(jsObject) + destroy() + } + } + ?.addOnFailureListener { e -> + Logger.error(e.message ?: e.toString()) + } + ?.addOnCompleteListener { + image.close() + mediaImage.close() + } + } + } + + @Command + fun vibrate(invoke: Invoke) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator!!.vibrate( + VibrationEffect.createOneShot( + 50, + VibrationEffect.DEFAULT_AMPLITUDE + ) + ) + } + invoke.resolve() + } + + @Command + fun cancel(invoke: Invoke) { + destroy() + savedInvoke?.reject("cancelled") + invoke.resolve() + } + + @Command + fun scan(invoke: Invoke) { + savedInvoke = invoke + if (hasCamera()) { + if (getPermissionState("camera") != PermissionState.GRANTED) { + throw Exception("No permission to use camera. Did you request it yet?") + } else { + webViewBackground = null + prepare(invoke.getString("cameraDirection", "back"), invoke.getBoolean("windowed", false)) + configureCamera(getFormats(invoke)) + } + } + } + + private fun markFirstPermissionRequest() { + val sharedPreference: SharedPreferences = + activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE) + sharedPreference.edit().putBoolean(PERMISSION_NAME, false).apply() + } + + private fun firstPermissionRequest(): Boolean { + return activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE) + .getBoolean(PERMISSION_NAME, true) + } + + @SuppressLint("ObsoleteSdkInt") + @PermissionCallback + fun cameraPermissionCallback(invoke: Invoke) { + if (requestPermissionResponse == null) { + return + } + + val requestPermissionResponse = requestPermissionResponse!! + + val granted = getPermissionState(PERMISSION_ALIAS_CAMERA) === PermissionState.GRANTED + + if (granted) { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (!activity.shouldShowRequestPermissionRationale(PERMISSION_NAME)) { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED) + } + } else { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } + } + + invoke.resolve(requestPermissionResponse) + this.requestPermissionResponse = null + } + + @SuppressLint("ObsoleteSdkInt") + @Command + override fun requestPermissions(invoke: Invoke) { + val requestPermissionResponse = JSObject() + this.requestPermissionResponse = requestPermissionResponse + if (getPermissionState(PERMISSION_ALIAS_CAMERA) === PermissionState.GRANTED) { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (firstPermissionRequest() || activity.shouldShowRequestPermissionRationale( + PERMISSION_NAME + ) + ) { + markFirstPermissionRequest() + requestPermissionForAlias( + PERMISSION_ALIAS_CAMERA, + invoke, + "cameraPermissionCallback" + ) + return + } else { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED) + } + } else { + requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED) + } + } + invoke.resolve(requestPermissionResponse) + } + + @Command + fun openAppSettings(invoke: Invoke) { + val intent = Intent( + Settings.ACTION_APPLICATION_DETAILS_SETTINGS, + Uri.fromParts("package", activity.packageName, null) + ) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivityForResult(invoke, intent, "openSettingsResult") + } + + @ActivityCallback + private fun openSettingsResult(invoke: Invoke, result: ActivityResult) { + invoke.resolve() + } +} diff --git a/plugins/barcode-scanner/android/src/main/java/GraphicOverlay.kt b/plugins/barcode-scanner/android/src/main/java/GraphicOverlay.kt new file mode 100644 index 000000000..76817540f --- /dev/null +++ b/plugins/barcode-scanner/android/src/main/java/GraphicOverlay.kt @@ -0,0 +1,192 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.google.android.gms.common.internal.Preconditions + +class GraphicOverlay: View { + private val lock = Any() + private val graphics: MutableList = ArrayList() + + private val transformationMatrix = Matrix() + + private var imageWidth = 0 + private var imageHeight = 0 + + private var scaleFactor = 1.0f + + private var postScaleWidthOffset = 0f + + private var postScaleHeightOffset = 0f + private var isImageFlipped = false + private var needUpdateTransformation = true + + abstract class Graphic(private val overlay: GraphicOverlay) { + abstract fun draw(canvas: Canvas?) + protected fun drawRect( + canvas: Canvas, + left: Float, + top: Float, + right: Float, + bottom: Float, + paint: Paint? + ) { + canvas.drawRect(left, top, right, bottom, paint!!) + } + + protected fun drawText(canvas: Canvas, text: String?, x: Float, y: Float, paint: Paint?) { + canvas.drawText(text!!, x, y, paint!!) + } + + /** Adjusts the supplied value from the image scale to the view scale. */ + fun scale(imagePixel: Float): Float { + return imagePixel * overlay.scaleFactor + } + + val applicationContext + get() = overlay.context.applicationContext + + fun isImageFlipped(): Boolean { + return overlay.isImageFlipped + } + + fun translateX(x: Float): Float { + return if (overlay.isImageFlipped) { + overlay.width - (scale(x) - overlay.postScaleWidthOffset) + } else { + scale(x) - overlay.postScaleWidthOffset + } + } + + fun translateY(y: Float): Float { + return scale(y) - overlay.postScaleHeightOffset + } + + fun getTransformationMatrix(): Matrix { + return overlay.transformationMatrix + } + + fun postInvalidate() { + overlay.postInvalidate() + } + + fun updatePaintColorByZValue( + paint: Paint, + canvas: Canvas, + visualizeZ: Boolean, + rescaleZForVisualization: Boolean, + zInImagePixel: Float, + zMin: Float, + zMax: Float + ) { + if (!visualizeZ) { + return + } + + val zLowerBoundInScreenPixel: Float + val zUpperBoundInScreenPixel: Float + if (rescaleZForVisualization) { + zLowerBoundInScreenPixel = (-0.001f).coerceAtMost(scale(zMin)) + zUpperBoundInScreenPixel = 0.001f.coerceAtLeast(scale(zMax)) + } else { + val defaultRangeFactor = 1f + zLowerBoundInScreenPixel = -defaultRangeFactor * canvas.width + zUpperBoundInScreenPixel = defaultRangeFactor * canvas.width + } + val zInScreenPixel = scale(zInImagePixel) + if (zInScreenPixel < 0) { + val v = (zInScreenPixel / zLowerBoundInScreenPixel * 255).toInt() + paint.setARGB(0, 0, 255, 0) + } else { + val v = (zInScreenPixel / zUpperBoundInScreenPixel * 255).toInt() + paint.setARGB(0, 0, 255, 0) + } + } + } + + constructor(context: Context): super(context) + + constructor(context: Context, attrs: AttributeSet): super(context, attrs) { + addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> + needUpdateTransformation = true + } + } + + fun clear() { + synchronized(lock) { graphics.clear() } + postInvalidate() + } + + fun add(graphic: Graphic) { + synchronized(lock) { graphics.add(graphic) } + } + + fun remove(graphic: Graphic) { + synchronized(lock) { graphics.remove(graphic) } + postInvalidate() + } + + fun setImageSourceInfo(imageWidth: Int, imageHeight: Int, isFlipped: Boolean) { + Preconditions.checkState(imageWidth > 0, "image width must be positive") + Preconditions.checkState(imageHeight > 0, "image height must be positive") + synchronized(lock) { + this.imageWidth = imageWidth + this.imageHeight = imageHeight + isImageFlipped = isFlipped + needUpdateTransformation = true + } + postInvalidate() + } + + fun getImageWidth(): Int { + return imageWidth + } + + fun getImageHeight(): Int { + return imageHeight + } + + private fun updateTransformationIfNeeded() { + if (!needUpdateTransformation || imageWidth <= 0 || imageHeight <= 0) { + return + } + val viewAspectRatio = width.toFloat() / height + val imageAspectRatio = imageWidth.toFloat() / imageHeight + postScaleWidthOffset = 0f + postScaleHeightOffset = 0f + if (viewAspectRatio > imageAspectRatio) { + // The image needs to be vertically cropped to be displayed in this view. + scaleFactor = width.toFloat() / imageWidth + postScaleHeightOffset = (width.toFloat() / imageAspectRatio - height) / 2 + } else { + // The image needs to be horizontally cropped to be displayed in this view. + scaleFactor = height.toFloat() / imageHeight + postScaleWidthOffset = (height.toFloat() * imageAspectRatio - width) / 2 + } + transformationMatrix.reset() + transformationMatrix.setScale(scaleFactor, scaleFactor) + transformationMatrix.postTranslate(-postScaleWidthOffset, -postScaleHeightOffset) + if (isImageFlipped) { + transformationMatrix.postScale(-1f, 1f, width / 2f, height / 2f) + } + needUpdateTransformation = false + } + + override fun onDraw(canvas: Canvas?) { + super.onDraw(canvas) + synchronized(lock) { + updateTransformationIfNeeded() + for (graphic in graphics) { + graphic.draw(canvas) + } + } + } +} \ No newline at end of file diff --git a/plugins/barcode-scanner/android/src/test/java/ExampleUnitTest.kt b/plugins/barcode-scanner/android/src/test/java/ExampleUnitTest.kt new file mode 100644 index 000000000..895a8ddba --- /dev/null +++ b/plugins/barcode-scanner/android/src/test/java/ExampleUnitTest.kt @@ -0,0 +1,21 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +package app.tauri.barcodescanner + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/plugins/barcode-scanner/banner.png b/plugins/barcode-scanner/banner.png new file mode 100644 index 000000000..491e98644 Binary files /dev/null and b/plugins/barcode-scanner/banner.png differ diff --git a/plugins/barcode-scanner/build.rs b/plugins/barcode-scanner/build.rs new file mode 100644 index 000000000..743096a60 --- /dev/null +++ b/plugins/barcode-scanner/build.rs @@ -0,0 +1,16 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::process::exit; + +fn main() { + if let Err(error) = tauri_build::mobile::PluginBuilder::new() + .android_path("android") + .ios_path("ios") + .run() + { + println!("{error:#}"); + exit(1); + } +} diff --git a/plugins/barcode-scanner/contributors/crabnebula.svg b/plugins/barcode-scanner/contributors/crabnebula.svg new file mode 100644 index 000000000..2c2a5d993 --- /dev/null +++ b/plugins/barcode-scanner/contributors/crabnebula.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/plugins/barcode-scanner/contributors/impierce.svg b/plugins/barcode-scanner/contributors/impierce.svg new file mode 100644 index 000000000..207246daa --- /dev/null +++ b/plugins/barcode-scanner/contributors/impierce.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/barcode-scanner/guest-js/index.ts b/plugins/barcode-scanner/guest-js/index.ts new file mode 100644 index 000000000..44269cdd9 --- /dev/null +++ b/plugins/barcode-scanner/guest-js/index.ts @@ -0,0 +1,75 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { invoke } from "@tauri-apps/api/tauri"; + +export type PermissionState = "granted" | "denied" | "prompt"; + +export enum Format { + QRCode = "QR_CODE", + UPC_A = "UPC_A", + UPC_E = "UPC_E", + EAN8 = "EAN_8", + EAN13 = "EAN_13", + Code39 = "CODE_39", + Code93 = "CODE_93", + Code128 = "CODE_128", + Codabar = "CODABAR", + ITF = "ITF", + Aztec = "AZTEC", + DataMatrix = "DATA_MATRIX", + PDF417 = "PDF_417", +} + +export interface ScanOptions { + cameraDirection?: "back" | "front"; + formats?: Format[]; + windowed?: boolean; +} + +export interface Scanned { + content: string; + format: Format; + bounds: unknown; +} + +/** + * Start scanning. + * @param options + */ +export async function scan(options?: ScanOptions): Promise { + return await invoke("plugin:barcodeScanner|scan", { ...options }); +} + +/** + * Cancel the current scan process. + */ +export async function cancel(): Promise { + return await invoke("plugin:barcodeScanner|cancel"); +} + +/** + * Get permission state. + */ +export async function checkPermissions(): Promise { + return await invoke<{ camera: PermissionState }>( + "plugin:barcodeScanner|checkPermissions", + ).then((r) => r.camera); +} + +/** + * Request permissions to use the camera. + */ +export async function requestPermissions(): Promise { + return await invoke<{ camera: PermissionState }>( + "plugin:barcodeScanner|requestPermissions", + ).then((r) => r.camera); +} + +/** + * Open application settings. Useful if permission was denied and the user must manually enable it. + */ +export async function openAppSettings(): Promise { + return await invoke("plugin:barcodeScanner|openAppSettings"); +} diff --git a/plugins/barcode-scanner/ios/.gitignore b/plugins/barcode-scanner/ios/.gitignore new file mode 100644 index 000000000..5922fdaa5 --- /dev/null +++ b/plugins/barcode-scanner/ios/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc +Package.resolved diff --git a/plugins/barcode-scanner/ios/Package.swift b/plugins/barcode-scanner/ios/Package.swift new file mode 100644 index 000000000..aafb41c3f --- /dev/null +++ b/plugins/barcode-scanner/ios/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version:5.3 +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "tauri-plugin-barcode-scanner", + platforms: [ + .iOS(.v13) + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "tauri-plugin-barcode-scanner", + type: .static, + targets: ["tauri-plugin-barcode-scanner"]) + ], + dependencies: [ + .package(name: "Tauri", path: "../.tauri/tauri-api") + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "tauri-plugin-barcode-scanner", + dependencies: [ + .byName(name: "Tauri") + ], + path: "Sources") + ] +) diff --git a/plugins/barcode-scanner/ios/README.md b/plugins/barcode-scanner/ios/README.md new file mode 100644 index 000000000..d954848c9 --- /dev/null +++ b/plugins/barcode-scanner/ios/README.md @@ -0,0 +1,3 @@ +# Tauri Plugin Barcode Scanner + +A description of this package. diff --git a/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift new file mode 100644 index 000000000..b86f14887 --- /dev/null +++ b/plugins/barcode-scanner/ios/Sources/BarcodeScannerPlugin.swift @@ -0,0 +1,300 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import AVFoundation +import Tauri +import UIKit +import WebKit + +enum SupportedFormat: String, CaseIterable { + // UPC_A not supported + case UPC_E + case EAN_8 + case EAN_13 + case CODE_39 + case CODE_93 + case CODE_128 + // CODABAR not supported + case ITF + case AZTEC + case DATA_MATRIX + case PDF_417 + case QR_CODE + + var value: AVMetadataObject.ObjectType { + switch self { + case .UPC_E: return AVMetadataObject.ObjectType.upce + case .EAN_8: return AVMetadataObject.ObjectType.ean8 + case .EAN_13: return AVMetadataObject.ObjectType.ean13 + case .CODE_39: return AVMetadataObject.ObjectType.code39 + case .CODE_93: return AVMetadataObject.ObjectType.code93 + case .CODE_128: return AVMetadataObject.ObjectType.code128 + case .ITF: return AVMetadataObject.ObjectType.interleaved2of5 + case .AZTEC: return AVMetadataObject.ObjectType.aztec + case .DATA_MATRIX: return AVMetadataObject.ObjectType.dataMatrix + case .PDF_417: return AVMetadataObject.ObjectType.pdf417 + case .QR_CODE: return AVMetadataObject.ObjectType.qr + } + } +} + +enum CaptureError: Error { + case backCameraUnavailable + case frontCameraUnavailable + case couldNotCaptureInput(error: NSError) +} + +class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate { + var webView: WKWebView! + var cameraView: CameraView! + var captureSession: AVCaptureSession? + var captureVideoPreviewLayer: AVCaptureVideoPreviewLayer? + var metaOutput: AVCaptureMetadataOutput? + + var currentCamera = 0 + var frontCamera: AVCaptureDevice? + var backCamera: AVCaptureDevice? + + var isScanning = false + + var windowed = false + var previousBackgroundColor: UIColor? = UIColor.white + + var invoke: Invoke? = nil + + var scanFormats = [AVMetadataObject.ObjectType]() + + public override func load(webview: WKWebView) { + self.webView = webview + loadCamera() + } + + private func loadCamera() { + cameraView = CameraView( + frame: CGRect( + x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) + cameraView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + + public func metadataOutput( + _ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], + from connection: AVCaptureConnection + ) { + if metadataObjects.count == 0 || !self.isScanning { + // while nothing is detected, or if scanning is false, do nothing. + return + } + + let found = metadataObjects[0] as! AVMetadataMachineReadableCodeObject + if scanFormats.contains(found.type) { + var jsObject: JsonObject = [:] + + jsObject["format"] = formatStringFromMetadata(found.type) + if found.stringValue != nil { + jsObject["content"] = found.stringValue + } + + invoke?.resolve(jsObject) + destroy() + + } + } + + private func setupCamera(direction: String, windowed: Bool) { + do { + var cameraDirection = direction + cameraView.backgroundColor = UIColor.clear + if windowed { + webView.superview?.insertSubview(cameraView, belowSubview: webView) + } else { + webView.superview?.insertSubview(cameraView, aboveSubview: webView) + } + + let availableVideoDevices = discoverCaptureDevices() + for device in availableVideoDevices { + if device.position == AVCaptureDevice.Position.back { + backCamera = device + } else if device.position == AVCaptureDevice.Position.front { + frontCamera = device + } + } + + // older iPods have no back camera + if cameraDirection == "back" { + if backCamera == nil { + cameraDirection = "front" + } + } else { + if frontCamera == nil { + cameraDirection = "back" + } + } + + let input: AVCaptureDeviceInput + input = try createCaptureDeviceInput( + cameraDirection: cameraDirection, backCamera: backCamera, frontCamera: frontCamera) + captureSession = AVCaptureSession() + captureSession!.addInput(input) + metaOutput = AVCaptureMetadataOutput() + captureSession!.addOutput(metaOutput!) + metaOutput!.setMetadataObjectsDelegate(self, queue: DispatchQueue.main) + captureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!) + cameraView.addPreviewLayer(captureVideoPreviewLayer) + + self.windowed = windowed + if windowed { + self.previousBackgroundColor = self.webView.backgroundColor + self.webView.isOpaque = false + self.webView.backgroundColor = UIColor.clear + self.webView.scrollView.backgroundColor = UIColor.clear + } + } catch CaptureError.backCameraUnavailable { + // + } catch CaptureError.frontCameraUnavailable { + // + } catch CaptureError.couldNotCaptureInput { + // + } catch { + // + } + } + + private func dismantleCamera() { + if self.captureSession != nil { + self.captureSession!.stopRunning() + self.cameraView.removePreviewLayer() + self.captureVideoPreviewLayer = nil + self.metaOutput = nil + self.captureSession = nil + self.frontCamera = nil + self.backCamera = nil + } + + self.isScanning = false + } + + private func destroy() { + dismantleCamera() + invoke = nil + if windowed { + let backgroundColor = previousBackgroundColor ?? UIColor.white + webView.isOpaque = true + webView.backgroundColor = backgroundColor + webView.scrollView.backgroundColor = backgroundColor + } + } + + private func getPermissionState() -> String { + var permissionState: String + + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: + permissionState = "granted" + case .denied: + permissionState = "denied" + default: + permissionState = "prompt" + } + + return permissionState + } + + @objc override func checkPermissions(_ invoke: Invoke) { + let permissionState = getPermissionState() + invoke.resolve(["camera": permissionState]) + } + + @objc override func requestPermissions(_ invoke: Invoke) { + let state = getPermissionState() + if state == "prompt" { + AVCaptureDevice.requestAccess(for: .video) { (authorized) in + invoke.resolve(["camera": authorized ? "granted" : "denied"]) + } + } else { + invoke.resolve(["camera": state]) + } + } + + @objc func openAppSettings(_ invoke: Invoke) { + guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { + return + } + + DispatchQueue.main.async { + if UIApplication.shared.canOpenURL(settingsUrl) { + UIApplication.shared.open( + settingsUrl, + completionHandler: { (success) in + invoke.resolve() + }) + } + } + } + + private func runScanner(_ invoke: Invoke) { + scanFormats = [AVMetadataObject.ObjectType]() + + if (invoke.data["formats"]) != nil { + let _scanFormats = invoke.getArray("formats", String.self) + + if _scanFormats != nil && _scanFormats?.count ?? 0 > 0 { + _scanFormats?.forEach { targetedFormat in + if let value = SupportedFormat(rawValue: targetedFormat)?.value { + scanFormats.append(value) + } + } + } + } + + if scanFormats.count == 0 { + for supportedFormat in SupportedFormat.allCases { + scanFormats.append(supportedFormat.value) + } + } + + self.metaOutput!.metadataObjectTypes = self.scanFormats + self.captureSession!.startRunning() + + self.isScanning = true + } + + @objc private func scan(_ invoke: Invoke) { + self.invoke = invoke + + var iOS14min: Bool = false + if #available(iOS 14.0, *) { iOS14min = true } + if !iOS14min && self.getPermissionState() != "granted" { + var authorized = false + AVCaptureDevice.requestAccess(for: .video) { (isAuthorized) in + authorized = isAuthorized + } + if !authorized { + invoke.reject("denied by the user") + return + } + } + + DispatchQueue.main.async { [self] in + self.loadCamera() + self.dismantleCamera() + self.setupCamera( + direction: invoke.getString("cameraDirection") ?? "back", + windowed: invoke.getBool("windowed") ?? false + ) + self.runScanner(invoke) + } + } + + @objc private func cancel(_ invoke: Invoke) { + self.invoke?.reject("cancelled") + + destroy() + invoke.resolve() + } +} + +@_cdecl("init_plugin_barcode_scanner") +func initPlugin() -> Plugin { + return BarcodeScannerPlugin() +} diff --git a/plugins/barcode-scanner/ios/Sources/CameraView.swift b/plugins/barcode-scanner/ios/Sources/CameraView.swift new file mode 100644 index 000000000..7f7b0cda5 --- /dev/null +++ b/plugins/barcode-scanner/ios/Sources/CameraView.swift @@ -0,0 +1,57 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import AVFoundation +import UIKit + +class CameraView: UIView { + var videoPreviewLayer: AVCaptureVideoPreviewLayer? + + func interfaceOrientationToVideoOrientation(_ orientation: UIInterfaceOrientation) + -> AVCaptureVideoOrientation + { + switch orientation { + case UIInterfaceOrientation.portrait: + return AVCaptureVideoOrientation.portrait + case UIInterfaceOrientation.portraitUpsideDown: + return AVCaptureVideoOrientation.portraitUpsideDown + case UIInterfaceOrientation.landscapeLeft: + return AVCaptureVideoOrientation.landscapeLeft + case UIInterfaceOrientation.landscapeRight: + return AVCaptureVideoOrientation.landscapeRight + default: + return AVCaptureVideoOrientation.portraitUpsideDown + } + } + + override func layoutSubviews() { + super.layoutSubviews() + if let sublayers = self.layer.sublayers { + for layer in sublayers { + layer.frame = self.bounds + } + } + + if let interfaceOrientation = UIApplication.shared.windows.first(where: { $0.isKeyWindow })? + .windowScene?.interfaceOrientation + { + self.videoPreviewLayer?.connection?.videoOrientation = interfaceOrientationToVideoOrientation( + interfaceOrientation) + } + } + + func addPreviewLayer(_ previewLayer: AVCaptureVideoPreviewLayer?) { + previewLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill + previewLayer!.frame = self.bounds + self.layer.addSublayer(previewLayer!) + self.videoPreviewLayer = previewLayer + } + + func removePreviewLayer() { + if self.videoPreviewLayer != nil { + self.videoPreviewLayer!.removeFromSuperlayer() + self.videoPreviewLayer = nil + } + } +} diff --git a/plugins/barcode-scanner/ios/Sources/Utils.swift b/plugins/barcode-scanner/ios/Sources/Utils.swift new file mode 100644 index 000000000..1d0e6e4fa --- /dev/null +++ b/plugins/barcode-scanner/ios/Sources/Utils.swift @@ -0,0 +1,81 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import AVFoundation + +func createCaptureDeviceInput( + cameraDirection: String, backCamera: AVCaptureDevice?, frontCamera: AVCaptureDevice? +) throws + -> AVCaptureDeviceInput +{ + var captureDevice: AVCaptureDevice + if cameraDirection == "back" { + if backCamera != nil { + captureDevice = backCamera! + } else { + throw CaptureError.backCameraUnavailable + } + } else { + if frontCamera != nil { + captureDevice = frontCamera! + } else { + throw CaptureError.frontCameraUnavailable + } + } + let captureDeviceInput: AVCaptureDeviceInput + do { + captureDeviceInput = try AVCaptureDeviceInput(device: captureDevice) + } catch let error as NSError { + throw CaptureError.couldNotCaptureInput(error: error) + } + return captureDeviceInput +} + +func discoverCaptureDevices() -> [AVCaptureDevice] { + if #available(iOS 13.0, *) { + return AVCaptureDevice.DiscoverySession( + deviceTypes: [ + .builtInTripleCamera, .builtInDualCamera, .builtInTelephotoCamera, + .builtInTrueDepthCamera, + .builtInUltraWideCamera, .builtInDualWideCamera, .builtInWideAngleCamera, + ], mediaType: .video, position: .unspecified + ).devices + } else { + return AVCaptureDevice.DiscoverySession( + deviceTypes: [ + .builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera, + .builtInTrueDepthCamera, + ], mediaType: .video, position: .unspecified + ).devices + } +} + +func formatStringFromMetadata(_ type: AVMetadataObject.ObjectType) -> String { + switch type { + case AVMetadataObject.ObjectType.upce: + return "UPC_E" + case AVMetadataObject.ObjectType.ean8: + return "EAN_8" + case AVMetadataObject.ObjectType.ean13: + return "EAN_13" + case AVMetadataObject.ObjectType.code39: + return "CODE_39" + case AVMetadataObject.ObjectType.code93: + return "CODE_93" + case AVMetadataObject.ObjectType.code128: + return "CODE_128" + case AVMetadataObject.ObjectType.interleaved2of5: + return "ITF" + case AVMetadataObject.ObjectType.aztec: + return "AZTEC" + case AVMetadataObject.ObjectType.dataMatrix: + return "DATA_MATRIX" + case AVMetadataObject.ObjectType.pdf417: + return "PDF_417" + case AVMetadataObject.ObjectType.qr: + return "QR_CODE" + default: + return type.rawValue + } +} diff --git a/plugins/barcode-scanner/ios/Tests/PluginTests/PluginTests.swift b/plugins/barcode-scanner/ios/Tests/PluginTests/PluginTests.swift new file mode 100644 index 000000000..9693b5c51 --- /dev/null +++ b/plugins/barcode-scanner/ios/Tests/PluginTests/PluginTests.swift @@ -0,0 +1,13 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import XCTest + +@testable import ExamplePlugin + +final class ExamplePluginTests: XCTestCase { + func testExample() throws { + let plugin = ExamplePlugin() + } +} diff --git a/plugins/barcode-scanner/package.json b/plugins/barcode-scanner/package.json new file mode 100644 index 000000000..b320cdee5 --- /dev/null +++ b/plugins/barcode-scanner/package.json @@ -0,0 +1,33 @@ +{ + "name": "@tauri-apps/plugin-barcode-scanner", + "version": "1.0.0", + "description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS", + "license": "MIT or APACHE-2.0", + "authors": [ + "Tauri Programme within The Commons Conservancy" + ], + "type": "module", + "browser": "dist-js/index.min.js", + "module": "dist-js/index.mjs", + "types": "dist-js/index.d.ts", + "exports": { + "import": "./dist-js/index.mjs", + "types": "./dist-js/index.d.ts", + "browser": "./dist-js/index.min.js" + }, + "scripts": { + "build": "rollup -c" + }, + "files": [ + "dist-js", + "!dist-js/**/*.map", + "README.md", + "LICENSE" + ], + "devDependencies": { + "tslib": "^2.5.0" + }, + "dependencies": { + "@tauri-apps/api": "2.0.0-alpha.8" + } +} diff --git a/plugins/barcode-scanner/rollup.config.mjs b/plugins/barcode-scanner/rollup.config.mjs new file mode 100644 index 000000000..99a3dd319 --- /dev/null +++ b/plugins/barcode-scanner/rollup.config.mjs @@ -0,0 +1,11 @@ +import { readFileSync } from "fs"; + +import { createConfig } from "../../shared/rollup.config.mjs"; + +export default createConfig({ + input: "guest-js/index.ts", + pkg: JSON.parse( + readFileSync(new URL("./package.json", import.meta.url), "utf8"), + ), + external: [/^@tauri-apps\/api/], +}); diff --git a/plugins/barcode-scanner/src/api-iife.js b/plugins/barcode-scanner/src/api-iife.js new file mode 100644 index 000000000..2d1c2a551 --- /dev/null +++ b/plugins/barcode-scanner/src/api-iife.js @@ -0,0 +1 @@ +if("__TAURI__"in window){var __TAURI_BARCODESCANNER__=function(e){"use strict";var n=Object.defineProperty,t=(e,n,t)=>{if(!n.has(e))throw TypeError("Cannot "+t)},r=(e,n,r)=>(t(e,n,"read from private field"),r?r.call(e):n.get(e)),a=(e,n,r,a)=>(t(e,n,"write to private field"),a?a.call(e,r):n.set(e,r),r);function i(e,n=!1){let t=window.crypto.getRandomValues(new Uint32Array(1))[0],r=`_${t}`;return Object.defineProperty(window,r,{value:t=>(n&&Reflect.deleteProperty(window,r),e?.(t)),writable:!1,configurable:!0}),t}((e,t)=>{for(var r in t)n(e,r,{get:t[r],enumerable:!0})})({},{Channel:()=>c,PluginListener:()=>_,addPluginListener:()=>l,convertFileSrc:()=>u,invoke:()=>d,transformCallback:()=>i});var o,c=class{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,((e,n,t)=>{if(n.has(e))throw TypeError("Cannot add the same private member more than once");n instanceof WeakSet?n.add(e):n.set(e,t)})(this,o,(()=>{})),this.id=i((e=>{r(this,o).call(this,e)}))}set onmessage(e){a(this,o,e)}get onmessage(){return r(this,o)}toJSON(){return`__CHANNEL__:${this.id}`}};o=new WeakMap;var s,_=class{constructor(e,n,t){this.plugin=e,this.event=n,this.channelId=t}async unregister(){return d(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}};async function l(e,n,t){let r=new c;return r.onmessage=t,d(`plugin:${e}|register_listener`,{event:n,handler:r}).then((()=>new _(e,n,r.id)))}async function d(e,n={},t){return new Promise(((r,a)=>{let o=i((e=>{r(e),Reflect.deleteProperty(window,`_${c}`)}),!0),c=i((e=>{a(e),Reflect.deleteProperty(window,`_${o}`)}),!0);window.__TAURI_IPC__({cmd:e,callback:o,error:c,payload:n,options:t})}))}function u(e,n="asset"){return window.__TAURI__.convertFileSrc(e,n)}return e.Format=void 0,(s=e.Format||(e.Format={})).QRCode="QR_CODE",s.UPC_A="UPC_A",s.UPC_E="UPC_E",s.EAN8="EAN_8",s.EAN13="EAN_13",s.Code39="CODE_39",s.Code93="CODE_93",s.Code128="CODE_128",s.Codabar="CODABAR",s.ITF="ITF",s.Aztec="AZTEC",s.DataMatrix="DATA_MATRIX",s.PDF417="PDF_417",e.cancel=async function(){return await d("plugin:barcodeScanner|cancel")},e.checkPermissions=async function(){return await d("plugin:barcodeScanner|checkPermissions").then((e=>e.camera))},e.openAppSettings=async function(){return await d("plugin:barcodeScanner|openAppSettings")},e.requestPermissions=async function(){return await d("plugin:barcodeScanner|requestPermissions").then((e=>e.camera))},e.scan=async function(e){return await d("plugin:barcodeScanner|scan",{...e})},e}({});Object.defineProperty(window.__TAURI__,"barcodeScanner",{value:__TAURI_BARCODESCANNER__})} diff --git a/plugins/barcode-scanner/src/error.rs b/plugins/barcode-scanner/src/error.rs new file mode 100644 index 000000000..339e763b1 --- /dev/null +++ b/plugins/barcode-scanner/src/error.rs @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use serde::{ser::Serializer, Serialize}; + +pub type Result = std::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[cfg(mobile)] + #[error(transparent)] + PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), +} + +impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + serializer.serialize_str(self.to_string().as_ref()) + } +} diff --git a/plugins/barcode-scanner/src/lib.rs b/plugins/barcode-scanner/src/lib.rs new file mode 100644 index 000000000..0ca820364 --- /dev/null +++ b/plugins/barcode-scanner/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +#![cfg(mobile)] + +use tauri::{ + plugin::{Builder, PluginHandle, TauriPlugin}, + Manager, Runtime, +}; + +pub use models::*; + +mod error; +mod models; + +pub use error::{Error, Result}; + +#[cfg(target_os = "android")] +const PLUGIN_IDENTIFIER: &str = "app.tauri.barcodescanner"; + +#[cfg(target_os = "ios")] +tauri::ios_plugin_binding!(init_plugin_barcode_scanner); + +/// Access to the scanner APIs. +pub struct BarcodeScanner(PluginHandle); + +impl BarcodeScanner {} + +/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the barcode scanner APIs. +pub trait BarcodeScannerExt { + fn barcode_scanner(&self) -> &BarcodeScanner; +} + +impl> crate::BarcodeScannerExt for T { + fn barcode_scanner(&self) -> &BarcodeScanner { + self.state::>().inner() + } +} + +/// Initializes the plugin. +pub fn init() -> TauriPlugin { + Builder::new("barcodeScanner") + .setup(|app, api| { + #[cfg(target_os = "android")] + let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "BarcodeScannerPlugin")?; + #[cfg(target_os = "ios")] + let handle = api.register_ios_plugin(init_plugin_barcode_scanner)?; + app.manage(BarcodeScanner(handle)); + Ok(()) + }) + .build() +} diff --git a/plugins/barcode-scanner/src/mobile.rs b/plugins/barcode-scanner/src/mobile.rs new file mode 100644 index 000000000..a05603857 --- /dev/null +++ b/plugins/barcode-scanner/src/mobile.rs @@ -0,0 +1,3 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT diff --git a/plugins/barcode-scanner/src/models.rs b/plugins/barcode-scanner/src/models.rs new file mode 100644 index 000000000..a05603857 --- /dev/null +++ b/plugins/barcode-scanner/src/models.rs @@ -0,0 +1,3 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT diff --git a/plugins/barcode-scanner/tsconfig.json b/plugins/barcode-scanner/tsconfig.json new file mode 100644 index 000000000..5098169aa --- /dev/null +++ b/plugins/barcode-scanner/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["guest-js/*.ts"] +} diff --git a/plugins/notification/src/desktop.rs b/plugins/notification/src/desktop.rs index 897790f0a..7dfde80b4 100644 --- a/plugins/notification/src/desktop.rs +++ b/plugins/notification/src/desktop.rs @@ -14,7 +14,7 @@ pub fn init( Ok(Notification(app.clone())) } -/// Access to the {{ plugin_name }} APIs. +/// Access to the notification APIs. pub struct Notification(AppHandle); impl crate::NotificationBuilder { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f008c821..3e9dff68d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: '@tauri-apps/plugin-app': specifier: 2.0.0-alpha.1 version: link:../../plugins/app + '@tauri-apps/plugin-barcode-scanner': + specifier: 1.0.0 + version: link:../../plugins/barcode-scanner '@tauri-apps/plugin-cli': specifier: 2.0.0-alpha.1 version: link:../../plugins/cli @@ -167,6 +170,16 @@ importers: specifier: 2.6.0 version: 2.6.0 + plugins/barcode-scanner: + dependencies: + '@tauri-apps/api': + specifier: 2.0.0-alpha.8 + version: 2.0.0-alpha.8 + devDependencies: + tslib: + specifier: ^2.5.0 + version: 2.6.0 + plugins/cli: dependencies: '@tauri-apps/api':