feat: add barcode scanner plugin (#536)
@ -0,0 +1,6 @@
|
||||
---
|
||||
"barcode-scanner": major
|
||||
"barcode-scanner-js": major
|
||||
---
|
||||
|
||||
Initial release.
|
@ -1,16 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.api" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<style name="Theme.api" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
|
@ -1,16 +1,6 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.api" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<style name="Theme.api" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
|
@ -0,0 +1,3 @@
|
||||
xcuserdata/
|
||||
build/
|
||||
Externals/
|
After Width: | Height: | Size: 844 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 152 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 11 KiB |
@ -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"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
@ -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
|
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace ffi {
|
||||
extern "C" {
|
||||
void start_app();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
#include "bindings/bindings.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
ffi::start_app();
|
||||
return 0;
|
||||
}
|
@ -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 = "<group>"; };
|
||||
1C1AB1B414CA2795AFBEDDB9 /* tray.rs */ = {isa = PBXFileReference; path = tray.rs; sourceTree = "<group>"; };
|
||||
2F63E2AA460089BB58D40C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
879941AE3DAA14534BBC6391 /* api_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = api_iOS.entitlements; sourceTree = "<group>"; };
|
||||
90D3B673AFAB8D8AB561F616 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
F835F52713CE8F029D5D252C /* cmd.rs */ = {isa = PBXFileReference; path = cmd.rs; sourceTree = "<group>"; };
|
||||
FC53D4128D7F74E4E6338455 /* libapi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libapi.a; sourceTree = "<group>"; };
|
||||
/* 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 = "<group>";
|
||||
};
|
||||
07051859D6E2D8109C8FB128 /* bindings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EC8C7948C50C3C9B5D96CB61 /* bindings.h */,
|
||||
);
|
||||
path = bindings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
4AC51E67B71E27F15B02C5CD /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5AC703CEBA41A121596066F3 /* api_iOS.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A9A7AC155D9E22E54D6D847 /* Sources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A3574F52DBC5463B9C3D043D /* api */,
|
||||
);
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
86D903732E10FAC4D300E8DF /* Externals */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = Externals;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A3574F52DBC5463B9C3D043D /* api */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
90D3B673AFAB8D8AB561F616 /* main.mm */,
|
||||
07051859D6E2D8109C8FB128 /* bindings */,
|
||||
);
|
||||
path = api;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CF9AA87D2F6E9C389B7AB70B /* src */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F835F52713CE8F029D5D252C /* cmd.rs */,
|
||||
785D025E9542F7E098BF22B5 /* lib.rs */,
|
||||
0E96CE07CD20273DD46BF325 /* main.rs */,
|
||||
1C1AB1B414CA2795AFBEDDB9 /* tray.rs */,
|
||||
);
|
||||
name = src;
|
||||
path = ../../src;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F2116A6428EED18BE2A07E2B /* api_iOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
879941AE3DAA14534BBC6391 /* api_iOS.entitlements */,
|
||||
2F63E2AA460089BB58D40C79 /* Info.plist */,
|
||||
);
|
||||
path = api_iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* 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 */;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildSystemType</key>
|
||||
<string>Original</string>
|
||||
<key>DisableBuildSystemDeprecationDiagnostic</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
runPostActionsOnFailure = "NO">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "54DC6E273C78071F3BA12EF3"
|
||||
BuildableName = "api_iOS.app"
|
||||
BlueprintName = "api_iOS"
|
||||
ReferencedContainer = "container:api.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "NO"
|
||||
onlyGenerateCoverageForSpecifiedTargets = "NO">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "54DC6E273C78071F3BA12EF3"
|
||||
BuildableName = "api_iOS.app"
|
||||
BlueprintName = "api_iOS"
|
||||
ReferencedContainer = "container:api.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
<CommandLineArguments>
|
||||
</CommandLineArguments>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "RUST_BACKTRACE"
|
||||
value = "full"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "RUST_LOG"
|
||||
value = "info"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "54DC6E273C78071F3BA12EF3"
|
||||
BuildableName = "api_iOS.app"
|
||||
BlueprintName = "api_iOS"
|
||||
ReferencedContainer = "container:api.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
</CommandLineArguments>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "RUST_BACKTRACE"
|
||||
value = "full"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "RUST_LOG"
|
||||
value = "info"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "release"
|
||||
shouldUseLaunchSchemeArgsEnv = "NO"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "54DC6E273C78071F3BA12EF3"
|
||||
BuildableName = "api_iOS.app"
|
||||
BlueprintName = "api_iOS"
|
||||
ReferencedContainer = "container:api.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<CommandLineArguments>
|
||||
</CommandLineArguments>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "RUST_BACKTRACE"
|
||||
value = "full"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "RUST_LOG"
|
||||
value = "info"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2.0.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
<string>metal</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>To be able to scan barcodes</string>
|
||||
</dict>
|
||||
</plist>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
@ -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
|
@ -0,0 +1,155 @@
|
||||
<script>
|
||||
import { scan, checkPermissions, requestPermissions, Format, cancel } from "@tauri-apps/plugin-barcode-scanner";
|
||||
|
||||
export let onMessage;
|
||||
|
||||
let scanning = false;
|
||||
let windowed = true;
|
||||
let formats = [Format.QRCode];
|
||||
const supportedFormats = [Format.QRCode, Format.EAN13];
|
||||
|
||||
async function startScan() {
|
||||
let permission = await checkPermissions();
|
||||
if (permission === 'prompt') {
|
||||
permission = await requestPermissions();
|
||||
}
|
||||
if (permission === 'granted') {
|
||||
scanning = true;
|
||||
scan({ windowed, formats })
|
||||
.then((res) => {
|
||||
scanning = false;
|
||||
onMessage(res);
|
||||
})
|
||||
.catch((error) => {
|
||||
scanning = false;
|
||||
onMessage(error);
|
||||
});
|
||||
} else {
|
||||
onMessage('Permission denied')
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelScan() {
|
||||
await cancel();
|
||||
scanning = false;
|
||||
onMessage("cancelled");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="full-height">
|
||||
<div class:invisible={scanning}>
|
||||
<div>
|
||||
<input type="checkbox" id="scanner-windowed" bind:checked={windowed} />
|
||||
<label for="scanner-windowed">Windowed</label>
|
||||
</div>
|
||||
<div>
|
||||
<select class="input" id="format" multiple bind:value={formats}>
|
||||
{#each supportedFormats as f}
|
||||
<option value={f}>{f}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn" type="button" on:click={startScan}>Scan</button>
|
||||
</div>
|
||||
<div class="scanning full-height" class:invisible={!scanning}>
|
||||
<div class="scanner-background">
|
||||
<!-- this background simulates the camera view -->
|
||||
</div>
|
||||
<div class="container full-height">
|
||||
<div class="barcode-scanner--area--container">
|
||||
<div class="relative">
|
||||
<p>Aim your camera at a QR code</p>
|
||||
<button class="btn" type="button" on:click={cancelScan}>Cancel</button>
|
||||
</div>
|
||||
<div class="square surround-cover">
|
||||
<div class="barcode-scanner--area--outer surround-cover">
|
||||
<div class="barcode-scanner--area--inner" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.square {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.square:after {
|
||||
content: "";
|
||||
top: 0;
|
||||
display: block;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
.square > div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.surround-cover {
|
||||
box-shadow: 0 0 0 99999px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.barcode-scanner--area--container {
|
||||
width: 80%;
|
||||
max-width: min(500px, 80vh);
|
||||
margin: auto;
|
||||
}
|
||||
.barcode-scanner--area--outer {
|
||||
display: flex;
|
||||
border-radius: 1em;
|
||||
}
|
||||
.barcode-scanner--area--inner {
|
||||
width: 100%;
|
||||
margin: 1rem;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0px 0px 2px 1px rgb(0 0 0 / 0.5),
|
||||
inset 0px 0px 2px 1px rgb(0 0 0 / 0.5);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.scanner-background {
|
||||
background: linear-gradient(45deg, #673ab7, transparent);
|
||||
background-position: 45% 50%;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
</style>
|
@ -0,0 +1 @@
|
||||
/.tauri
|
@ -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 }
|
@ -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: <text>Tauri is a rust project that enables developers to make secure
|
||||
and small desktop applications using a web frontend.
|
||||
</text>
|
||||
PackageComment: <text>The package includes the following libraries; see
|
||||
Relationship information.
|
||||
</text>
|
||||
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
|
@ -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
|
@ -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.
|
@ -0,0 +1,98 @@
|
||||

|
||||
|
||||
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
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://crabnebula.dev" target="_blank">
|
||||
<img src="contributors/crabnebula.svg" alt="CrabNebula" width="283">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://impierce.com" target="_blank">
|
||||
<img src="contributors/impierce.svg" alt="Impierce" width="283" height="90">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## License
|
||||
|
||||
Code: (c) 2015 - Present - The Tauri Programme within The Commons Conservancy.
|
||||
|
||||
MIT or MIT/Apache 2.0 where applicable.
|
@ -0,0 +1,2 @@
|
||||
/build
|
||||
/.tauri
|
@ -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"))
|
||||
}
|
@ -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
|
@ -0,0 +1,2 @@
|
||||
include ':tauri-android'
|
||||
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:hardwareAccelerated="true">
|
||||
|
||||
<!-- Paste the following line into the AndroidManifest.xml of your project -->
|
||||
<!-- See also: https://capacitorjs.com/docs/plugins/android#manifest -->
|
||||
<!-- <uses-feature android:name="android.hardware.camera" android:required="false" /> -->
|
||||
|
||||
<uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
</manifest>
|
@ -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<ProcessCameraProvider>? = 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<String, Int> {
|
||||
val map: MutableMap<String, Int> = 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<Int> {
|
||||
val jsFormats = invoke.getArray("formats", JSArray())
|
||||
val formats = ArrayList<Int>()
|
||||
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<Int>) {
|
||||
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<Int>): 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()
|
||||
}
|
||||
}
|
@ -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<Graphic> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 48 KiB |
@ -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);
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
@ -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<Scanned> {
|
||||
return await invoke("plugin:barcodeScanner|scan", { ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the current scan process.
|
||||
*/
|
||||
export async function cancel(): Promise<void> {
|
||||
return await invoke("plugin:barcodeScanner|cancel");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permission state.
|
||||
*/
|
||||
export async function checkPermissions(): Promise<PermissionState> {
|
||||
return await invoke<{ camera: PermissionState }>(
|
||||
"plugin:barcodeScanner|checkPermissions",
|
||||
).then((r) => r.camera);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request permissions to use the camera.
|
||||
*/
|
||||
export async function requestPermissions(): Promise<PermissionState> {
|
||||
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<void> {
|
||||
return await invoke("plugin:barcodeScanner|openAppSettings");
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
Package.resolved
|
@ -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")
|
||||
]
|
||||
)
|
@ -0,0 +1,3 @@
|
||||
# Tauri Plugin Barcode Scanner
|
||||
|
||||
A description of this package.
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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/],
|
||||
});
|
@ -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__})}
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
@ -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<R: Runtime>(PluginHandle<R>);
|
||||
|
||||
impl<R: Runtime> BarcodeScanner<R> {}
|
||||
|
||||
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the barcode scanner APIs.
|
||||
pub trait BarcodeScannerExt<R: Runtime> {
|
||||
fn barcode_scanner(&self) -> &BarcodeScanner<R>;
|
||||
}
|
||||
|
||||
impl<R: Runtime, T: Manager<R>> crate::BarcodeScannerExt<R> for T {
|
||||
fn barcode_scanner(&self) -> &BarcodeScanner<R> {
|
||||
self.state::<BarcodeScanner<R>>().inner()
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the plugin.
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
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()
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
@ -0,0 +1,3 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["guest-js/*.ts"]
|
||||
}
|