expo-harmony-toolkit 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +32 -16
- package/README.md +37 -15
- package/build/core/build.js +1 -1
- package/build/core/constants.d.ts +2 -1
- package/build/core/constants.js +3 -2
- package/build/core/env.js +10 -12
- package/build/core/report.js +29 -1
- package/build/core/signing.d.ts +11 -0
- package/build/core/signing.js +113 -0
- package/build/core/template.js +3182 -182
- package/build/data/capabilities.js +50 -19
- package/build/types.d.ts +19 -1
- package/docs/cli-build.md +1 -1
- package/docs/npm-release.md +70 -23
- package/docs/official-app-shell-sample.md +33 -12
- package/docs/official-minimal-sample.md +43 -10
- package/docs/official-native-capabilities-sample.md +79 -23
- package/docs/official-ui-stack-sample.md +23 -18
- package/docs/roadmap.md +68 -40
- package/docs/support-matrix.md +75 -39
- package/docs/v1.7.0-acceptance.md +70 -101
- package/docs/v1.7.1-acceptance.md +111 -0
- package/docs/v1.7.2-acceptance.md +37 -0
- package/package.json +1 -1
package/build/core/template.js
CHANGED
|
@@ -23,6 +23,7 @@ const uiStack_1 = require("../data/uiStack");
|
|
|
23
23
|
const project_1 = require("./project");
|
|
24
24
|
const javascriptDependencies_1 = require("./javascriptDependencies");
|
|
25
25
|
const report_1 = require("./report");
|
|
26
|
+
const signing_1 = require("./signing");
|
|
26
27
|
const TEMPLATE_ROOT = path_1.default.resolve(__dirname, '..', '..', 'templates', 'harmony');
|
|
27
28
|
const TEMPLATE_FILE_PATHS = [
|
|
28
29
|
'README.md',
|
|
@@ -149,7 +150,9 @@ async function syncProjectTemplate(projectRoot, force = false, options = {}) {
|
|
|
149
150
|
async function buildManagedFiles(loadedProject, identifiers, previousToolkitConfig) {
|
|
150
151
|
const hasExpoRouter = usesExpoRouter(loadedProject.packageJson);
|
|
151
152
|
const enabledCapabilities = (0, capabilities_1.getCapabilityDefinitionsForProject)(loadedProject.packageJson);
|
|
153
|
+
const hasManagedExpoHarmonyPackage = enabledCapabilities.some((capability) => capability.runtimeMode !== 'shim');
|
|
152
154
|
const requestedHarmonyPermissions = (0, capabilities_1.collectCapabilityHarmonyPermissions)(loadedProject.packageJson);
|
|
155
|
+
const signingLocalConfig = await (0, signing_1.readSigningLocalConfig)(loadedProject.projectRoot);
|
|
153
156
|
const hvigorPluginFilename = await (0, project_1.resolveRnohHvigorPluginFilename)(loadedProject.projectRoot);
|
|
154
157
|
const renderedHarmonyRootPackage = renderTemplate(await fs_extra_1.default.readFile(path_1.default.join(TEMPLATE_ROOT, 'oh-package.json5'), 'utf8'), loadedProject, identifiers, hvigorPluginFilename);
|
|
155
158
|
const templateFiles = await Promise.all(TEMPLATE_FILE_PATHS.map(async (relativePath) => {
|
|
@@ -161,11 +164,21 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
161
164
|
: renderTemplate(rawContents.toString('utf8'), loadedProject, identifiers, hvigorPluginFilename);
|
|
162
165
|
return {
|
|
163
166
|
relativePath: path_1.default.join('harmony', relativePath),
|
|
164
|
-
contents: relativePath === '
|
|
165
|
-
?
|
|
166
|
-
: relativePath === 'entry/src/main/
|
|
167
|
-
?
|
|
168
|
-
:
|
|
167
|
+
contents: relativePath === 'build-profile.json5'
|
|
168
|
+
? (0, signing_1.mergeSigningLocalConfigIntoBuildProfile)(contents.toString('utf8'), signingLocalConfig)
|
|
169
|
+
: relativePath === 'entry/src/main/module.json5'
|
|
170
|
+
? renderEntryModuleConfig(identifiers.entryModuleName, requestedHarmonyPermissions)
|
|
171
|
+
: relativePath === 'entry/src/main/resources/base/element/string.json'
|
|
172
|
+
? renderEntryStringResources(`${identifiers.appName} official minimal Harmony sample`, identifiers.appName, requestedHarmonyPermissions)
|
|
173
|
+
: relativePath === 'entry/src/main/ets/PackageProvider.ets'
|
|
174
|
+
? renderPackageProvider({
|
|
175
|
+
hasManagedExpoHarmonyPackage,
|
|
176
|
+
})
|
|
177
|
+
: relativePath === 'entry/src/main/cpp/PackageProvider.cpp'
|
|
178
|
+
? renderPackageProviderCpp({
|
|
179
|
+
hasManagedExpoHarmonyPackage,
|
|
180
|
+
})
|
|
181
|
+
: contents,
|
|
169
182
|
binary,
|
|
170
183
|
};
|
|
171
184
|
}));
|
|
@@ -179,7 +192,13 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
179
192
|
rnohCliVersion: constants_1.RNOH_CLI_VERSION,
|
|
180
193
|
bundleName: identifiers.bundleName,
|
|
181
194
|
entryModuleName: identifiers.entryModuleName,
|
|
182
|
-
capabilities: enabledCapabilities.map((capability) =>
|
|
195
|
+
capabilities: enabledCapabilities.map((capability) => ({
|
|
196
|
+
id: capability.id,
|
|
197
|
+
packageName: capability.packageName,
|
|
198
|
+
supportTier: capability.supportTier,
|
|
199
|
+
runtimeMode: capability.runtimeMode,
|
|
200
|
+
evidence: { ...capability.evidence },
|
|
201
|
+
})),
|
|
183
202
|
requestedHarmonyPermissions,
|
|
184
203
|
project: {
|
|
185
204
|
name: loadedProject.expoConfig.name ?? identifiers.appName,
|
|
@@ -224,6 +243,34 @@ async function buildManagedFiles(loadedProject, identifiers, previousToolkitConf
|
|
|
224
243
|
},
|
|
225
244
|
]
|
|
226
245
|
: []),
|
|
246
|
+
...(hasManagedExpoHarmonyPackage
|
|
247
|
+
? [
|
|
248
|
+
{
|
|
249
|
+
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'cpp', 'expoHarmony', 'ExpoHarmonyPackage.h'),
|
|
250
|
+
contents: renderExpoHarmonyCppPackage(),
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyPackage.ets'),
|
|
254
|
+
contents: renderExpoHarmonyPackage(),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyFileSystemTurboModule.ts'),
|
|
258
|
+
contents: renderExpoHarmonyFileSystemTurboModule(),
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyImagePickerTurboModule.ts'),
|
|
262
|
+
contents: renderExpoHarmonyImagePickerTurboModule(),
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyLocationTurboModule.ts'),
|
|
266
|
+
contents: renderExpoHarmonyLocationTurboModule(),
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
relativePath: path_1.default.join('harmony', 'entry', 'src', 'main', 'ets', 'expoHarmony', 'ExpoHarmonyCameraTurboModule.ts'),
|
|
270
|
+
contents: renderExpoHarmonyCameraTurboModule(),
|
|
271
|
+
},
|
|
272
|
+
]
|
|
273
|
+
: []),
|
|
227
274
|
{
|
|
228
275
|
relativePath: path_1.default.join(constants_1.GENERATED_DIR, constants_1.TOOLKIT_CONFIG_FILENAME),
|
|
229
276
|
contents: JSON.stringify(toolkitConfig, null, 2) + '\n',
|
|
@@ -669,11 +716,17 @@ function getHarmonyPermissionReasonValue(permissionName) {
|
|
|
669
716
|
switch (permissionName) {
|
|
670
717
|
case 'ohos.permission.CAMERA':
|
|
671
718
|
return 'Camera access is required for Harmony preview image capture flows.';
|
|
719
|
+
case 'ohos.permission.MICROPHONE':
|
|
720
|
+
return 'Microphone access is required for Harmony preview video recording flows.';
|
|
672
721
|
case 'ohos.permission.READ_IMAGEVIDEO':
|
|
673
722
|
return 'Media library access is required for Harmony preview image selection flows.';
|
|
674
723
|
case 'ohos.permission.LOCATION':
|
|
675
724
|
case 'ohos.permission.APPROXIMATELY_LOCATION':
|
|
676
725
|
return 'Location access is required for Harmony preview location flows.';
|
|
726
|
+
case 'ohos.permission.LOCATION_IN_BACKGROUND':
|
|
727
|
+
return 'Background location access is required for Harmony preview background location flows.';
|
|
728
|
+
case 'ohos.permission.ACCELEROMETER':
|
|
729
|
+
return 'Motion sensor access is required for Harmony preview heading flows.';
|
|
677
730
|
case 'ohos.permission.NOTIFICATION_CONTROLLER':
|
|
678
731
|
return 'Notification access is required for Harmony preview notification flows.';
|
|
679
732
|
default:
|
|
@@ -683,6 +736,7 @@ function getHarmonyPermissionReasonValue(permissionName) {
|
|
|
683
736
|
function getHarmonyPermissionWhen(permissionName) {
|
|
684
737
|
switch (permissionName) {
|
|
685
738
|
case 'ohos.permission.NOTIFICATION_CONTROLLER':
|
|
739
|
+
case 'ohos.permission.LOCATION_IN_BACKGROUND':
|
|
686
740
|
return 'always';
|
|
687
741
|
default:
|
|
688
742
|
return 'inuse';
|
|
@@ -711,16 +765,299 @@ function renderEntryStringResources(appDescription, appName, requestedHarmonyPer
|
|
|
711
765
|
string: stringEntries,
|
|
712
766
|
}, null, 2) + '\n';
|
|
713
767
|
}
|
|
768
|
+
function renderPackageProvider(options) {
|
|
769
|
+
const managedImports = options.hasManagedExpoHarmonyPackage
|
|
770
|
+
? "import { ExpoHarmonyPackage } from './expoHarmony/ExpoHarmonyPackage';\n"
|
|
771
|
+
: '';
|
|
772
|
+
const managedPackages = options.hasManagedExpoHarmonyPackage ? ', new ExpoHarmonyPackage(ctx)' : '';
|
|
773
|
+
return `import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';
|
|
774
|
+
import { createRNOHPackages as createRNOHPackagesAutolinking } from './RNOHPackagesFactory';
|
|
775
|
+
${managedImports}
|
|
776
|
+
export function getRNOHPackages(ctx: RNPackageContext): RNPackage[] {
|
|
777
|
+
return [...createRNOHPackagesAutolinking(ctx)${managedPackages}];
|
|
778
|
+
}
|
|
779
|
+
`;
|
|
780
|
+
}
|
|
781
|
+
function renderPackageProviderCpp(options) {
|
|
782
|
+
const managedInclude = options.hasManagedExpoHarmonyPackage
|
|
783
|
+
? '#include "expoHarmony/ExpoHarmonyPackage.h"\n'
|
|
784
|
+
: '';
|
|
785
|
+
const managedPackage = options.hasManagedExpoHarmonyPackage
|
|
786
|
+
? ' packages.push_back(std::make_shared<ExpoHarmonyPackage>(ctx));\n'
|
|
787
|
+
: '';
|
|
788
|
+
return `#include "RNOH/PackageProvider.h"
|
|
789
|
+
#include "RNOHPackagesFactory.h"
|
|
790
|
+
#include "generated/RNOHGeneratedPackage.h"
|
|
791
|
+
${managedInclude}
|
|
792
|
+
using namespace rnoh;
|
|
793
|
+
|
|
794
|
+
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(
|
|
795
|
+
Package::Context ctx) {
|
|
796
|
+
auto packages = createRNOHPackages(ctx);
|
|
797
|
+
packages.push_back(std::make_shared<RNOHGeneratedPackage>(ctx));
|
|
798
|
+
${managedPackage} return packages;
|
|
799
|
+
}
|
|
800
|
+
`;
|
|
801
|
+
}
|
|
802
|
+
function renderExpoHarmonyCppPackage() {
|
|
803
|
+
return `#pragma once
|
|
804
|
+
|
|
805
|
+
#include <ReactCommon/TurboModule.h>
|
|
806
|
+
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
|
|
807
|
+
#include <react/renderer/core/ConcreteComponentDescriptor.h>
|
|
808
|
+
#include "RNOH/ArkTSTurboModule.h"
|
|
809
|
+
#include "RNOH/Package.h"
|
|
810
|
+
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h"
|
|
811
|
+
|
|
812
|
+
namespace rnoh {
|
|
813
|
+
using namespace facebook;
|
|
814
|
+
|
|
815
|
+
inline constexpr char ExpoHarmonyCameraViewComponentName[] =
|
|
816
|
+
"ExpoHarmonyCameraView";
|
|
817
|
+
using ExpoHarmonyCameraViewShadowNode =
|
|
818
|
+
react::ConcreteViewShadowNode<ExpoHarmonyCameraViewComponentName>;
|
|
819
|
+
using ExpoHarmonyCameraViewComponentDescriptor =
|
|
820
|
+
react::ConcreteComponentDescriptor<ExpoHarmonyCameraViewShadowNode>;
|
|
821
|
+
|
|
822
|
+
static jsi::Value __hostFunction_ExpoHarmonyFileSystemTurboModule_getConstants(
|
|
823
|
+
jsi::Runtime& rt,
|
|
824
|
+
react::TurboModule& turboModule,
|
|
825
|
+
const jsi::Value* args,
|
|
826
|
+
size_t count) {
|
|
827
|
+
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
828
|
+
.call(rt, "getConstants", args, count);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
static jsi::Value __hostFunction_ExpoHarmonyImagePickerTurboModule_getConstants(
|
|
832
|
+
jsi::Runtime& rt,
|
|
833
|
+
react::TurboModule& turboModule,
|
|
834
|
+
const jsi::Value* args,
|
|
835
|
+
size_t count) {
|
|
836
|
+
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
837
|
+
.call(rt, "getConstants", args, count);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
static jsi::Value __hostFunction_ExpoHarmonyLocationTurboModule_getConstants(
|
|
841
|
+
jsi::Runtime& rt,
|
|
842
|
+
react::TurboModule& turboModule,
|
|
843
|
+
const jsi::Value* args,
|
|
844
|
+
size_t count) {
|
|
845
|
+
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
846
|
+
.call(rt, "getConstants", args, count);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
static jsi::Value __hostFunction_ExpoHarmonyCameraTurboModule_getConstants(
|
|
850
|
+
jsi::Runtime& rt,
|
|
851
|
+
react::TurboModule& turboModule,
|
|
852
|
+
const jsi::Value* args,
|
|
853
|
+
size_t count) {
|
|
854
|
+
return static_cast<ArkTSTurboModule&>(turboModule)
|
|
855
|
+
.call(rt, "getConstants", args, count);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
class JSI_EXPORT ExpoHarmonyFileSystemTurboModule : public ArkTSTurboModule {
|
|
859
|
+
public:
|
|
860
|
+
ExpoHarmonyFileSystemTurboModule(
|
|
861
|
+
const ArkTSTurboModule::Context ctx,
|
|
862
|
+
const std::string name)
|
|
863
|
+
: ArkTSTurboModule(ctx, name) {
|
|
864
|
+
methodMap_["getConstants"] = MethodMetadata{
|
|
865
|
+
0, __hostFunction_ExpoHarmonyFileSystemTurboModule_getConstants};
|
|
866
|
+
methodMap_["getInfo"] = MethodMetadata{2, ARK_ASYNC_METHOD_CALLER(getInfo)};
|
|
867
|
+
methodMap_["readAsString"] = MethodMetadata{
|
|
868
|
+
2, ARK_ASYNC_METHOD_CALLER(readAsString)};
|
|
869
|
+
methodMap_["writeAsString"] = MethodMetadata{
|
|
870
|
+
3, ARK_ASYNC_METHOD_CALLER(writeAsString)};
|
|
871
|
+
methodMap_["deletePath"] = MethodMetadata{
|
|
872
|
+
2, ARK_ASYNC_METHOD_CALLER(deletePath)};
|
|
873
|
+
methodMap_["makeDirectory"] = MethodMetadata{
|
|
874
|
+
2, ARK_ASYNC_METHOD_CALLER(makeDirectory)};
|
|
875
|
+
methodMap_["readDirectory"] = MethodMetadata{
|
|
876
|
+
1, ARK_ASYNC_METHOD_CALLER(readDirectory)};
|
|
877
|
+
methodMap_["copy"] = MethodMetadata{2, ARK_ASYNC_METHOD_CALLER(copy)};
|
|
878
|
+
methodMap_["move"] = MethodMetadata{2, ARK_ASYNC_METHOD_CALLER(move)};
|
|
879
|
+
methodMap_["download"] = MethodMetadata{
|
|
880
|
+
3, ARK_ASYNC_METHOD_CALLER(download)};
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
class JSI_EXPORT ExpoHarmonyImagePickerTurboModule : public ArkTSTurboModule {
|
|
885
|
+
public:
|
|
886
|
+
ExpoHarmonyImagePickerTurboModule(
|
|
887
|
+
const ArkTSTurboModule::Context ctx,
|
|
888
|
+
const std::string name)
|
|
889
|
+
: ArkTSTurboModule(ctx, name) {
|
|
890
|
+
methodMap_["getConstants"] = MethodMetadata{
|
|
891
|
+
0, __hostFunction_ExpoHarmonyImagePickerTurboModule_getConstants};
|
|
892
|
+
methodMap_["getMediaLibraryPermissionStatus"] = MethodMetadata{
|
|
893
|
+
1, ARK_ASYNC_METHOD_CALLER(getMediaLibraryPermissionStatus)};
|
|
894
|
+
methodMap_["requestMediaLibraryPermission"] = MethodMetadata{
|
|
895
|
+
1, ARK_ASYNC_METHOD_CALLER(requestMediaLibraryPermission)};
|
|
896
|
+
methodMap_["getCameraPermissionStatus"] = MethodMetadata{
|
|
897
|
+
0, ARK_ASYNC_METHOD_CALLER(getCameraPermissionStatus)};
|
|
898
|
+
methodMap_["requestCameraPermission"] = MethodMetadata{
|
|
899
|
+
0, ARK_ASYNC_METHOD_CALLER(requestCameraPermission)};
|
|
900
|
+
methodMap_["launchImageLibrary"] = MethodMetadata{
|
|
901
|
+
1, ARK_ASYNC_METHOD_CALLER(launchImageLibrary)};
|
|
902
|
+
methodMap_["launchCamera"] = MethodMetadata{
|
|
903
|
+
1, ARK_ASYNC_METHOD_CALLER(launchCamera)};
|
|
904
|
+
methodMap_["getPendingResult"] = MethodMetadata{
|
|
905
|
+
0, ARK_ASYNC_METHOD_CALLER(getPendingResult)};
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
class JSI_EXPORT ExpoHarmonyLocationTurboModule : public ArkTSTurboModule {
|
|
910
|
+
public:
|
|
911
|
+
ExpoHarmonyLocationTurboModule(
|
|
912
|
+
const ArkTSTurboModule::Context ctx,
|
|
913
|
+
const std::string name)
|
|
914
|
+
: ArkTSTurboModule(ctx, name) {
|
|
915
|
+
methodMap_["getConstants"] = MethodMetadata{
|
|
916
|
+
0, __hostFunction_ExpoHarmonyLocationTurboModule_getConstants};
|
|
917
|
+
methodMap_["getForegroundPermissionStatus"] = MethodMetadata{
|
|
918
|
+
0, ARK_ASYNC_METHOD_CALLER(getForegroundPermissionStatus)};
|
|
919
|
+
methodMap_["requestForegroundPermission"] = MethodMetadata{
|
|
920
|
+
0, ARK_ASYNC_METHOD_CALLER(requestForegroundPermission)};
|
|
921
|
+
methodMap_["getBackgroundPermissionStatus"] = MethodMetadata{
|
|
922
|
+
0, ARK_ASYNC_METHOD_CALLER(getBackgroundPermissionStatus)};
|
|
923
|
+
methodMap_["requestBackgroundPermission"] = MethodMetadata{
|
|
924
|
+
0, ARK_ASYNC_METHOD_CALLER(requestBackgroundPermission)};
|
|
925
|
+
methodMap_["hasServicesEnabled"] = MethodMetadata{
|
|
926
|
+
0, ARK_ASYNC_METHOD_CALLER(hasServicesEnabled)};
|
|
927
|
+
methodMap_["getProviderStatus"] = MethodMetadata{
|
|
928
|
+
0, ARK_ASYNC_METHOD_CALLER(getProviderStatus)};
|
|
929
|
+
methodMap_["getCurrentPosition"] = MethodMetadata{
|
|
930
|
+
1, ARK_ASYNC_METHOD_CALLER(getCurrentPosition)};
|
|
931
|
+
methodMap_["getLastKnownPosition"] = MethodMetadata{
|
|
932
|
+
1, ARK_ASYNC_METHOD_CALLER(getLastKnownPosition)};
|
|
933
|
+
methodMap_["geocode"] = MethodMetadata{1, ARK_ASYNC_METHOD_CALLER(geocode)};
|
|
934
|
+
methodMap_["reverseGeocode"] = MethodMetadata{
|
|
935
|
+
1, ARK_ASYNC_METHOD_CALLER(reverseGeocode)};
|
|
936
|
+
methodMap_["startWatchPosition"] = MethodMetadata{
|
|
937
|
+
2, ARK_ASYNC_METHOD_CALLER(startWatchPosition)};
|
|
938
|
+
methodMap_["stopWatchPosition"] = MethodMetadata{
|
|
939
|
+
1, ARK_ASYNC_METHOD_CALLER(stopWatchPosition)};
|
|
940
|
+
methodMap_["getHeading"] = MethodMetadata{
|
|
941
|
+
0, ARK_ASYNC_METHOD_CALLER(getHeading)};
|
|
942
|
+
methodMap_["startWatchHeading"] = MethodMetadata{
|
|
943
|
+
1, ARK_ASYNC_METHOD_CALLER(startWatchHeading)};
|
|
944
|
+
methodMap_["stopWatchHeading"] = MethodMetadata{
|
|
945
|
+
1, ARK_ASYNC_METHOD_CALLER(stopWatchHeading)};
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
class JSI_EXPORT ExpoHarmonyCameraTurboModule : public ArkTSTurboModule {
|
|
950
|
+
public:
|
|
951
|
+
ExpoHarmonyCameraTurboModule(
|
|
952
|
+
const ArkTSTurboModule::Context ctx,
|
|
953
|
+
const std::string name)
|
|
954
|
+
: ArkTSTurboModule(ctx, name) {
|
|
955
|
+
methodMap_["getConstants"] = MethodMetadata{
|
|
956
|
+
0, __hostFunction_ExpoHarmonyCameraTurboModule_getConstants};
|
|
957
|
+
methodMap_["getCameraPermissionStatus"] = MethodMetadata{
|
|
958
|
+
0, ARK_ASYNC_METHOD_CALLER(getCameraPermissionStatus)};
|
|
959
|
+
methodMap_["requestCameraPermission"] = MethodMetadata{
|
|
960
|
+
0, ARK_ASYNC_METHOD_CALLER(requestCameraPermission)};
|
|
961
|
+
methodMap_["getMicrophonePermissionStatus"] = MethodMetadata{
|
|
962
|
+
0, ARK_ASYNC_METHOD_CALLER(getMicrophonePermissionStatus)};
|
|
963
|
+
methodMap_["requestMicrophonePermission"] = MethodMetadata{
|
|
964
|
+
0, ARK_ASYNC_METHOD_CALLER(requestMicrophonePermission)};
|
|
965
|
+
methodMap_["createPreview"] = MethodMetadata{
|
|
966
|
+
1, ARK_ASYNC_METHOD_CALLER(createPreview)};
|
|
967
|
+
methodMap_["disposePreview"] = MethodMetadata{
|
|
968
|
+
1, ARK_ASYNC_METHOD_CALLER(disposePreview)};
|
|
969
|
+
methodMap_["pausePreview"] = MethodMetadata{
|
|
970
|
+
1, ARK_ASYNC_METHOD_CALLER(pausePreview)};
|
|
971
|
+
methodMap_["resumePreview"] = MethodMetadata{
|
|
972
|
+
1, ARK_ASYNC_METHOD_CALLER(resumePreview)};
|
|
973
|
+
methodMap_["takePicture"] = MethodMetadata{
|
|
974
|
+
1, ARK_ASYNC_METHOD_CALLER(takePicture)};
|
|
975
|
+
methodMap_["startRecording"] = MethodMetadata{
|
|
976
|
+
1, ARK_ASYNC_METHOD_CALLER(startRecording)};
|
|
977
|
+
methodMap_["stopRecording"] = MethodMetadata{
|
|
978
|
+
1, ARK_ASYNC_METHOD_CALLER(stopRecording)};
|
|
979
|
+
methodMap_["toggleRecording"] = MethodMetadata{
|
|
980
|
+
1, ARK_ASYNC_METHOD_CALLER(toggleRecording)};
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
class ExpoHarmonyTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate {
|
|
985
|
+
public:
|
|
986
|
+
SharedTurboModule createTurboModule(Context ctx, const std::string& name)
|
|
987
|
+
const override {
|
|
988
|
+
if (name == "ExpoHarmonyFileSystem") {
|
|
989
|
+
return std::make_shared<ExpoHarmonyFileSystemTurboModule>(ctx, name);
|
|
990
|
+
}
|
|
991
|
+
if (name == "ExpoHarmonyImagePicker") {
|
|
992
|
+
return std::make_shared<ExpoHarmonyImagePickerTurboModule>(ctx, name);
|
|
993
|
+
}
|
|
994
|
+
if (name == "ExpoHarmonyLocation") {
|
|
995
|
+
return std::make_shared<ExpoHarmonyLocationTurboModule>(ctx, name);
|
|
996
|
+
}
|
|
997
|
+
if (name == "ExpoHarmonyCamera") {
|
|
998
|
+
return std::make_shared<ExpoHarmonyCameraTurboModule>(ctx, name);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
return nullptr;
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
class ExpoHarmonyCameraViewJSIBinder : public ViewComponentJSIBinder {
|
|
1006
|
+
protected:
|
|
1007
|
+
facebook::jsi::Object createNativeProps(facebook::jsi::Runtime& rt) override {
|
|
1008
|
+
auto nativeProps = ViewComponentJSIBinder::createNativeProps(rt);
|
|
1009
|
+
nativeProps.setProperty(rt, "viewId", "string");
|
|
1010
|
+
nativeProps.setProperty(rt, "facing", "string");
|
|
1011
|
+
nativeProps.setProperty(rt, "mode", "string");
|
|
1012
|
+
return nativeProps;
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
class ExpoHarmonyPackage : public Package {
|
|
1017
|
+
public:
|
|
1018
|
+
explicit ExpoHarmonyPackage(Package::Context ctx) : Package(ctx) {}
|
|
1019
|
+
|
|
1020
|
+
std::unique_ptr<TurboModuleFactoryDelegate> createTurboModuleFactoryDelegate()
|
|
1021
|
+
override {
|
|
1022
|
+
return std::make_unique<ExpoHarmonyTurboModuleFactoryDelegate>();
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
std::vector<facebook::react::ComponentDescriptorProvider>
|
|
1026
|
+
createComponentDescriptorProviders() override {
|
|
1027
|
+
return {
|
|
1028
|
+
facebook::react::concreteComponentDescriptorProvider<
|
|
1029
|
+
ExpoHarmonyCameraViewComponentDescriptor>(),
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
ComponentJSIBinderByString createComponentJSIBinderByName() override {
|
|
1034
|
+
auto binder = std::make_shared<ExpoHarmonyCameraViewJSIBinder>();
|
|
1035
|
+
return {
|
|
1036
|
+
{"ExpoHarmonyCameraView", binder},
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
} // namespace rnoh
|
|
1041
|
+
`;
|
|
1042
|
+
}
|
|
714
1043
|
function renderCapabilityModuleShim(capability) {
|
|
715
1044
|
switch (capability.packageName) {
|
|
716
1045
|
case 'expo-file-system':
|
|
717
|
-
return
|
|
1046
|
+
return capability.runtimeMode !== 'shim'
|
|
1047
|
+
? renderExpoFileSystemHarmonyAdapterShim(capability)
|
|
1048
|
+
: renderExpoFileSystemPreviewShim(capability);
|
|
718
1049
|
case 'expo-image-picker':
|
|
719
|
-
return
|
|
1050
|
+
return capability.runtimeMode !== 'shim'
|
|
1051
|
+
? renderExpoImagePickerHarmonyAdapterShim(capability)
|
|
1052
|
+
: renderExpoImagePickerPreviewShim(capability);
|
|
720
1053
|
case 'expo-location':
|
|
721
|
-
return
|
|
1054
|
+
return capability.runtimeMode !== 'shim'
|
|
1055
|
+
? renderExpoLocationHarmonyAdapterShim(capability)
|
|
1056
|
+
: renderExpoLocationPreviewShim(capability);
|
|
722
1057
|
case 'expo-camera':
|
|
723
|
-
return
|
|
1058
|
+
return capability.runtimeMode !== 'shim'
|
|
1059
|
+
? renderExpoCameraHarmonyAdapterShim(capability)
|
|
1060
|
+
: renderExpoCameraPreviewShim(capability);
|
|
724
1061
|
default:
|
|
725
1062
|
return renderUnsupportedCapabilityShim(capability);
|
|
726
1063
|
}
|
|
@@ -963,199 +1300,2839 @@ module.exports = new Proxy(
|
|
|
963
1300
|
);
|
|
964
1301
|
`;
|
|
965
1302
|
}
|
|
966
|
-
function
|
|
967
|
-
return `
|
|
968
|
-
|
|
969
|
-
|
|
1303
|
+
function renderExpoHarmonyPackage() {
|
|
1304
|
+
return `import type {
|
|
1305
|
+
AnyThreadTurboModule,
|
|
1306
|
+
AnyThreadTurboModuleContext,
|
|
1307
|
+
UITurboModule,
|
|
1308
|
+
UITurboModuleContext,
|
|
1309
|
+
} from '@rnoh/react-native-openharmony';
|
|
1310
|
+
import {
|
|
1311
|
+
RNOHPackage,
|
|
1312
|
+
} from '@rnoh/react-native-openharmony';
|
|
1313
|
+
import { ExpoHarmonyFileSystemTurboModule } from './ExpoHarmonyFileSystemTurboModule';
|
|
1314
|
+
import { ExpoHarmonyImagePickerTurboModule } from './ExpoHarmonyImagePickerTurboModule';
|
|
1315
|
+
import { ExpoHarmonyLocationTurboModule } from './ExpoHarmonyLocationTurboModule';
|
|
1316
|
+
import { ExpoHarmonyCameraTurboModule } from './ExpoHarmonyCameraTurboModule';
|
|
1317
|
+
|
|
1318
|
+
export class ExpoHarmonyPackage extends RNOHPackage {
|
|
1319
|
+
override getAnyThreadTurboModuleFactoryByNameMap(): Map<
|
|
1320
|
+
string,
|
|
1321
|
+
(ctx: AnyThreadTurboModuleContext) => AnyThreadTurboModule | null
|
|
1322
|
+
> {
|
|
1323
|
+
return new Map<string, (ctx: AnyThreadTurboModuleContext) => AnyThreadTurboModule | null>()
|
|
1324
|
+
.set(
|
|
1325
|
+
ExpoHarmonyFileSystemTurboModule.NAME,
|
|
1326
|
+
(ctx) => new ExpoHarmonyFileSystemTurboModule(ctx),
|
|
1327
|
+
)
|
|
1328
|
+
.set(
|
|
1329
|
+
ExpoHarmonyLocationTurboModule.NAME,
|
|
1330
|
+
(ctx) => new ExpoHarmonyLocationTurboModule(ctx),
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
970
1333
|
|
|
971
|
-
|
|
972
|
-
|
|
1334
|
+
override getUITurboModuleFactoryByNameMap(): Map<
|
|
1335
|
+
string,
|
|
1336
|
+
(ctx: UITurboModuleContext) => UITurboModule | null
|
|
1337
|
+
> {
|
|
1338
|
+
return new Map<string, (ctx: UITurboModuleContext) => UITurboModule | null>()
|
|
1339
|
+
.set(
|
|
1340
|
+
ExpoHarmonyImagePickerTurboModule.NAME,
|
|
1341
|
+
(ctx) => new ExpoHarmonyImagePickerTurboModule(ctx),
|
|
1342
|
+
)
|
|
1343
|
+
.set(
|
|
1344
|
+
ExpoHarmonyCameraTurboModule.NAME,
|
|
1345
|
+
(ctx) => new ExpoHarmonyCameraTurboModule(ctx),
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
973
1348
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
978
|
-
);
|
|
1349
|
+
override getDebugName() {
|
|
1350
|
+
return 'expo-harmony';
|
|
1351
|
+
}
|
|
979
1352
|
}
|
|
980
|
-
|
|
981
|
-
async function unavailable(operationName) {
|
|
982
|
-
throw createPreviewError(operationName);
|
|
1353
|
+
`;
|
|
983
1354
|
}
|
|
1355
|
+
function renderExpoHarmonyFileSystemTurboModule() {
|
|
1356
|
+
return `import fs from '@ohos.file.fs';
|
|
1357
|
+
import { AnyThreadTurboModuleContext, AnyThreadTurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
984
1358
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
documentDirectory: 'file:///expo-harmony/document/',
|
|
988
|
-
bundleDirectory: 'file:///expo-harmony/bundle/',
|
|
989
|
-
EncodingType: {
|
|
990
|
-
UTF8: 'utf8',
|
|
991
|
-
Base64: 'base64',
|
|
992
|
-
},
|
|
993
|
-
FileSystemSessionType: {
|
|
994
|
-
BACKGROUND: 0,
|
|
995
|
-
FOREGROUND: 1,
|
|
996
|
-
},
|
|
997
|
-
getInfoAsync(path) {
|
|
998
|
-
return unavailable('getInfoAsync(' + String(path) + ')');
|
|
999
|
-
},
|
|
1000
|
-
readAsStringAsync(path) {
|
|
1001
|
-
return unavailable('readAsStringAsync(' + String(path) + ')');
|
|
1002
|
-
},
|
|
1003
|
-
writeAsStringAsync(path) {
|
|
1004
|
-
return unavailable('writeAsStringAsync(' + String(path) + ')');
|
|
1005
|
-
},
|
|
1006
|
-
deleteAsync(path) {
|
|
1007
|
-
return unavailable('deleteAsync(' + String(path) + ')');
|
|
1008
|
-
},
|
|
1009
|
-
makeDirectoryAsync(path) {
|
|
1010
|
-
return unavailable('makeDirectoryAsync(' + String(path) + ')');
|
|
1011
|
-
},
|
|
1012
|
-
readDirectoryAsync(path) {
|
|
1013
|
-
return unavailable('readDirectoryAsync(' + String(path) + ')');
|
|
1014
|
-
},
|
|
1015
|
-
copyAsync(options) {
|
|
1016
|
-
return unavailable('copyAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1017
|
-
},
|
|
1018
|
-
moveAsync(options) {
|
|
1019
|
-
return unavailable('moveAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1020
|
-
},
|
|
1021
|
-
downloadAsync(url) {
|
|
1022
|
-
return unavailable('downloadAsync(' + String(url) + ')');
|
|
1023
|
-
},
|
|
1359
|
+
type FileInfoOptions = {
|
|
1360
|
+
md5?: boolean;
|
|
1024
1361
|
};
|
|
1025
|
-
`;
|
|
1026
|
-
}
|
|
1027
|
-
function renderExpoImagePickerPreviewShim(capability) {
|
|
1028
|
-
return `'use strict';
|
|
1029
1362
|
|
|
1030
|
-
|
|
1363
|
+
type WriteOptions = {
|
|
1364
|
+
encoding?: string;
|
|
1365
|
+
append?: boolean;
|
|
1366
|
+
};
|
|
1031
1367
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1368
|
+
type ReadOptions = {
|
|
1369
|
+
encoding?: string;
|
|
1370
|
+
position?: number;
|
|
1371
|
+
length?: number;
|
|
1372
|
+
};
|
|
1034
1373
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1374
|
+
type MakeDirectoryOptions = {
|
|
1375
|
+
intermediates?: boolean;
|
|
1376
|
+
};
|
|
1041
1377
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
}
|
|
1378
|
+
type DeleteOptions = {
|
|
1379
|
+
idempotent?: boolean;
|
|
1380
|
+
};
|
|
1045
1381
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
AUTOMATIC: 'automatic',
|
|
1054
|
-
FULL_SCREEN: 'fullScreen',
|
|
1055
|
-
PAGE_SHEET: 'pageSheet',
|
|
1056
|
-
FORM_SHEET: 'formSheet',
|
|
1057
|
-
CURRENT_CONTEXT: 'currentContext',
|
|
1058
|
-
OVER_FULL_SCREEN: 'overFullScreen',
|
|
1059
|
-
},
|
|
1060
|
-
requestCameraPermissionsAsync() {
|
|
1061
|
-
return unavailable('requestCameraPermissionsAsync');
|
|
1062
|
-
},
|
|
1063
|
-
requestMediaLibraryPermissionsAsync() {
|
|
1064
|
-
return unavailable('requestMediaLibraryPermissionsAsync');
|
|
1065
|
-
},
|
|
1066
|
-
getCameraPermissionsAsync() {
|
|
1067
|
-
return unavailable('getCameraPermissionsAsync');
|
|
1068
|
-
},
|
|
1069
|
-
getMediaLibraryPermissionsAsync() {
|
|
1070
|
-
return unavailable('getMediaLibraryPermissionsAsync');
|
|
1071
|
-
},
|
|
1072
|
-
launchCameraAsync(options) {
|
|
1073
|
-
return unavailable('launchCameraAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1074
|
-
},
|
|
1075
|
-
launchImageLibraryAsync(options) {
|
|
1076
|
-
return unavailable('launchImageLibraryAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1077
|
-
},
|
|
1382
|
+
type FileInfoResult = {
|
|
1383
|
+
exists: boolean;
|
|
1384
|
+
path: string;
|
|
1385
|
+
isDirectory: boolean;
|
|
1386
|
+
size?: number;
|
|
1387
|
+
modificationTime?: number;
|
|
1388
|
+
md5?: string;
|
|
1078
1389
|
};
|
|
1079
|
-
`;
|
|
1080
|
-
}
|
|
1081
|
-
function renderExpoLocationPreviewShim(capability) {
|
|
1082
|
-
return `'use strict';
|
|
1083
1390
|
|
|
1084
|
-
|
|
1391
|
+
type DownloadOptions = {
|
|
1392
|
+
headers?: Record<string, string>;
|
|
1393
|
+
md5?: boolean;
|
|
1394
|
+
};
|
|
1085
1395
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
canAskAgain: true,
|
|
1092
|
-
expires: 'never',
|
|
1396
|
+
type DownloadResult = {
|
|
1397
|
+
uri: string;
|
|
1398
|
+
status: number;
|
|
1399
|
+
headers: Record<string, string>;
|
|
1400
|
+
md5?: string;
|
|
1093
1401
|
};
|
|
1094
1402
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
'ERR_EXPO_HARMONY_PREVIEW',
|
|
1098
|
-
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1403
|
+
export class ExpoHarmonyFileSystemTurboModule extends AnyThreadTurboModule {
|
|
1404
|
+
public static readonly NAME = 'ExpoHarmonyFileSystem';
|
|
1101
1405
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1406
|
+
public constructor(ctx: AnyThreadTurboModuleContext) {
|
|
1407
|
+
super(ctx);
|
|
1408
|
+
this.ensureManagedDirectoriesSync();
|
|
1409
|
+
}
|
|
1105
1410
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1411
|
+
getConstants(): {
|
|
1412
|
+
documentDirectoryPath: string;
|
|
1413
|
+
cacheDirectoryPath: string;
|
|
1414
|
+
bundleDirectoryPath: string | null;
|
|
1415
|
+
} {
|
|
1416
|
+
const abilityContext = this.ctx.uiAbilityContext as {
|
|
1417
|
+
bundleCodeDir?: string;
|
|
1418
|
+
};
|
|
1109
1419
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}
|
|
1119
|
-
PermissionStatus: {
|
|
1120
|
-
DENIED: 'denied',
|
|
1121
|
-
GRANTED: 'granted',
|
|
1122
|
-
UNDETERMINED: 'undetermined',
|
|
1123
|
-
},
|
|
1124
|
-
requestForegroundPermissionsAsync() {
|
|
1125
|
-
return unavailable('requestForegroundPermissionsAsync');
|
|
1126
|
-
},
|
|
1127
|
-
getForegroundPermissionsAsync() {
|
|
1128
|
-
return Promise.resolve(getPermissionResponse());
|
|
1129
|
-
},
|
|
1130
|
-
requestBackgroundPermissionsAsync() {
|
|
1131
|
-
return unavailable('requestBackgroundPermissionsAsync');
|
|
1132
|
-
},
|
|
1133
|
-
getBackgroundPermissionsAsync() {
|
|
1134
|
-
return Promise.resolve(getPermissionResponse());
|
|
1135
|
-
},
|
|
1136
|
-
hasServicesEnabledAsync() {
|
|
1137
|
-
return Promise.resolve(false);
|
|
1138
|
-
},
|
|
1139
|
-
getCurrentPositionAsync(options) {
|
|
1140
|
-
return unavailable('getCurrentPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1141
|
-
},
|
|
1142
|
-
getLastKnownPositionAsync(options) {
|
|
1143
|
-
return unavailable('getLastKnownPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1144
|
-
},
|
|
1145
|
-
watchPositionAsync(options) {
|
|
1146
|
-
return unavailable('watchPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1147
|
-
},
|
|
1148
|
-
};
|
|
1149
|
-
`;
|
|
1150
|
-
}
|
|
1151
|
-
function renderExpoCameraPreviewShim(capability) {
|
|
1152
|
-
return `'use strict';
|
|
1420
|
+
return {
|
|
1421
|
+
documentDirectoryPath: this.documentDirectoryPath,
|
|
1422
|
+
cacheDirectoryPath: this.cacheDirectoryPath,
|
|
1423
|
+
bundleDirectoryPath:
|
|
1424
|
+
typeof abilityContext.bundleCodeDir === 'string' && abilityContext.bundleCodeDir.length > 0
|
|
1425
|
+
? abilityContext.bundleCodeDir
|
|
1426
|
+
: null,
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1153
1429
|
|
|
1154
|
-
|
|
1155
|
-
const
|
|
1156
|
-
const
|
|
1430
|
+
async getInfo(path: string, options?: FileInfoOptions): Promise<FileInfoResult> {
|
|
1431
|
+
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1432
|
+
const stat = await this.getStatOrNull(normalizedPath);
|
|
1157
1433
|
|
|
1158
|
-
|
|
1434
|
+
if (!stat) {
|
|
1435
|
+
return {
|
|
1436
|
+
exists: false,
|
|
1437
|
+
path: normalizedPath,
|
|
1438
|
+
isDirectory: false,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return {
|
|
1443
|
+
exists: true,
|
|
1444
|
+
path: normalizedPath,
|
|
1445
|
+
isDirectory: stat.isDirectory(),
|
|
1446
|
+
size: Number(stat.size),
|
|
1447
|
+
modificationTime: Number(stat.mtime),
|
|
1448
|
+
md5:
|
|
1449
|
+
options?.md5 === true && !stat.isDirectory()
|
|
1450
|
+
? this.computePreviewDigest(await this.readFileBytes(normalizedPath))
|
|
1451
|
+
: undefined,
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
async readAsString(path: string, options?: ReadOptions): Promise<string> {
|
|
1456
|
+
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1457
|
+
const encoding = options?.encoding ?? 'utf8';
|
|
1458
|
+
const bytes = await this.readFileBytes(normalizedPath);
|
|
1459
|
+
const position = typeof options?.position === 'number' && options.position > 0
|
|
1460
|
+
? Math.floor(options.position)
|
|
1461
|
+
: 0;
|
|
1462
|
+
const length = typeof options?.length === 'number' && options.length >= 0
|
|
1463
|
+
? Math.floor(options.length)
|
|
1464
|
+
: bytes.length - position;
|
|
1465
|
+
const slicedBytes = bytes.slice(position, position + length);
|
|
1466
|
+
|
|
1467
|
+
if (encoding === 'base64') {
|
|
1468
|
+
return this.encodeBase64(slicedBytes);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
return this.decodeUtf8(slicedBytes);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
async writeAsString(path: string, contents: string, options?: WriteOptions): Promise<void> {
|
|
1475
|
+
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1476
|
+
const encoding = options?.encoding ?? 'utf8';
|
|
1477
|
+
|
|
1478
|
+
await this.ensureParentDirectory(normalizedPath);
|
|
1479
|
+
|
|
1480
|
+
const file = await fs.open(
|
|
1481
|
+
normalizedPath,
|
|
1482
|
+
fs.OpenMode.READ_WRITE |
|
|
1483
|
+
fs.OpenMode.CREATE |
|
|
1484
|
+
(options?.append === true ? fs.OpenMode.APPEND : fs.OpenMode.TRUNC),
|
|
1485
|
+
);
|
|
1486
|
+
|
|
1487
|
+
try {
|
|
1488
|
+
if (encoding === 'base64') {
|
|
1489
|
+
await fs.write(file.fd, this.decodeBase64(contents).buffer);
|
|
1490
|
+
} else {
|
|
1491
|
+
await fs.write(file.fd, contents);
|
|
1492
|
+
}
|
|
1493
|
+
} finally {
|
|
1494
|
+
await fs.close(file);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
async deletePath(path: string, options?: DeleteOptions): Promise<void> {
|
|
1499
|
+
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1500
|
+
await this.deleteInternal(normalizedPath, options?.idempotent === true);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
async makeDirectory(path: string, options?: MakeDirectoryOptions): Promise<void> {
|
|
1504
|
+
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1505
|
+
await fs.mkdir(normalizedPath, options?.intermediates === true);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
async readDirectory(path: string): Promise<string[]> {
|
|
1509
|
+
const normalizedPath = this.normalizeSandboxPath(path);
|
|
1510
|
+
const stat = await fs.stat(normalizedPath);
|
|
1511
|
+
|
|
1512
|
+
if (!stat.isDirectory()) {
|
|
1513
|
+
throw new Error('readDirectory expects a directory path.');
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
return fs.listFile(normalizedPath);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
async copy(from: string, to: string): Promise<void> {
|
|
1520
|
+
const fromPath = this.normalizeSandboxPath(from);
|
|
1521
|
+
const toPath = this.normalizeSandboxPath(to);
|
|
1522
|
+
await this.copyInternal(fromPath, toPath);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
async move(from: string, to: string): Promise<void> {
|
|
1526
|
+
const fromPath = this.normalizeSandboxPath(from);
|
|
1527
|
+
const toPath = this.normalizeSandboxPath(to);
|
|
1528
|
+
const stat = await fs.stat(fromPath);
|
|
1529
|
+
|
|
1530
|
+
await this.ensureParentDirectory(toPath);
|
|
1531
|
+
|
|
1532
|
+
if (stat.isDirectory()) {
|
|
1533
|
+
await this.copyInternal(fromPath, toPath);
|
|
1534
|
+
await this.deleteInternal(fromPath, false);
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
await fs.moveFile(fromPath, toPath);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
async download(url: string, destinationPath: string, options?: DownloadOptions): Promise<DownloadResult> {
|
|
1542
|
+
const normalizedDestinationPath = this.normalizeSandboxPath(destinationPath);
|
|
1543
|
+
await this.ensureParentDirectory(normalizedDestinationPath);
|
|
1544
|
+
|
|
1545
|
+
const response = await fetch(url, {
|
|
1546
|
+
headers: options?.headers ?? {},
|
|
1547
|
+
});
|
|
1548
|
+
const responseBuffer = new Uint8Array(await response.arrayBuffer());
|
|
1549
|
+
const file = await fs.open(
|
|
1550
|
+
normalizedDestinationPath,
|
|
1551
|
+
fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC,
|
|
1552
|
+
);
|
|
1553
|
+
|
|
1554
|
+
try {
|
|
1555
|
+
await fs.write(file.fd, responseBuffer.buffer);
|
|
1556
|
+
} finally {
|
|
1557
|
+
await fs.close(file);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
return {
|
|
1561
|
+
uri: normalizedDestinationPath,
|
|
1562
|
+
status: Number(response.status ?? 200),
|
|
1563
|
+
headers: {},
|
|
1564
|
+
md5: options?.md5 === true ? this.computePreviewDigest(responseBuffer) : undefined,
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
private get documentDirectoryPath(): string {
|
|
1569
|
+
return \`\${this.ctx.uiAbilityContext.filesDir}/expo-harmony/document\`;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
private get cacheDirectoryPath(): string {
|
|
1573
|
+
return \`\${this.ctx.uiAbilityContext.cacheDir}/expo-harmony/cache\`;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
private ensureManagedDirectoriesSync(): void {
|
|
1577
|
+
this.ensureDirectorySync(this.documentDirectoryPath);
|
|
1578
|
+
this.ensureDirectorySync(this.cacheDirectoryPath);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
private ensureDirectorySync(directoryPath: string): void {
|
|
1582
|
+
if (!fs.accessSync(directoryPath)) {
|
|
1583
|
+
fs.mkdirSync(directoryPath, true);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
private async ensureParentDirectory(targetPath: string): Promise<void> {
|
|
1588
|
+
const parentPath = this.getParentPath(targetPath);
|
|
1589
|
+
|
|
1590
|
+
if (!parentPath) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
const parentStat = await this.getStatOrNull(parentPath);
|
|
1595
|
+
|
|
1596
|
+
if (parentStat) {
|
|
1597
|
+
if (!parentStat.isDirectory()) {
|
|
1598
|
+
throw new Error(\`Expected parent path to be a directory: \${parentPath}\`);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
await fs.mkdir(parentPath, true);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
private getParentPath(targetPath: string): string | null {
|
|
1608
|
+
let normalizedPath = targetPath;
|
|
1609
|
+
|
|
1610
|
+
while (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
|
|
1611
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
1612
|
+
}
|
|
1613
|
+
const slashIndex = normalizedPath.lastIndexOf('/');
|
|
1614
|
+
|
|
1615
|
+
if (slashIndex <= 0) {
|
|
1616
|
+
return null;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
return normalizedPath.slice(0, slashIndex);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
private async deleteInternal(targetPath: string, idempotent: boolean): Promise<void> {
|
|
1623
|
+
const stat = await this.getStatOrNull(targetPath);
|
|
1624
|
+
|
|
1625
|
+
if (!stat) {
|
|
1626
|
+
if (idempotent) {
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
throw new Error(\`No file or directory exists at \${targetPath}.\`);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
if (stat.isDirectory()) {
|
|
1634
|
+
const entries = await fs.listFile(targetPath);
|
|
1635
|
+
|
|
1636
|
+
for (const entryName of entries) {
|
|
1637
|
+
await this.deleteInternal(\`\${targetPath}/\${entryName}\`, idempotent);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
await fs.rmdir(targetPath);
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
await fs.unlink(targetPath);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
private async copyInternal(fromPath: string, toPath: string): Promise<void> {
|
|
1648
|
+
const stat = await fs.stat(fromPath);
|
|
1649
|
+
|
|
1650
|
+
await this.ensureParentDirectory(toPath);
|
|
1651
|
+
|
|
1652
|
+
if (stat.isDirectory()) {
|
|
1653
|
+
await fs.mkdir(toPath, true);
|
|
1654
|
+
const entries = await fs.listFile(fromPath);
|
|
1655
|
+
|
|
1656
|
+
for (const entryName of entries) {
|
|
1657
|
+
await this.copyInternal(\`\${fromPath}/\${entryName}\`, \`\${toPath}/\${entryName}\`);
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
await fs.copyFile(fromPath, toPath);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
private async getStatOrNull(targetPath: string): Promise<fs.Stat | null> {
|
|
1667
|
+
try {
|
|
1668
|
+
return await fs.stat(targetPath);
|
|
1669
|
+
} catch (error) {
|
|
1670
|
+
if (this.isNoSuchFileError(error)) {
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
throw error;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
private isNoSuchFileError(error: unknown): boolean {
|
|
1679
|
+
return (
|
|
1680
|
+
typeof error === 'object' &&
|
|
1681
|
+
error !== null &&
|
|
1682
|
+
'code' in error &&
|
|
1683
|
+
Number((error as { code?: number }).code) === 13900002
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
private async readFileBytes(targetPath: string): Promise<Uint8Array> {
|
|
1688
|
+
const stat = await fs.stat(targetPath);
|
|
1689
|
+
const file = await fs.open(targetPath, fs.OpenMode.READ_ONLY);
|
|
1690
|
+
const buffer = new ArrayBuffer(Number(stat.size ?? 0));
|
|
1691
|
+
|
|
1692
|
+
try {
|
|
1693
|
+
await fs.read(file.fd, buffer);
|
|
1694
|
+
return new Uint8Array(buffer);
|
|
1695
|
+
} finally {
|
|
1696
|
+
await fs.close(file);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
private decodeUtf8(bytes: Uint8Array): string {
|
|
1701
|
+
return new TextDecoder().decode(bytes);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
private encodeBase64(bytes: Uint8Array): string {
|
|
1705
|
+
let binary = '';
|
|
1706
|
+
|
|
1707
|
+
for (const byte of bytes) {
|
|
1708
|
+
binary += String.fromCharCode(byte);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
return btoa(binary);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
private decodeBase64(contents: string): Uint8Array {
|
|
1715
|
+
const binary = atob(contents);
|
|
1716
|
+
const bytes = new Uint8Array(binary.length);
|
|
1717
|
+
|
|
1718
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
1719
|
+
bytes[index] = binary.charCodeAt(index);
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
return bytes;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
private computePreviewDigest(bytes: Uint8Array): string {
|
|
1726
|
+
let hash = 2166136261;
|
|
1727
|
+
|
|
1728
|
+
for (const byte of bytes) {
|
|
1729
|
+
hash ^= byte;
|
|
1730
|
+
hash = Math.imul(hash, 16777619);
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
return String(hash >>> 0).padStart(32, '0').slice(-32);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
private normalizeSandboxPath(inputPath: string): string {
|
|
1737
|
+
if (typeof inputPath !== 'string' || inputPath.length === 0) {
|
|
1738
|
+
throw new Error('ExpoHarmonyFileSystem expected a non-empty sandbox path.');
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
if (!inputPath.startsWith('/')) {
|
|
1742
|
+
throw new Error('ExpoHarmonyFileSystem accepts only absolute sandbox paths.');
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
if (inputPath.includes('/../') || inputPath.endsWith('/..') || inputPath.includes('/./')) {
|
|
1746
|
+
throw new Error('ExpoHarmonyFileSystem does not accept relative path segments.');
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const allowedRoots = [
|
|
1750
|
+
this.ctx.uiAbilityContext.filesDir,
|
|
1751
|
+
this.ctx.uiAbilityContext.cacheDir,
|
|
1752
|
+
].filter((value): value is string => typeof value === 'string' && value.length > 0);
|
|
1753
|
+
|
|
1754
|
+
const isAllowed = allowedRoots.some(
|
|
1755
|
+
(rootPath) => inputPath === rootPath || inputPath.startsWith(\`\${rootPath}/\`),
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
if (!isAllowed) {
|
|
1759
|
+
throw new Error('ExpoHarmonyFileSystem accepts only app sandbox paths.');
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
return inputPath;
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
`;
|
|
1766
|
+
}
|
|
1767
|
+
function renderExpoHarmonyImagePickerTurboModule() {
|
|
1768
|
+
return `import type { Permissions } from '@ohos.abilityAccessCtrl';
|
|
1769
|
+
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
|
|
1770
|
+
import photoAccessHelper from '@ohos.file.photoAccessHelper';
|
|
1771
|
+
import picker from '@ohos.file.picker';
|
|
1772
|
+
import image from '@ohos.multimedia.image';
|
|
1773
|
+
import fs from '@ohos.file.fs';
|
|
1774
|
+
import { UITurboModuleContext, UITurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
1775
|
+
|
|
1776
|
+
type PermissionResponse = {
|
|
1777
|
+
status: 'granted' | 'denied' | 'undetermined';
|
|
1778
|
+
granted: boolean;
|
|
1779
|
+
canAskAgain: boolean;
|
|
1780
|
+
expires: 'never';
|
|
1781
|
+
accessPrivileges?: 'all' | 'limited' | 'none';
|
|
1782
|
+
};
|
|
1783
|
+
|
|
1784
|
+
type LaunchImageLibraryOptions = {
|
|
1785
|
+
mediaTypes?: string | string[];
|
|
1786
|
+
allowsMultipleSelection?: boolean;
|
|
1787
|
+
selectionLimit?: number;
|
|
1788
|
+
};
|
|
1789
|
+
|
|
1790
|
+
type LaunchCameraOptions = {
|
|
1791
|
+
mediaTypes?: string | string[];
|
|
1792
|
+
cameraType?: string;
|
|
1793
|
+
};
|
|
1794
|
+
|
|
1795
|
+
type ImagePickerAsset = {
|
|
1796
|
+
uri: string;
|
|
1797
|
+
assetId: string | null;
|
|
1798
|
+
width: number;
|
|
1799
|
+
height: number;
|
|
1800
|
+
type: 'image' | 'video' | null;
|
|
1801
|
+
fileName: string | null;
|
|
1802
|
+
fileSize: number | null;
|
|
1803
|
+
mimeType: string | null;
|
|
1804
|
+
duration: number | null;
|
|
1805
|
+
exif: null;
|
|
1806
|
+
base64: null;
|
|
1807
|
+
};
|
|
1808
|
+
|
|
1809
|
+
type ImagePickerResult = {
|
|
1810
|
+
canceled: boolean;
|
|
1811
|
+
assets: ImagePickerAsset[] | null;
|
|
1812
|
+
};
|
|
1813
|
+
|
|
1814
|
+
export class ExpoHarmonyImagePickerTurboModule extends UITurboModule {
|
|
1815
|
+
public static readonly NAME = 'ExpoHarmonyImagePicker';
|
|
1816
|
+
|
|
1817
|
+
private readonly atManager = abilityAccessCtrl.createAtManager();
|
|
1818
|
+
private pendingResult: ImagePickerResult | null = null;
|
|
1819
|
+
|
|
1820
|
+
getConstants(): Record<string, never> {
|
|
1821
|
+
return {};
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
async getMediaLibraryPermissionStatus(_writeOnly?: boolean): Promise<PermissionResponse> {
|
|
1825
|
+
return this.getPermissionResponse('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
async requestMediaLibraryPermission(_writeOnly?: boolean): Promise<PermissionResponse> {
|
|
1829
|
+
return this.requestPermissionResponse('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
async getCameraPermissionStatus(): Promise<PermissionResponse> {
|
|
1833
|
+
return this.getPermissionResponse('ohos.permission.CAMERA', false);
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
async requestCameraPermission(): Promise<PermissionResponse> {
|
|
1837
|
+
return this.requestPermissionResponse('ohos.permission.CAMERA', false);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
async launchImageLibrary(options?: LaunchImageLibraryOptions): Promise<ImagePickerResult> {
|
|
1841
|
+
await this.ensurePermissionGranted('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1842
|
+
|
|
1843
|
+
const photoPicker = new photoAccessHelper.PhotoViewPicker();
|
|
1844
|
+
const selection = await photoPicker.select(this.createPhotoSelectOptions(options));
|
|
1845
|
+
let selectedUris = this.normalizeSelectedUris(selection?.photoUris);
|
|
1846
|
+
|
|
1847
|
+
if (selectedUris.length === 0) {
|
|
1848
|
+
selectedUris = await this.launchLegacyPhotoPicker(options);
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
if (selectedUris.length === 0) {
|
|
1852
|
+
return this.createCanceledResult();
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
const authorizedUris = await this.requestAuthorizedUris(selectedUris);
|
|
1856
|
+
const assets = await Promise.all(
|
|
1857
|
+
authorizedUris.map((uri, index) =>
|
|
1858
|
+
this.createImagePickerAsset(uri, selectedUris[index] ?? uri, this.inferAssetTypeFromMediaTypes(options?.mediaTypes)),
|
|
1859
|
+
),
|
|
1860
|
+
);
|
|
1861
|
+
|
|
1862
|
+
const result = {
|
|
1863
|
+
canceled: false,
|
|
1864
|
+
assets,
|
|
1865
|
+
};
|
|
1866
|
+
this.pendingResult = result;
|
|
1867
|
+
return result;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
async launchCamera(options?: LaunchCameraOptions): Promise<ImagePickerResult> {
|
|
1871
|
+
const requestedAssetType = this.inferAssetTypeFromMediaTypes(options?.mediaTypes);
|
|
1872
|
+
|
|
1873
|
+
await this.ensurePermissionGranted('ohos.permission.CAMERA', false);
|
|
1874
|
+
await this.ensurePermissionGranted('ohos.permission.READ_IMAGEVIDEO', true);
|
|
1875
|
+
if (requestedAssetType === 'video') {
|
|
1876
|
+
await this.ensurePermissionGranted('ohos.permission.MICROPHONE', false);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
const cameraEntryPicker = new photoAccessHelper.PhotoViewPicker();
|
|
1880
|
+
const selection = await cameraEntryPicker.select(
|
|
1881
|
+
this.createPhotoSelectOptions(
|
|
1882
|
+
{
|
|
1883
|
+
mediaTypes: requestedAssetType === 'video' ? 'videos' : 'images',
|
|
1884
|
+
allowsMultipleSelection: false,
|
|
1885
|
+
},
|
|
1886
|
+
true,
|
|
1887
|
+
),
|
|
1888
|
+
);
|
|
1889
|
+
const selectedUris = this.normalizeSelectedUris(selection?.photoUris);
|
|
1890
|
+
|
|
1891
|
+
if (selectedUris.length === 0) {
|
|
1892
|
+
return this.createCanceledResult();
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
const authorizedUris = await this.requestAuthorizedUris(selectedUris);
|
|
1896
|
+
const assetUri = authorizedUris[0] ?? selectedUris[0];
|
|
1897
|
+
|
|
1898
|
+
const result = {
|
|
1899
|
+
canceled: false,
|
|
1900
|
+
assets: [await this.createImagePickerAsset(assetUri, selectedUris[0], requestedAssetType ?? 'image')],
|
|
1901
|
+
};
|
|
1902
|
+
this.pendingResult = result;
|
|
1903
|
+
return result;
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
async getPendingResult(): Promise<ImagePickerResult | null> {
|
|
1907
|
+
const result = this.pendingResult;
|
|
1908
|
+
this.pendingResult = null;
|
|
1909
|
+
return result;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
private async ensurePermissionGranted(
|
|
1913
|
+
permissionName: Permissions,
|
|
1914
|
+
isMediaLibraryPermission: boolean,
|
|
1915
|
+
): Promise<void> {
|
|
1916
|
+
const permissionResponse = await this.getPermissionResponse(permissionName, isMediaLibraryPermission);
|
|
1917
|
+
|
|
1918
|
+
if (permissionResponse.granted) {
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const requestedResponse = await this.requestPermissionResponse(permissionName, isMediaLibraryPermission);
|
|
1923
|
+
|
|
1924
|
+
if (!requestedResponse.granted) {
|
|
1925
|
+
throw new Error(\`Permission denied for \${permissionName}.\`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
private async getPermissionResponse(
|
|
1930
|
+
permissionName: Permissions,
|
|
1931
|
+
isMediaLibraryPermission: boolean,
|
|
1932
|
+
): Promise<PermissionResponse> {
|
|
1933
|
+
return this.permissionResponseFromStatus(
|
|
1934
|
+
this.resolvePermissionStatus(permissionName),
|
|
1935
|
+
isMediaLibraryPermission,
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
private async requestPermissionResponse(
|
|
1940
|
+
permissionName: Permissions,
|
|
1941
|
+
isMediaLibraryPermission: boolean,
|
|
1942
|
+
): Promise<PermissionResponse> {
|
|
1943
|
+
const requestResult = await this.atManager.requestPermissionsFromUser(
|
|
1944
|
+
this.ctx.uiAbilityContext,
|
|
1945
|
+
[permissionName],
|
|
1946
|
+
);
|
|
1947
|
+
const authResult = Array.isArray(requestResult.authResults)
|
|
1948
|
+
? Number(requestResult.authResults[0] ?? abilityAccessCtrl.GrantStatus.PERMISSION_DENIED)
|
|
1949
|
+
: abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;
|
|
1950
|
+
|
|
1951
|
+
if (authResult === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
|
|
1952
|
+
return this.permissionResponseFromStatus(
|
|
1953
|
+
abilityAccessCtrl.PermissionStatus.GRANTED,
|
|
1954
|
+
isMediaLibraryPermission,
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
return this.permissionResponseFromStatus(
|
|
1959
|
+
abilityAccessCtrl.PermissionStatus.DENIED,
|
|
1960
|
+
isMediaLibraryPermission,
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
private resolvePermissionStatus(permissionName: Permissions): abilityAccessCtrl.PermissionStatus {
|
|
1965
|
+
const atManagerWithSelfStatus = this.atManager as abilityAccessCtrl.AtManager & {
|
|
1966
|
+
getSelfPermissionStatus?: (permission: Permissions) => abilityAccessCtrl.PermissionStatus;
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
if (typeof atManagerWithSelfStatus.getSelfPermissionStatus === 'function') {
|
|
1970
|
+
return atManagerWithSelfStatus.getSelfPermissionStatus(permissionName);
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
const accessTokenId = this.ctx.uiAbilityContext.abilityInfo.applicationInfo.accessTokenId;
|
|
1974
|
+
const grantStatus = this.atManager.checkAccessTokenSync(accessTokenId, permissionName);
|
|
1975
|
+
|
|
1976
|
+
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
|
|
1977
|
+
? abilityAccessCtrl.PermissionStatus.GRANTED
|
|
1978
|
+
: abilityAccessCtrl.PermissionStatus.NOT_DETERMINED;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
private permissionResponseFromStatus(
|
|
1982
|
+
permissionStatus: abilityAccessCtrl.PermissionStatus,
|
|
1983
|
+
isMediaLibraryPermission: boolean,
|
|
1984
|
+
): PermissionResponse {
|
|
1985
|
+
const granted = permissionStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
1986
|
+
const denied =
|
|
1987
|
+
permissionStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
1988
|
+
permissionStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
1989
|
+
permissionStatus === abilityAccessCtrl.PermissionStatus.INVALID;
|
|
1990
|
+
|
|
1991
|
+
return {
|
|
1992
|
+
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
1993
|
+
granted,
|
|
1994
|
+
canAskAgain: !denied,
|
|
1995
|
+
expires: 'never',
|
|
1996
|
+
...(isMediaLibraryPermission
|
|
1997
|
+
? {
|
|
1998
|
+
accessPrivileges: granted ? 'all' : 'none',
|
|
1999
|
+
}
|
|
2000
|
+
: {}),
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
private createPhotoSelectOptions(
|
|
2005
|
+
options?: LaunchImageLibraryOptions,
|
|
2006
|
+
isPhotoTakingSupported: boolean = false,
|
|
2007
|
+
): photoAccessHelper.PhotoSelectOptions {
|
|
2008
|
+
const selectOptions = new photoAccessHelper.PhotoSelectOptions();
|
|
2009
|
+
selectOptions.MIMEType = this.resolvePhotoViewMimeType(options?.mediaTypes);
|
|
2010
|
+
selectOptions.maxSelectNumber =
|
|
2011
|
+
options?.allowsMultipleSelection === true
|
|
2012
|
+
? this.resolveSelectionLimit(options?.selectionLimit)
|
|
2013
|
+
: 1;
|
|
2014
|
+
selectOptions.isSearchSupported = true;
|
|
2015
|
+
selectOptions.isPhotoTakingSupported = isPhotoTakingSupported;
|
|
2016
|
+
return selectOptions;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
private async launchLegacyPhotoPicker(options?: LaunchImageLibraryOptions): Promise<string[]> {
|
|
2020
|
+
const legacyPicker = new picker.PhotoViewPicker(this.ctx.uiAbilityContext);
|
|
2021
|
+
const legacyOptions = new picker.PhotoSelectOptions();
|
|
2022
|
+
legacyOptions.MIMEType = this.resolveLegacyPhotoViewMimeType(options?.mediaTypes);
|
|
2023
|
+
legacyOptions.maxSelectNumber =
|
|
2024
|
+
options?.allowsMultipleSelection === true
|
|
2025
|
+
? this.resolveSelectionLimit(options?.selectionLimit)
|
|
2026
|
+
: 1;
|
|
2027
|
+
|
|
2028
|
+
const selection = await legacyPicker.select(legacyOptions);
|
|
2029
|
+
return this.normalizeSelectedUris(selection?.photoUris);
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
private resolveSelectionLimit(selectionLimit?: number): number {
|
|
2033
|
+
if (typeof selectionLimit === 'number' && Number.isFinite(selectionLimit) && selectionLimit > 0) {
|
|
2034
|
+
return Math.floor(selectionLimit);
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
return 20;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
private resolvePhotoViewMimeType(
|
|
2041
|
+
rawMediaTypes?: string | string[],
|
|
2042
|
+
): photoAccessHelper.PhotoViewMIMETypes {
|
|
2043
|
+
const normalized = this.normalizeMediaTypes(rawMediaTypes);
|
|
2044
|
+
|
|
2045
|
+
if (normalized.includes('video') && !normalized.includes('image')) {
|
|
2046
|
+
return photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
if (normalized.includes('video') && normalized.includes('image')) {
|
|
2050
|
+
return photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
return photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
private resolveLegacyPhotoViewMimeType(
|
|
2057
|
+
rawMediaTypes?: string | string[],
|
|
2058
|
+
): picker.PhotoViewMIMETypes {
|
|
2059
|
+
const normalized = this.normalizeMediaTypes(rawMediaTypes);
|
|
2060
|
+
|
|
2061
|
+
if (normalized.includes('video') && !normalized.includes('image')) {
|
|
2062
|
+
return picker.PhotoViewMIMETypes.VIDEO_TYPE;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
if (normalized.includes('video') && normalized.includes('image')) {
|
|
2066
|
+
return picker.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
return picker.PhotoViewMIMETypes.IMAGE_TYPE;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
private inferAssetTypeFromMediaTypes(
|
|
2073
|
+
rawMediaTypes?: string | string[],
|
|
2074
|
+
): 'image' | 'video' | null {
|
|
2075
|
+
const normalized = this.normalizeMediaTypes(rawMediaTypes);
|
|
2076
|
+
|
|
2077
|
+
if (normalized.includes('video') && !normalized.includes('image')) {
|
|
2078
|
+
return 'video';
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
if (normalized.includes('image')) {
|
|
2082
|
+
return 'image';
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
return null;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
private normalizeMediaTypes(rawMediaTypes?: string | string[]): string[] {
|
|
2089
|
+
if (Array.isArray(rawMediaTypes)) {
|
|
2090
|
+
return Array.from(
|
|
2091
|
+
new Set(
|
|
2092
|
+
rawMediaTypes
|
|
2093
|
+
.map((value) => this.normalizeMediaTypeValue(value))
|
|
2094
|
+
.filter((value): value is string => value !== null),
|
|
2095
|
+
),
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
const singleValue = this.normalizeMediaTypeValue(rawMediaTypes);
|
|
2100
|
+
return singleValue ? [singleValue] : ['image'];
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
private normalizeMediaTypeValue(rawValue?: string): string | null {
|
|
2104
|
+
if (typeof rawValue !== 'string' || rawValue.length === 0) {
|
|
2105
|
+
return 'image';
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
switch (rawValue) {
|
|
2109
|
+
case 'All':
|
|
2110
|
+
case 'all':
|
|
2111
|
+
case 'images':
|
|
2112
|
+
case 'image':
|
|
2113
|
+
case 'livePhotos':
|
|
2114
|
+
return 'image';
|
|
2115
|
+
case 'Videos':
|
|
2116
|
+
case 'videos':
|
|
2117
|
+
case 'video':
|
|
2118
|
+
return 'video';
|
|
2119
|
+
default:
|
|
2120
|
+
return rawValue.includes('video') ? 'video' : rawValue.includes('image') ? 'image' : null;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
private normalizeSelectedUris(photoUris: Array<string> | undefined | null): string[] {
|
|
2125
|
+
if (!Array.isArray(photoUris)) {
|
|
2126
|
+
return [];
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
return photoUris.filter(
|
|
2130
|
+
(value): value is string => typeof value === 'string' && value.length > 0,
|
|
2131
|
+
);
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
private async requestAuthorizedUris(photoUris: string[]): Promise<string[]> {
|
|
2135
|
+
const helper = photoAccessHelper.getPhotoAccessHelper(this.ctx.uiAbilityContext);
|
|
2136
|
+
|
|
2137
|
+
try {
|
|
2138
|
+
const authorizedUris = await helper.requestPhotoUrisReadPermission(photoUris);
|
|
2139
|
+
const normalizedAuthorizedUris = this.normalizeSelectedUris(authorizedUris);
|
|
2140
|
+
return normalizedAuthorizedUris.length > 0 ? normalizedAuthorizedUris : photoUris;
|
|
2141
|
+
} catch (_error) {
|
|
2142
|
+
return photoUris;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
private async createImagePickerAsset(
|
|
2147
|
+
assetUri: string,
|
|
2148
|
+
originalUri: string,
|
|
2149
|
+
fallbackType: 'image' | 'video' | null,
|
|
2150
|
+
): Promise<ImagePickerAsset> {
|
|
2151
|
+
const inferredType = this.inferAssetTypeFromUri(assetUri, fallbackType);
|
|
2152
|
+
const imageSize =
|
|
2153
|
+
inferredType === 'image' ? await this.getImageSize(assetUri) : { width: 0, height: 0 };
|
|
2154
|
+
const fileSize = await this.getFileSize(assetUri);
|
|
2155
|
+
const fileName = this.extractFileName(assetUri) ?? this.extractFileName(originalUri);
|
|
2156
|
+
|
|
2157
|
+
return {
|
|
2158
|
+
uri: assetUri,
|
|
2159
|
+
assetId: originalUri,
|
|
2160
|
+
width: imageSize.width,
|
|
2161
|
+
height: imageSize.height,
|
|
2162
|
+
type: inferredType,
|
|
2163
|
+
fileName,
|
|
2164
|
+
fileSize,
|
|
2165
|
+
mimeType: this.inferMimeType(assetUri, inferredType),
|
|
2166
|
+
duration: inferredType === 'video' ? await this.getVideoDuration(assetUri) : null,
|
|
2167
|
+
exif: null,
|
|
2168
|
+
base64: null,
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
private inferAssetTypeFromUri(
|
|
2173
|
+
assetUri: string,
|
|
2174
|
+
fallbackType: 'image' | 'video' | null,
|
|
2175
|
+
): 'image' | 'video' | null {
|
|
2176
|
+
const normalizedUri = assetUri.toLowerCase();
|
|
2177
|
+
|
|
2178
|
+
if (normalizedUri.match(/\\.(mp4|m4v|mov|3gp|webm)(\\?|#|$)/)) {
|
|
2179
|
+
return 'video';
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
if (normalizedUri.match(/\\.(png|jpe?g|gif|bmp|webp|heic|heif)(\\?|#|$)/)) {
|
|
2183
|
+
return 'image';
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
return fallbackType;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
private inferMimeType(
|
|
2190
|
+
assetUri: string,
|
|
2191
|
+
assetType: 'image' | 'video' | null,
|
|
2192
|
+
): string | null {
|
|
2193
|
+
const normalizedUri = assetUri.toLowerCase();
|
|
2194
|
+
|
|
2195
|
+
if (normalizedUri.endsWith('.png')) {
|
|
2196
|
+
return 'image/png';
|
|
2197
|
+
}
|
|
2198
|
+
if (normalizedUri.endsWith('.gif')) {
|
|
2199
|
+
return 'image/gif';
|
|
2200
|
+
}
|
|
2201
|
+
if (normalizedUri.endsWith('.webp')) {
|
|
2202
|
+
return 'image/webp';
|
|
2203
|
+
}
|
|
2204
|
+
if (normalizedUri.endsWith('.bmp')) {
|
|
2205
|
+
return 'image/bmp';
|
|
2206
|
+
}
|
|
2207
|
+
if (normalizedUri.endsWith('.heic')) {
|
|
2208
|
+
return 'image/heic';
|
|
2209
|
+
}
|
|
2210
|
+
if (normalizedUri.endsWith('.heif')) {
|
|
2211
|
+
return 'image/heif';
|
|
2212
|
+
}
|
|
2213
|
+
if (normalizedUri.match(/\\.jpe?g(\\?|#|$)/)) {
|
|
2214
|
+
return 'image/jpeg';
|
|
2215
|
+
}
|
|
2216
|
+
if (normalizedUri.match(/\\.(mp4|m4v)(\\?|#|$)/)) {
|
|
2217
|
+
return 'video/mp4';
|
|
2218
|
+
}
|
|
2219
|
+
if (normalizedUri.match(/\\.mov(\\?|#|$)/)) {
|
|
2220
|
+
return 'video/quicktime';
|
|
2221
|
+
}
|
|
2222
|
+
if (normalizedUri.match(/\\.webm(\\?|#|$)/)) {
|
|
2223
|
+
return 'video/webm';
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
return assetType === 'video' ? 'video/*' : assetType === 'image' ? 'image/*' : null;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
private extractFileName(assetUri: string): string | null {
|
|
2230
|
+
if (typeof assetUri !== 'string' || assetUri.length === 0) {
|
|
2231
|
+
return null;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
const sanitizedUri = assetUri.split('?')[0]?.split('#')[0] ?? assetUri;
|
|
2235
|
+
const lastSlashIndex = sanitizedUri.lastIndexOf('/');
|
|
2236
|
+
const rawFileName =
|
|
2237
|
+
lastSlashIndex >= 0 ? sanitizedUri.slice(lastSlashIndex + 1) : sanitizedUri;
|
|
2238
|
+
|
|
2239
|
+
if (rawFileName.length === 0) {
|
|
2240
|
+
return null;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
try {
|
|
2244
|
+
return decodeURIComponent(rawFileName);
|
|
2245
|
+
} catch (_error) {
|
|
2246
|
+
return rawFileName;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
private async getImageSize(assetUri: string): Promise<{ width: number; height: number }> {
|
|
2251
|
+
let imageSource: image.ImageSource | null = null;
|
|
2252
|
+
|
|
2253
|
+
try {
|
|
2254
|
+
imageSource = image.createImageSource(assetUri);
|
|
2255
|
+
const imageInfo = await imageSource.getImageInfo();
|
|
2256
|
+
return {
|
|
2257
|
+
width: Number(imageInfo.size?.width ?? 0),
|
|
2258
|
+
height: Number(imageInfo.size?.height ?? 0),
|
|
2259
|
+
};
|
|
2260
|
+
} catch (_error) {
|
|
2261
|
+
return {
|
|
2262
|
+
width: 0,
|
|
2263
|
+
height: 0,
|
|
2264
|
+
};
|
|
2265
|
+
} finally {
|
|
2266
|
+
if (imageSource) {
|
|
2267
|
+
try {
|
|
2268
|
+
await imageSource.release();
|
|
2269
|
+
} catch (_error) {
|
|
2270
|
+
// Ignore cleanup errors from ImageSource release.
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
private async getFileSize(assetUri: string): Promise<number | null> {
|
|
2277
|
+
const fsTarget = this.resolveFsTarget(assetUri);
|
|
2278
|
+
|
|
2279
|
+
if (!fsTarget) {
|
|
2280
|
+
return null;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
try {
|
|
2284
|
+
const stat = await fs.stat(fsTarget);
|
|
2285
|
+
return Number(stat.size ?? 0);
|
|
2286
|
+
} catch (_error) {
|
|
2287
|
+
return null;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
private async getVideoDuration(_assetUri: string): Promise<number | null> {
|
|
2292
|
+
return 0;
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2295
|
+
private resolveFsTarget(assetUri: string): string | null {
|
|
2296
|
+
if (assetUri.startsWith('file://')) {
|
|
2297
|
+
return assetUri.slice('file://'.length);
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
return assetUri.startsWith('/') ? assetUri : null;
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
private createCanceledResult(): ImagePickerResult {
|
|
2304
|
+
return {
|
|
2305
|
+
canceled: true,
|
|
2306
|
+
assets: null,
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
`;
|
|
2311
|
+
}
|
|
2312
|
+
function renderExpoHarmonyLocationTurboModule() {
|
|
2313
|
+
return `import type { Permissions } from '@ohos.abilityAccessCtrl';
|
|
2314
|
+
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
|
|
2315
|
+
import geoLocationManager from '@ohos.geoLocationManager';
|
|
2316
|
+
import { sensor } from '@kit.SensorServiceKit';
|
|
2317
|
+
import { AnyThreadTurboModuleContext, AnyThreadTurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
2318
|
+
|
|
2319
|
+
type PermissionResponse = {
|
|
2320
|
+
status: 'granted' | 'denied' | 'undetermined';
|
|
2321
|
+
granted: boolean;
|
|
2322
|
+
canAskAgain: boolean;
|
|
2323
|
+
expires: 'never';
|
|
2324
|
+
android: {
|
|
2325
|
+
accuracy: 'fine' | 'coarse' | 'none';
|
|
2326
|
+
};
|
|
2327
|
+
};
|
|
2328
|
+
|
|
2329
|
+
type ExpoLocationObject = {
|
|
2330
|
+
coords: {
|
|
2331
|
+
latitude: number;
|
|
2332
|
+
longitude: number;
|
|
2333
|
+
altitude: number | null;
|
|
2334
|
+
accuracy: number | null;
|
|
2335
|
+
altitudeAccuracy: number | null;
|
|
2336
|
+
heading: number | null;
|
|
2337
|
+
speed: number | null;
|
|
2338
|
+
};
|
|
2339
|
+
timestamp: number;
|
|
2340
|
+
mocked: false;
|
|
2341
|
+
};
|
|
2342
|
+
|
|
2343
|
+
type ProviderStatus = {
|
|
2344
|
+
locationServicesEnabled: boolean;
|
|
2345
|
+
backgroundModeEnabled: boolean;
|
|
2346
|
+
gpsAvailable: boolean;
|
|
2347
|
+
networkAvailable: boolean;
|
|
2348
|
+
passiveAvailable: boolean;
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
type ReverseGeocodeResult = {
|
|
2352
|
+
city: string | null;
|
|
2353
|
+
district: string | null;
|
|
2354
|
+
streetNumber: string | null;
|
|
2355
|
+
street: string | null;
|
|
2356
|
+
region: string | null;
|
|
2357
|
+
subregion: string | null;
|
|
2358
|
+
country: string | null;
|
|
2359
|
+
postalCode: string | null;
|
|
2360
|
+
name: string | null;
|
|
2361
|
+
isoCountryCode: string | null;
|
|
2362
|
+
timezone: null;
|
|
2363
|
+
formattedAddress: string | null;
|
|
2364
|
+
};
|
|
2365
|
+
|
|
2366
|
+
type GeocodeResult = {
|
|
2367
|
+
latitude: number;
|
|
2368
|
+
longitude: number;
|
|
2369
|
+
altitude?: number;
|
|
2370
|
+
accuracy?: number;
|
|
2371
|
+
};
|
|
2372
|
+
|
|
2373
|
+
type HeadingObject = {
|
|
2374
|
+
magHeading: number;
|
|
2375
|
+
trueHeading: number | null;
|
|
2376
|
+
accuracy: number;
|
|
2377
|
+
};
|
|
2378
|
+
|
|
2379
|
+
export class ExpoHarmonyLocationTurboModule extends AnyThreadTurboModule {
|
|
2380
|
+
public static readonly NAME = 'ExpoHarmonyLocation';
|
|
2381
|
+
|
|
2382
|
+
private readonly atManager = abilityAccessCtrl.createAtManager();
|
|
2383
|
+
private nextWatchId = 1;
|
|
2384
|
+
|
|
2385
|
+
getConstants(): Record<string, never> {
|
|
2386
|
+
return {};
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
async getForegroundPermissionStatus(): Promise<PermissionResponse> {
|
|
2390
|
+
return this.getLocationPermissionResponse();
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
async requestForegroundPermission(): Promise<PermissionResponse> {
|
|
2394
|
+
return this.requestLocationPermissionResponse();
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
async getBackgroundPermissionStatus(): Promise<PermissionResponse> {
|
|
2398
|
+
const backgroundStatus = this.resolvePermissionStatus('ohos.permission.LOCATION_IN_BACKGROUND');
|
|
2399
|
+
return this.buildBackgroundPermissionResponse(backgroundStatus);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
async requestBackgroundPermission(): Promise<PermissionResponse> {
|
|
2403
|
+
await this.atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, [
|
|
2404
|
+
'ohos.permission.LOCATION_IN_BACKGROUND',
|
|
2405
|
+
]);
|
|
2406
|
+
return this.getBackgroundPermissionStatus();
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
async hasServicesEnabled(): Promise<boolean> {
|
|
2410
|
+
return geoLocationManager.isLocationEnabled();
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
async getProviderStatus(): Promise<ProviderStatus> {
|
|
2414
|
+
const locationServicesEnabled = geoLocationManager.isLocationEnabled();
|
|
2415
|
+
|
|
2416
|
+
return {
|
|
2417
|
+
locationServicesEnabled,
|
|
2418
|
+
backgroundModeEnabled:
|
|
2419
|
+
this.resolvePermissionStatus('ohos.permission.LOCATION_IN_BACKGROUND') ===
|
|
2420
|
+
abilityAccessCtrl.PermissionStatus.GRANTED,
|
|
2421
|
+
gpsAvailable: locationServicesEnabled,
|
|
2422
|
+
networkAvailable: locationServicesEnabled,
|
|
2423
|
+
passiveAvailable: locationServicesEnabled,
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
async getCurrentPosition(options?: { accuracy?: number }): Promise<ExpoLocationObject> {
|
|
2428
|
+
const location = await geoLocationManager.getCurrentLocation(
|
|
2429
|
+
this.createCurrentLocationRequest(options?.accuracy),
|
|
2430
|
+
);
|
|
2431
|
+
return this.normalizeLocation(location);
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
async getLastKnownPosition(_options?: Record<string, unknown>): Promise<ExpoLocationObject | null> {
|
|
2435
|
+
try {
|
|
2436
|
+
const location = geoLocationManager.getLastLocation();
|
|
2437
|
+
return this.hasCoordinates(location) ? this.normalizeLocation(location) : null;
|
|
2438
|
+
} catch (_error) {
|
|
2439
|
+
return null;
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
async geocode(address: string): Promise<GeocodeResult[]> {
|
|
2444
|
+
const addresses = await geoLocationManager.getAddressesFromLocationName({
|
|
2445
|
+
description: address,
|
|
2446
|
+
locale: 'en-US',
|
|
2447
|
+
maxItems: 5,
|
|
2448
|
+
});
|
|
2449
|
+
|
|
2450
|
+
return addresses.map((addressEntry) => ({
|
|
2451
|
+
latitude: Number(addressEntry.latitude ?? 0),
|
|
2452
|
+
longitude: Number(addressEntry.longitude ?? 0),
|
|
2453
|
+
}));
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
async reverseGeocode(location: { latitude: number; longitude: number }): Promise<ReverseGeocodeResult[]> {
|
|
2457
|
+
const addresses = await geoLocationManager.getAddressesFromLocation({
|
|
2458
|
+
latitude: Number(location.latitude),
|
|
2459
|
+
longitude: Number(location.longitude),
|
|
2460
|
+
locale: 'en-US',
|
|
2461
|
+
maxItems: 5,
|
|
2462
|
+
});
|
|
2463
|
+
|
|
2464
|
+
return addresses.map((addressEntry) => ({
|
|
2465
|
+
city: addressEntry.locality ?? null,
|
|
2466
|
+
district: addressEntry.subLocality ?? null,
|
|
2467
|
+
streetNumber: addressEntry.subThoroughfare ?? null,
|
|
2468
|
+
street: addressEntry.thoroughfare ?? null,
|
|
2469
|
+
region: addressEntry.administrativeArea ?? null,
|
|
2470
|
+
subregion: addressEntry.subAdministrativeArea ?? null,
|
|
2471
|
+
country: addressEntry.countryName ?? null,
|
|
2472
|
+
postalCode: addressEntry.postalCode ?? null,
|
|
2473
|
+
name: addressEntry.placeName ?? null,
|
|
2474
|
+
isoCountryCode: addressEntry.countryCode ?? null,
|
|
2475
|
+
timezone: null,
|
|
2476
|
+
formattedAddress:
|
|
2477
|
+
addressEntry.addressUrl ??
|
|
2478
|
+
(Array.isArray(addressEntry.descriptions) && addressEntry.descriptions.length > 0
|
|
2479
|
+
? addressEntry.descriptions.join(', ')
|
|
2480
|
+
: null),
|
|
2481
|
+
}));
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
async startWatchPosition(
|
|
2485
|
+
options?: { accuracy?: number },
|
|
2486
|
+
_listenerConfig?: Record<string, unknown>,
|
|
2487
|
+
): Promise<{ watchId: number; initialLocation: ExpoLocationObject | null }> {
|
|
2488
|
+
const watchId = this.nextWatchId++;
|
|
2489
|
+
return {
|
|
2490
|
+
watchId,
|
|
2491
|
+
initialLocation: await this.getLastKnownPosition(options ?? {}),
|
|
2492
|
+
};
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
async stopWatchPosition(_watchId: number): Promise<void> {
|
|
2496
|
+
return;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
async getHeading(): Promise<HeadingObject> {
|
|
2500
|
+
return this.readHeadingSnapshot();
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
async startWatchHeading(
|
|
2504
|
+
_listenerConfig?: Record<string, unknown>,
|
|
2505
|
+
): Promise<{ watchId: number; initialHeading: HeadingObject }> {
|
|
2506
|
+
const watchId = this.nextWatchId++;
|
|
2507
|
+
return {
|
|
2508
|
+
watchId,
|
|
2509
|
+
initialHeading: await this.getHeading(),
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
async stopWatchHeading(_watchId: number): Promise<void> {
|
|
2514
|
+
return;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
private async getLocationPermissionResponse(): Promise<PermissionResponse> {
|
|
2518
|
+
const approximateStatus = this.resolvePermissionStatus('ohos.permission.APPROXIMATELY_LOCATION');
|
|
2519
|
+
const preciseStatus = this.resolvePermissionStatus('ohos.permission.LOCATION');
|
|
2520
|
+
|
|
2521
|
+
return this.buildPermissionResponse(approximateStatus, preciseStatus);
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
private buildBackgroundPermissionResponse(
|
|
2525
|
+
backgroundStatus: abilityAccessCtrl.PermissionStatus,
|
|
2526
|
+
): PermissionResponse {
|
|
2527
|
+
const foreground = this.getLocationPermissionResponse();
|
|
2528
|
+
const granted = backgroundStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2529
|
+
const denied =
|
|
2530
|
+
backgroundStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2531
|
+
backgroundStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2532
|
+
backgroundStatus === abilityAccessCtrl.PermissionStatus.INVALID;
|
|
2533
|
+
|
|
2534
|
+
return foreground.then((foregroundPermission) => ({
|
|
2535
|
+
...foregroundPermission,
|
|
2536
|
+
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
2537
|
+
granted,
|
|
2538
|
+
canAskAgain: !denied,
|
|
2539
|
+
}));
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
private async requestLocationPermissionResponse(): Promise<PermissionResponse> {
|
|
2543
|
+
await this.atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, [
|
|
2544
|
+
'ohos.permission.APPROXIMATELY_LOCATION',
|
|
2545
|
+
'ohos.permission.LOCATION',
|
|
2546
|
+
]);
|
|
2547
|
+
|
|
2548
|
+
return this.getLocationPermissionResponse();
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
private resolvePermissionStatus(permissionName: Permissions): abilityAccessCtrl.PermissionStatus {
|
|
2552
|
+
const atManagerWithSelfStatus = this.atManager as abilityAccessCtrl.AtManager & {
|
|
2553
|
+
getSelfPermissionStatus?: (permission: Permissions) => abilityAccessCtrl.PermissionStatus;
|
|
2554
|
+
};
|
|
2555
|
+
|
|
2556
|
+
if (typeof atManagerWithSelfStatus.getSelfPermissionStatus === 'function') {
|
|
2557
|
+
return atManagerWithSelfStatus.getSelfPermissionStatus(permissionName);
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
const accessTokenId = this.ctx.uiAbilityContext.abilityInfo.applicationInfo.accessTokenId;
|
|
2561
|
+
const grantStatus = this.atManager.checkAccessTokenSync(accessTokenId, permissionName);
|
|
2562
|
+
|
|
2563
|
+
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
|
|
2564
|
+
? abilityAccessCtrl.PermissionStatus.GRANTED
|
|
2565
|
+
: abilityAccessCtrl.PermissionStatus.NOT_DETERMINED;
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
private buildPermissionResponse(
|
|
2569
|
+
approximateStatus: abilityAccessCtrl.PermissionStatus,
|
|
2570
|
+
preciseStatus: abilityAccessCtrl.PermissionStatus,
|
|
2571
|
+
): PermissionResponse {
|
|
2572
|
+
const coarseGranted = approximateStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2573
|
+
const fineGranted = preciseStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2574
|
+
const granted = coarseGranted || fineGranted;
|
|
2575
|
+
const denied =
|
|
2576
|
+
(approximateStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2577
|
+
approximateStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2578
|
+
approximateStatus === abilityAccessCtrl.PermissionStatus.INVALID) &&
|
|
2579
|
+
(preciseStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2580
|
+
preciseStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2581
|
+
preciseStatus === abilityAccessCtrl.PermissionStatus.INVALID);
|
|
2582
|
+
|
|
2583
|
+
return {
|
|
2584
|
+
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
2585
|
+
granted,
|
|
2586
|
+
canAskAgain: !denied,
|
|
2587
|
+
expires: 'never',
|
|
2588
|
+
android: {
|
|
2589
|
+
accuracy: fineGranted ? 'fine' : coarseGranted ? 'coarse' : 'none',
|
|
2590
|
+
},
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
private createCurrentLocationRequest(accuracy?: number): geoLocationManager.CurrentLocationRequest {
|
|
2595
|
+
if (typeof accuracy === 'number' && accuracy >= 4) {
|
|
2596
|
+
return {
|
|
2597
|
+
priority: geoLocationManager.LocationRequestPriority.ACCURACY,
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
if (typeof accuracy === 'number' && accuracy <= 2) {
|
|
2602
|
+
return {
|
|
2603
|
+
priority: geoLocationManager.LocationRequestPriority.LOW_POWER,
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
return {
|
|
2608
|
+
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
private hasCoordinates(location: geoLocationManager.Location | null | undefined): location is geoLocationManager.Location {
|
|
2613
|
+
return (
|
|
2614
|
+
!!location &&
|
|
2615
|
+
typeof location.latitude === 'number' &&
|
|
2616
|
+
typeof location.longitude === 'number'
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
private normalizeLocation(location: geoLocationManager.Location): ExpoLocationObject {
|
|
2621
|
+
return {
|
|
2622
|
+
coords: {
|
|
2623
|
+
latitude: Number(location.latitude),
|
|
2624
|
+
longitude: Number(location.longitude),
|
|
2625
|
+
altitude: typeof location.altitude === 'number' ? location.altitude : null,
|
|
2626
|
+
accuracy: typeof location.accuracy === 'number' ? location.accuracy : null,
|
|
2627
|
+
altitudeAccuracy: null,
|
|
2628
|
+
heading: typeof location.direction === 'number' ? location.direction : null,
|
|
2629
|
+
speed: typeof location.speed === 'number' ? location.speed : null,
|
|
2630
|
+
},
|
|
2631
|
+
timestamp: Number(location.timeStamp ?? Date.now()),
|
|
2632
|
+
mocked: false,
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
private async readHeadingSnapshot(): Promise<HeadingObject> {
|
|
2637
|
+
return new Promise((resolve) => {
|
|
2638
|
+
try {
|
|
2639
|
+
sensor.once(sensor.SensorId.ROTATION_VECTOR, (data: Record<string, number>) => {
|
|
2640
|
+
const x = Number(data?.x ?? 0);
|
|
2641
|
+
const y = Number(data?.y ?? 0);
|
|
2642
|
+
const z = Number(data?.z ?? 0);
|
|
2643
|
+
const w = Number(data?.w ?? 1);
|
|
2644
|
+
const sinyCosp = 2 * (w * z + x * y);
|
|
2645
|
+
const cosyCosp = 1 - 2 * (y * y + z * z);
|
|
2646
|
+
const magHeading = (Math.atan2(sinyCosp, cosyCosp) * 180 / Math.PI + 360) % 360;
|
|
2647
|
+
resolve({
|
|
2648
|
+
magHeading,
|
|
2649
|
+
trueHeading: magHeading,
|
|
2650
|
+
accuracy: 3,
|
|
2651
|
+
});
|
|
2652
|
+
});
|
|
2653
|
+
} catch (_error) {
|
|
2654
|
+
resolve({
|
|
2655
|
+
magHeading: 0,
|
|
2656
|
+
trueHeading: null,
|
|
2657
|
+
accuracy: 0,
|
|
2658
|
+
});
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
`;
|
|
2664
|
+
}
|
|
2665
|
+
function renderExpoHarmonyCameraTurboModule() {
|
|
2666
|
+
return `import type { Permissions } from '@ohos.abilityAccessCtrl';
|
|
2667
|
+
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
|
|
2668
|
+
import camera from '@ohos.multimedia.camera';
|
|
2669
|
+
import cameraPicker from '@ohos.multimedia.cameraPicker';
|
|
2670
|
+
import image from '@ohos.multimedia.image';
|
|
2671
|
+
import { UITurboModuleContext, UITurboModule } from '@rnoh/react-native-openharmony/ts';
|
|
2672
|
+
|
|
2673
|
+
type PermissionResponse = {
|
|
2674
|
+
status: 'granted' | 'denied' | 'undetermined';
|
|
2675
|
+
granted: boolean;
|
|
2676
|
+
canAskAgain: boolean;
|
|
2677
|
+
expires: 'never';
|
|
2678
|
+
};
|
|
2679
|
+
|
|
2680
|
+
type CameraCaptureResult = {
|
|
2681
|
+
uri: string;
|
|
2682
|
+
width: number;
|
|
2683
|
+
height: number;
|
|
2684
|
+
base64?: string;
|
|
2685
|
+
exif: null;
|
|
2686
|
+
};
|
|
2687
|
+
|
|
2688
|
+
type CameraRecordingResult = {
|
|
2689
|
+
uri: string;
|
|
2690
|
+
duration: number;
|
|
2691
|
+
fileSize: number | null;
|
|
2692
|
+
mimeType: string;
|
|
2693
|
+
};
|
|
2694
|
+
|
|
2695
|
+
export class ExpoHarmonyCameraTurboModule extends UITurboModule {
|
|
2696
|
+
public static readonly NAME = 'ExpoHarmonyCamera';
|
|
2697
|
+
|
|
2698
|
+
private readonly atManager = abilityAccessCtrl.createAtManager();
|
|
2699
|
+
private readonly previewStates = new Map<string, 'running' | 'paused'>();
|
|
2700
|
+
private readonly activeRecordings = new Map<string, CameraRecordingResult>();
|
|
2701
|
+
|
|
2702
|
+
getConstants(): Record<string, never> {
|
|
2703
|
+
return {};
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
async getCameraPermissionStatus(): Promise<PermissionResponse> {
|
|
2707
|
+
return this.getPermissionResponse('ohos.permission.CAMERA');
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
async requestCameraPermission(): Promise<PermissionResponse> {
|
|
2711
|
+
return this.requestPermissionResponse('ohos.permission.CAMERA');
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
async getMicrophonePermissionStatus(): Promise<PermissionResponse> {
|
|
2715
|
+
return this.getPermissionResponse('ohos.permission.MICROPHONE');
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
async requestMicrophonePermission(): Promise<PermissionResponse> {
|
|
2719
|
+
return this.requestPermissionResponse('ohos.permission.MICROPHONE');
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
async createPreview(options?: { viewId?: string }): Promise<{ viewId: string; state: 'running' }> {
|
|
2723
|
+
const viewId = typeof options?.viewId === 'string' && options.viewId.length > 0
|
|
2724
|
+
? options.viewId
|
|
2725
|
+
: 'expo-harmony-camera-preview';
|
|
2726
|
+
this.previewStates.set(viewId, 'running');
|
|
2727
|
+
return {
|
|
2728
|
+
viewId,
|
|
2729
|
+
state: 'running',
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
async disposePreview(options?: { viewId?: string }): Promise<void> {
|
|
2734
|
+
if (typeof options?.viewId === 'string') {
|
|
2735
|
+
this.previewStates.delete(options.viewId);
|
|
2736
|
+
this.activeRecordings.delete(options.viewId);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
async pausePreview(options?: { viewId?: string }): Promise<{ paused: boolean }> {
|
|
2741
|
+
if (typeof options?.viewId === 'string') {
|
|
2742
|
+
this.previewStates.set(options.viewId, 'paused');
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
paused: true,
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
async resumePreview(options?: { viewId?: string }): Promise<{ paused: boolean }> {
|
|
2750
|
+
if (typeof options?.viewId === 'string') {
|
|
2751
|
+
this.previewStates.set(options.viewId, 'running');
|
|
2752
|
+
}
|
|
2753
|
+
return {
|
|
2754
|
+
paused: false,
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
async takePicture(options?: { cameraType?: string; viewId?: string }): Promise<CameraCaptureResult> {
|
|
2759
|
+
const profile = new cameraPicker.PickerProfile();
|
|
2760
|
+
profile.cameraPosition =
|
|
2761
|
+
options?.cameraType === 'front'
|
|
2762
|
+
? camera.CameraPosition.CAMERA_POSITION_FRONT
|
|
2763
|
+
: camera.CameraPosition.CAMERA_POSITION_BACK;
|
|
2764
|
+
|
|
2765
|
+
const result = await cameraPicker.pick(
|
|
2766
|
+
this.ctx.uiAbilityContext,
|
|
2767
|
+
[cameraPicker.PickerMediaType.PHOTO],
|
|
2768
|
+
profile,
|
|
2769
|
+
);
|
|
2770
|
+
|
|
2771
|
+
if (!result || typeof result.resultUri !== 'string' || result.resultUri.length === 0) {
|
|
2772
|
+
throw new Error('Camera capture was canceled.');
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
const imageSize = await this.getImageSize(result.resultUri);
|
|
2776
|
+
|
|
2777
|
+
return {
|
|
2778
|
+
uri: result.resultUri,
|
|
2779
|
+
width: imageSize.width,
|
|
2780
|
+
height: imageSize.height,
|
|
2781
|
+
exif: null,
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2785
|
+
async startRecording(options?: { viewId?: string; cameraType?: string }): Promise<CameraRecordingResult> {
|
|
2786
|
+
await this.requestPermissionResponse('ohos.permission.MICROPHONE');
|
|
2787
|
+
const profile = new cameraPicker.PickerProfile();
|
|
2788
|
+
profile.cameraPosition =
|
|
2789
|
+
options?.cameraType === 'front'
|
|
2790
|
+
? camera.CameraPosition.CAMERA_POSITION_FRONT
|
|
2791
|
+
: camera.CameraPosition.CAMERA_POSITION_BACK;
|
|
2792
|
+
|
|
2793
|
+
const result = await cameraPicker.pick(
|
|
2794
|
+
this.ctx.uiAbilityContext,
|
|
2795
|
+
[cameraPicker.PickerMediaType.VIDEO],
|
|
2796
|
+
profile,
|
|
2797
|
+
);
|
|
2798
|
+
|
|
2799
|
+
if (!result || typeof result.resultUri !== 'string' || result.resultUri.length === 0) {
|
|
2800
|
+
throw new Error('Camera recording was canceled.');
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
const recording = {
|
|
2804
|
+
uri: result.resultUri,
|
|
2805
|
+
duration: 0,
|
|
2806
|
+
fileSize: null,
|
|
2807
|
+
mimeType: 'video/mp4',
|
|
2808
|
+
};
|
|
2809
|
+
|
|
2810
|
+
if (typeof options?.viewId === 'string') {
|
|
2811
|
+
this.activeRecordings.set(options.viewId, recording);
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
return recording;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
async stopRecording(options?: { viewId?: string }): Promise<CameraRecordingResult | null> {
|
|
2818
|
+
if (typeof options?.viewId === 'string') {
|
|
2819
|
+
const recording = this.activeRecordings.get(options.viewId) ?? null;
|
|
2820
|
+
this.activeRecordings.delete(options.viewId);
|
|
2821
|
+
return recording;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
return null;
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
async toggleRecording(options?: { viewId?: string; cameraType?: string }): Promise<CameraRecordingResult | null> {
|
|
2828
|
+
if (typeof options?.viewId === 'string' && this.activeRecordings.has(options.viewId)) {
|
|
2829
|
+
return this.stopRecording(options);
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
return this.startRecording(options);
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
private async getPermissionResponse(permissionName: Permissions): Promise<PermissionResponse> {
|
|
2836
|
+
const atManagerWithSelfStatus = this.atManager as abilityAccessCtrl.AtManager & {
|
|
2837
|
+
getSelfPermissionStatus?: (permission: Permissions) => abilityAccessCtrl.PermissionStatus;
|
|
2838
|
+
};
|
|
2839
|
+
const permissionStatus =
|
|
2840
|
+
typeof atManagerWithSelfStatus.getSelfPermissionStatus === 'function'
|
|
2841
|
+
? atManagerWithSelfStatus.getSelfPermissionStatus(permissionName)
|
|
2842
|
+
: this.atManager.checkAccessTokenSync(
|
|
2843
|
+
this.ctx.uiAbilityContext.abilityInfo.applicationInfo.accessTokenId,
|
|
2844
|
+
permissionName,
|
|
2845
|
+
) === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
|
|
2846
|
+
? abilityAccessCtrl.PermissionStatus.GRANTED
|
|
2847
|
+
: abilityAccessCtrl.PermissionStatus.NOT_DETERMINED;
|
|
2848
|
+
|
|
2849
|
+
return this.createPermissionResponse(permissionStatus);
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
private async requestPermissionResponse(permissionName: Permissions): Promise<PermissionResponse> {
|
|
2853
|
+
await this.atManager.requestPermissionsFromUser(this.ctx.uiAbilityContext, [permissionName]);
|
|
2854
|
+
return this.getPermissionResponse(permissionName);
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
private createPermissionResponse(
|
|
2858
|
+
permissionStatus: abilityAccessCtrl.PermissionStatus,
|
|
2859
|
+
): PermissionResponse {
|
|
2860
|
+
const granted = permissionStatus === abilityAccessCtrl.PermissionStatus.GRANTED;
|
|
2861
|
+
const denied =
|
|
2862
|
+
permissionStatus === abilityAccessCtrl.PermissionStatus.DENIED ||
|
|
2863
|
+
permissionStatus === abilityAccessCtrl.PermissionStatus.RESTRICTED ||
|
|
2864
|
+
permissionStatus === abilityAccessCtrl.PermissionStatus.INVALID;
|
|
2865
|
+
|
|
2866
|
+
return {
|
|
2867
|
+
status: granted ? 'granted' : denied ? 'denied' : 'undetermined',
|
|
2868
|
+
granted,
|
|
2869
|
+
canAskAgain: !denied,
|
|
2870
|
+
expires: 'never',
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
private async getImageSize(assetUri: string): Promise<{ width: number; height: number }> {
|
|
2875
|
+
let imageSource: image.ImageSource | null = null;
|
|
2876
|
+
|
|
2877
|
+
try {
|
|
2878
|
+
imageSource = image.createImageSource(assetUri);
|
|
2879
|
+
const imageInfo = await imageSource.getImageInfo();
|
|
2880
|
+
return {
|
|
2881
|
+
width: Number(imageInfo.size?.width ?? 0),
|
|
2882
|
+
height: Number(imageInfo.size?.height ?? 0),
|
|
2883
|
+
};
|
|
2884
|
+
} catch (_error) {
|
|
2885
|
+
return {
|
|
2886
|
+
width: 0,
|
|
2887
|
+
height: 0,
|
|
2888
|
+
};
|
|
2889
|
+
} finally {
|
|
2890
|
+
if (imageSource) {
|
|
2891
|
+
try {
|
|
2892
|
+
await imageSource.release();
|
|
2893
|
+
} catch (_error) {
|
|
2894
|
+
// Ignore cleanup errors from ImageSource release.
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
`;
|
|
2901
|
+
}
|
|
2902
|
+
function renderExpoFileSystemHarmonyAdapterShim(capability) {
|
|
2903
|
+
return `'use strict';
|
|
2904
|
+
|
|
2905
|
+
const { TurboModuleRegistry } = require('react-native');
|
|
2906
|
+
const { CodedError } = require('expo-modules-core');
|
|
2907
|
+
|
|
2908
|
+
const FILE_SCHEME = 'file://';
|
|
2909
|
+
const NATIVE_MODULE_NAME = 'ExpoHarmonyFileSystem';
|
|
2910
|
+
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
2911
|
+
const NATIVE_CONSTANTS = NATIVE_MODULE?.getConstants ? NATIVE_MODULE.getConstants() : {};
|
|
2912
|
+
|
|
2913
|
+
function createError(code, message) {
|
|
2914
|
+
return new CodedError(code, message);
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
function requireNativeModule(operationName) {
|
|
2918
|
+
if (NATIVE_MODULE) {
|
|
2919
|
+
return NATIVE_MODULE;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
throw createError(
|
|
2923
|
+
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
2924
|
+
'${capability.packageName} expected the ' +
|
|
2925
|
+
NATIVE_MODULE_NAME +
|
|
2926
|
+
' TurboModule to be registered, but it was missing while running ' +
|
|
2927
|
+
operationName +
|
|
2928
|
+
'.',
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
function createUnsupportedError(operationName) {
|
|
2933
|
+
return createError(
|
|
2934
|
+
'ERR_EXPO_HARMONY_UNSUPPORTED',
|
|
2935
|
+
'${capability.packageName} currently supports UTF-8 sandbox file operations only. Unsupported operation: ' +
|
|
2936
|
+
operationName +
|
|
2937
|
+
'.',
|
|
2938
|
+
);
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
function toFileUri(pathValue, ensureTrailingSlash) {
|
|
2942
|
+
if (typeof pathValue !== 'string' || pathValue.length === 0) {
|
|
2943
|
+
return null;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
const normalizedPath = pathValue.startsWith(FILE_SCHEME)
|
|
2947
|
+
? pathValue.slice(FILE_SCHEME.length)
|
|
2948
|
+
: pathValue;
|
|
2949
|
+
const withScheme = FILE_SCHEME + normalizedPath;
|
|
2950
|
+
|
|
2951
|
+
if (!ensureTrailingSlash) {
|
|
2952
|
+
return withScheme;
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
let normalizedSchemePath = withScheme;
|
|
2956
|
+
|
|
2957
|
+
while (normalizedSchemePath.endsWith('/')) {
|
|
2958
|
+
normalizedSchemePath = normalizedSchemePath.slice(0, -1);
|
|
2959
|
+
}
|
|
2960
|
+
|
|
2961
|
+
return normalizedSchemePath + '/';
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
function normalizeInputPath(inputPath) {
|
|
2965
|
+
if (typeof inputPath !== 'string' || inputPath.length === 0) {
|
|
2966
|
+
throw createError(
|
|
2967
|
+
'ERR_EXPO_HARMONY_INVALID_URI',
|
|
2968
|
+
'${capability.packageName} expected a non-empty file URI.',
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2972
|
+
const normalizedPath = inputPath.startsWith(FILE_SCHEME)
|
|
2973
|
+
? inputPath.slice(FILE_SCHEME.length)
|
|
2974
|
+
: inputPath;
|
|
2975
|
+
|
|
2976
|
+
if (!normalizedPath.startsWith('/')) {
|
|
2977
|
+
throw createError(
|
|
2978
|
+
'ERR_EXPO_HARMONY_INVALID_URI',
|
|
2979
|
+
'${capability.packageName} supports only absolute file:// URIs inside the app sandbox.',
|
|
2980
|
+
);
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
if (
|
|
2984
|
+
normalizedPath.includes('/../') ||
|
|
2985
|
+
normalizedPath.endsWith('/..') ||
|
|
2986
|
+
normalizedPath.includes('/./')
|
|
2987
|
+
) {
|
|
2988
|
+
throw createError(
|
|
2989
|
+
'ERR_EXPO_HARMONY_INVALID_URI',
|
|
2990
|
+
'${capability.packageName} does not accept relative path segments.',
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
return normalizedPath;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
function normalizeStringEncoding(rawEncoding) {
|
|
2998
|
+
if (rawEncoding == null || rawEncoding === 'utf8') {
|
|
2999
|
+
return 'utf8';
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
if (rawEncoding === 'base64') {
|
|
3003
|
+
return 'base64';
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
throw createUnsupportedError('encoding=' + String(rawEncoding));
|
|
3007
|
+
}
|
|
3008
|
+
|
|
3009
|
+
function normalizeFileDownloadResult(result, requestedUri) {
|
|
3010
|
+
return {
|
|
3011
|
+
uri: result?.uri ?? String(requestedUri),
|
|
3012
|
+
status:
|
|
3013
|
+
typeof result?.status === 'number' && Number.isFinite(result.status)
|
|
3014
|
+
? result.status
|
|
3015
|
+
: 200,
|
|
3016
|
+
headers: result?.headers && typeof result.headers === 'object' ? result.headers : {},
|
|
3017
|
+
md5: typeof result?.md5 === 'string' ? result.md5 : undefined,
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
function normalizeFileInfoResult(requestedUri, nativeResult) {
|
|
3022
|
+
if (!nativeResult || nativeResult.exists !== true) {
|
|
3023
|
+
return {
|
|
3024
|
+
exists: false,
|
|
3025
|
+
isDirectory: false,
|
|
3026
|
+
uri: String(requestedUri),
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
3029
|
+
|
|
3030
|
+
const normalizedResult = {
|
|
3031
|
+
exists: true,
|
|
3032
|
+
uri: toFileUri(nativeResult.path, false) ?? String(requestedUri),
|
|
3033
|
+
size: Number(nativeResult.size ?? 0),
|
|
3034
|
+
isDirectory: nativeResult.isDirectory === true,
|
|
3035
|
+
modificationTime: Number(nativeResult.modificationTime ?? 0),
|
|
3036
|
+
};
|
|
3037
|
+
|
|
3038
|
+
if (typeof nativeResult.md5 === 'string' && nativeResult.md5.length > 0) {
|
|
3039
|
+
normalizedResult.md5 = nativeResult.md5;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
return normalizedResult;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
function normalizeNativeError(error) {
|
|
3046
|
+
if (error instanceof Error) {
|
|
3047
|
+
return error;
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
if (error && typeof error === 'object') {
|
|
3051
|
+
const code =
|
|
3052
|
+
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3053
|
+
? String(error.code)
|
|
3054
|
+
: null;
|
|
3055
|
+
const message =
|
|
3056
|
+
typeof error.message === 'string' && error.message.length > 0
|
|
3057
|
+
? error.message
|
|
3058
|
+
: typeof error.name === 'string' && error.name.length > 0
|
|
3059
|
+
? error.name
|
|
3060
|
+
: JSON.stringify(error);
|
|
3061
|
+
|
|
3062
|
+
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
return new Error(String(error));
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
module.exports = {
|
|
3069
|
+
documentDirectory: toFileUri(NATIVE_CONSTANTS.documentDirectoryPath, true),
|
|
3070
|
+
cacheDirectory: toFileUri(NATIVE_CONSTANTS.cacheDirectoryPath, true),
|
|
3071
|
+
bundleDirectory: toFileUri(NATIVE_CONSTANTS.bundleDirectoryPath, true),
|
|
3072
|
+
EncodingType: {
|
|
3073
|
+
UTF8: 'utf8',
|
|
3074
|
+
Base64: 'base64',
|
|
3075
|
+
},
|
|
3076
|
+
FileSystemSessionType: {
|
|
3077
|
+
BACKGROUND: 0,
|
|
3078
|
+
FOREGROUND: 1,
|
|
3079
|
+
},
|
|
3080
|
+
async getInfoAsync(fileUri, options) {
|
|
3081
|
+
const normalizedPath = normalizeInputPath(fileUri);
|
|
3082
|
+
try {
|
|
3083
|
+
const result = await requireNativeModule('getInfoAsync').getInfo(normalizedPath, {
|
|
3084
|
+
md5: options?.md5 === true,
|
|
3085
|
+
});
|
|
3086
|
+
return normalizeFileInfoResult(fileUri, result);
|
|
3087
|
+
} catch (error) {
|
|
3088
|
+
throw normalizeNativeError(error);
|
|
3089
|
+
}
|
|
3090
|
+
},
|
|
3091
|
+
async readAsStringAsync(fileUri, options) {
|
|
3092
|
+
const normalizedPath = normalizeInputPath(fileUri);
|
|
3093
|
+
try {
|
|
3094
|
+
return await requireNativeModule('readAsStringAsync').readAsString(normalizedPath, {
|
|
3095
|
+
encoding: normalizeStringEncoding(options?.encoding),
|
|
3096
|
+
position: options?.position,
|
|
3097
|
+
length: options?.length,
|
|
3098
|
+
});
|
|
3099
|
+
} catch (error) {
|
|
3100
|
+
throw normalizeNativeError(error);
|
|
3101
|
+
}
|
|
3102
|
+
},
|
|
3103
|
+
async writeAsStringAsync(fileUri, contents, options) {
|
|
3104
|
+
const normalizedPath = normalizeInputPath(fileUri);
|
|
3105
|
+
try {
|
|
3106
|
+
await requireNativeModule('writeAsStringAsync').writeAsString(
|
|
3107
|
+
normalizedPath,
|
|
3108
|
+
String(contents),
|
|
3109
|
+
{
|
|
3110
|
+
encoding: normalizeStringEncoding(options?.encoding),
|
|
3111
|
+
append: options?.append === true,
|
|
3112
|
+
},
|
|
3113
|
+
);
|
|
3114
|
+
} catch (error) {
|
|
3115
|
+
throw normalizeNativeError(error);
|
|
3116
|
+
}
|
|
3117
|
+
},
|
|
3118
|
+
async deleteAsync(fileUri, options) {
|
|
3119
|
+
const normalizedPath = normalizeInputPath(fileUri);
|
|
3120
|
+
try {
|
|
3121
|
+
await requireNativeModule('deleteAsync').deletePath(normalizedPath, {
|
|
3122
|
+
idempotent: options?.idempotent === true,
|
|
3123
|
+
});
|
|
3124
|
+
} catch (error) {
|
|
3125
|
+
throw normalizeNativeError(error);
|
|
3126
|
+
}
|
|
3127
|
+
},
|
|
3128
|
+
async makeDirectoryAsync(fileUri, options) {
|
|
3129
|
+
const normalizedPath = normalizeInputPath(fileUri);
|
|
3130
|
+
try {
|
|
3131
|
+
await requireNativeModule('makeDirectoryAsync').makeDirectory(normalizedPath, {
|
|
3132
|
+
intermediates: options?.intermediates === true,
|
|
3133
|
+
});
|
|
3134
|
+
} catch (error) {
|
|
3135
|
+
throw normalizeNativeError(error);
|
|
3136
|
+
}
|
|
3137
|
+
},
|
|
3138
|
+
async readDirectoryAsync(fileUri) {
|
|
3139
|
+
const normalizedPath = normalizeInputPath(fileUri);
|
|
3140
|
+
try {
|
|
3141
|
+
return await requireNativeModule('readDirectoryAsync').readDirectory(normalizedPath);
|
|
3142
|
+
} catch (error) {
|
|
3143
|
+
throw normalizeNativeError(error);
|
|
3144
|
+
}
|
|
3145
|
+
},
|
|
3146
|
+
async copyAsync(options) {
|
|
3147
|
+
const fromPath = normalizeInputPath(options?.from);
|
|
3148
|
+
const toPath = normalizeInputPath(options?.to);
|
|
3149
|
+
try {
|
|
3150
|
+
await requireNativeModule('copyAsync').copy(fromPath, toPath);
|
|
3151
|
+
} catch (error) {
|
|
3152
|
+
throw normalizeNativeError(error);
|
|
3153
|
+
}
|
|
3154
|
+
},
|
|
3155
|
+
async moveAsync(options) {
|
|
3156
|
+
const fromPath = normalizeInputPath(options?.from);
|
|
3157
|
+
const toPath = normalizeInputPath(options?.to);
|
|
3158
|
+
try {
|
|
3159
|
+
await requireNativeModule('moveAsync').move(fromPath, toPath);
|
|
3160
|
+
} catch (error) {
|
|
3161
|
+
throw normalizeNativeError(error);
|
|
3162
|
+
}
|
|
3163
|
+
},
|
|
3164
|
+
async downloadAsync(url, fileUri, options) {
|
|
3165
|
+
const normalizedPath = normalizeInputPath(fileUri);
|
|
3166
|
+
try {
|
|
3167
|
+
return normalizeFileDownloadResult(
|
|
3168
|
+
await requireNativeModule('downloadAsync').download(
|
|
3169
|
+
String(url),
|
|
3170
|
+
normalizedPath,
|
|
3171
|
+
{
|
|
3172
|
+
headers: options?.headers ?? {},
|
|
3173
|
+
md5: options?.md5 === true,
|
|
3174
|
+
},
|
|
3175
|
+
),
|
|
3176
|
+
fileUri,
|
|
3177
|
+
);
|
|
3178
|
+
} catch (error) {
|
|
3179
|
+
throw normalizeNativeError(error);
|
|
3180
|
+
}
|
|
3181
|
+
},
|
|
3182
|
+
};
|
|
3183
|
+
`;
|
|
3184
|
+
}
|
|
3185
|
+
function renderExpoFileSystemPreviewShim(capability) {
|
|
3186
|
+
return `'use strict';
|
|
3187
|
+
|
|
3188
|
+
const { CodedError } = require('expo-modules-core');
|
|
3189
|
+
|
|
3190
|
+
const PREVIEW_MESSAGE =
|
|
3191
|
+
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, but runtime file-system behavior is not verified yet.';
|
|
3192
|
+
|
|
3193
|
+
function createPreviewError(operationName) {
|
|
3194
|
+
return new CodedError(
|
|
3195
|
+
'ERR_EXPO_HARMONY_PREVIEW',
|
|
3196
|
+
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
3197
|
+
);
|
|
3198
|
+
}
|
|
3199
|
+
|
|
3200
|
+
async function unavailable(operationName) {
|
|
3201
|
+
throw createPreviewError(operationName);
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
module.exports = {
|
|
3205
|
+
cacheDirectory: 'file:///expo-harmony/cache/',
|
|
3206
|
+
documentDirectory: 'file:///expo-harmony/document/',
|
|
3207
|
+
bundleDirectory: 'file:///expo-harmony/bundle/',
|
|
3208
|
+
EncodingType: {
|
|
3209
|
+
UTF8: 'utf8',
|
|
3210
|
+
Base64: 'base64',
|
|
3211
|
+
},
|
|
3212
|
+
FileSystemSessionType: {
|
|
3213
|
+
BACKGROUND: 0,
|
|
3214
|
+
FOREGROUND: 1,
|
|
3215
|
+
},
|
|
3216
|
+
getInfoAsync(path) {
|
|
3217
|
+
return unavailable('getInfoAsync(' + String(path) + ')');
|
|
3218
|
+
},
|
|
3219
|
+
readAsStringAsync(path) {
|
|
3220
|
+
return unavailable('readAsStringAsync(' + String(path) + ')');
|
|
3221
|
+
},
|
|
3222
|
+
writeAsStringAsync(path) {
|
|
3223
|
+
return unavailable('writeAsStringAsync(' + String(path) + ')');
|
|
3224
|
+
},
|
|
3225
|
+
deleteAsync(path) {
|
|
3226
|
+
return unavailable('deleteAsync(' + String(path) + ')');
|
|
3227
|
+
},
|
|
3228
|
+
makeDirectoryAsync(path) {
|
|
3229
|
+
return unavailable('makeDirectoryAsync(' + String(path) + ')');
|
|
3230
|
+
},
|
|
3231
|
+
readDirectoryAsync(path) {
|
|
3232
|
+
return unavailable('readDirectoryAsync(' + String(path) + ')');
|
|
3233
|
+
},
|
|
3234
|
+
copyAsync(options) {
|
|
3235
|
+
return unavailable('copyAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3236
|
+
},
|
|
3237
|
+
moveAsync(options) {
|
|
3238
|
+
return unavailable('moveAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3239
|
+
},
|
|
3240
|
+
downloadAsync(url) {
|
|
3241
|
+
return unavailable('downloadAsync(' + String(url) + ')');
|
|
3242
|
+
},
|
|
3243
|
+
};
|
|
3244
|
+
`;
|
|
3245
|
+
}
|
|
3246
|
+
function renderExpoImagePickerHarmonyAdapterShim(capability) {
|
|
3247
|
+
return `'use strict';
|
|
3248
|
+
|
|
3249
|
+
const { TurboModuleRegistry } = require('react-native');
|
|
3250
|
+
const { CodedError } = require('expo-modules-core');
|
|
3251
|
+
|
|
3252
|
+
const NATIVE_MODULE_NAME = 'ExpoHarmonyImagePicker';
|
|
3253
|
+
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
3254
|
+
|
|
3255
|
+
function createError(code, message) {
|
|
3256
|
+
return new CodedError(code, message);
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
function requireNativeModule(operationName) {
|
|
3260
|
+
if (NATIVE_MODULE) {
|
|
3261
|
+
return NATIVE_MODULE;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
throw createError(
|
|
3265
|
+
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
3266
|
+
'${capability.packageName} expected the ' +
|
|
3267
|
+
NATIVE_MODULE_NAME +
|
|
3268
|
+
' TurboModule to be registered, but it was missing while running ' +
|
|
3269
|
+
operationName +
|
|
3270
|
+
'.',
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
function normalizeNativeError(error) {
|
|
3275
|
+
if (error instanceof Error) {
|
|
3276
|
+
return error;
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
if (error && typeof error === 'object') {
|
|
3280
|
+
const code =
|
|
3281
|
+
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3282
|
+
? String(error.code)
|
|
3283
|
+
: null;
|
|
3284
|
+
const message =
|
|
3285
|
+
typeof error.message === 'string' && error.message.length > 0
|
|
3286
|
+
? error.message
|
|
3287
|
+
: typeof error.name === 'string' && error.name.length > 0
|
|
3288
|
+
? error.name
|
|
3289
|
+
: JSON.stringify(error);
|
|
3290
|
+
|
|
3291
|
+
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
return new Error(String(error));
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
function normalizePickerResult(result) {
|
|
3298
|
+
if (!result || result.canceled === true || !Array.isArray(result.assets) || result.assets.length === 0) {
|
|
3299
|
+
return {
|
|
3300
|
+
canceled: true,
|
|
3301
|
+
assets: null,
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
return {
|
|
3306
|
+
canceled: false,
|
|
3307
|
+
assets: result.assets.map((asset) => ({
|
|
3308
|
+
uri: String(asset.uri),
|
|
3309
|
+
assetId: asset.assetId ?? null,
|
|
3310
|
+
width: Number(asset.width ?? 0),
|
|
3311
|
+
height: Number(asset.height ?? 0),
|
|
3312
|
+
type: asset.type ?? null,
|
|
3313
|
+
fileName: asset.fileName ?? null,
|
|
3314
|
+
fileSize:
|
|
3315
|
+
typeof asset.fileSize === 'number' && Number.isFinite(asset.fileSize)
|
|
3316
|
+
? asset.fileSize
|
|
3317
|
+
: null,
|
|
3318
|
+
mimeType: asset.mimeType ?? null,
|
|
3319
|
+
duration:
|
|
3320
|
+
typeof asset.duration === 'number' && Number.isFinite(asset.duration)
|
|
3321
|
+
? asset.duration
|
|
3322
|
+
: null,
|
|
3323
|
+
exif: asset.exif ?? null,
|
|
3324
|
+
base64: asset.base64 ?? null,
|
|
3325
|
+
})),
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
async function invokeNative(methodName, operationName, ...args) {
|
|
3330
|
+
try {
|
|
3331
|
+
return await requireNativeModule(operationName)[methodName](...args);
|
|
3332
|
+
} catch (error) {
|
|
3333
|
+
throw normalizeNativeError(error);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
module.exports = {
|
|
3338
|
+
MediaTypeOptions: {
|
|
3339
|
+
All: 'All',
|
|
3340
|
+
Images: 'Images',
|
|
3341
|
+
Videos: 'Videos',
|
|
3342
|
+
},
|
|
3343
|
+
CameraType: {
|
|
3344
|
+
front: 'front',
|
|
3345
|
+
back: 'back',
|
|
3346
|
+
},
|
|
3347
|
+
UIImagePickerPresentationStyle: {
|
|
3348
|
+
AUTOMATIC: 'automatic',
|
|
3349
|
+
FULL_SCREEN: 'fullScreen',
|
|
3350
|
+
PAGE_SHEET: 'pageSheet',
|
|
3351
|
+
FORM_SHEET: 'formSheet',
|
|
3352
|
+
CURRENT_CONTEXT: 'currentContext',
|
|
3353
|
+
OVER_FULL_SCREEN: 'overFullScreen',
|
|
3354
|
+
},
|
|
3355
|
+
async requestCameraPermissionsAsync() {
|
|
3356
|
+
return invokeNative(
|
|
3357
|
+
'requestCameraPermission',
|
|
3358
|
+
'requestCameraPermissionsAsync',
|
|
3359
|
+
);
|
|
3360
|
+
},
|
|
3361
|
+
async requestMediaLibraryPermissionsAsync(writeOnly) {
|
|
3362
|
+
return invokeNative(
|
|
3363
|
+
'requestMediaLibraryPermission',
|
|
3364
|
+
'requestMediaLibraryPermissionsAsync',
|
|
3365
|
+
writeOnly === true,
|
|
3366
|
+
);
|
|
3367
|
+
},
|
|
3368
|
+
async getCameraPermissionsAsync() {
|
|
3369
|
+
return invokeNative('getCameraPermissionStatus', 'getCameraPermissionsAsync');
|
|
3370
|
+
},
|
|
3371
|
+
async getMediaLibraryPermissionsAsync(writeOnly) {
|
|
3372
|
+
return invokeNative(
|
|
3373
|
+
'getMediaLibraryPermissionStatus',
|
|
3374
|
+
'getMediaLibraryPermissionsAsync',
|
|
3375
|
+
writeOnly === true,
|
|
3376
|
+
);
|
|
3377
|
+
},
|
|
3378
|
+
async launchCameraAsync(options) {
|
|
3379
|
+
return normalizePickerResult(
|
|
3380
|
+
await invokeNative('launchCamera', 'launchCameraAsync', options ?? {}),
|
|
3381
|
+
);
|
|
3382
|
+
},
|
|
3383
|
+
async launchImageLibraryAsync(options) {
|
|
3384
|
+
return normalizePickerResult(
|
|
3385
|
+
await invokeNative('launchImageLibrary', 'launchImageLibraryAsync', options ?? {}),
|
|
3386
|
+
);
|
|
3387
|
+
},
|
|
3388
|
+
async getPendingResultAsync() {
|
|
3389
|
+
const result = await invokeNative('getPendingResult', 'getPendingResultAsync');
|
|
3390
|
+
return result ? normalizePickerResult(result) : null;
|
|
3391
|
+
},
|
|
3392
|
+
};
|
|
3393
|
+
`;
|
|
3394
|
+
}
|
|
3395
|
+
function renderExpoLocationHarmonyAdapterShim(capability) {
|
|
3396
|
+
return `'use strict';
|
|
3397
|
+
|
|
3398
|
+
const { TurboModuleRegistry } = require('react-native');
|
|
3399
|
+
const { CodedError } = require('expo-modules-core');
|
|
3400
|
+
|
|
3401
|
+
const NATIVE_MODULE_NAME = 'ExpoHarmonyLocation';
|
|
3402
|
+
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
3403
|
+
|
|
3404
|
+
function createError(code, message) {
|
|
3405
|
+
return new CodedError(code, message);
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
function createUnsupportedError(operationName) {
|
|
3409
|
+
return createError(
|
|
3410
|
+
'ERR_EXPO_HARMONY_UNSUPPORTED',
|
|
3411
|
+
'${capability.packageName} does not implement ' + operationName + ' on HarmonyOS yet.',
|
|
3412
|
+
);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
function requireNativeModule(operationName) {
|
|
3416
|
+
if (NATIVE_MODULE) {
|
|
3417
|
+
return NATIVE_MODULE;
|
|
3418
|
+
}
|
|
3419
|
+
|
|
3420
|
+
throw createError(
|
|
3421
|
+
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
3422
|
+
'${capability.packageName} expected the ' +
|
|
3423
|
+
NATIVE_MODULE_NAME +
|
|
3424
|
+
' TurboModule to be registered, but it was missing while running ' +
|
|
3425
|
+
operationName +
|
|
3426
|
+
'.',
|
|
3427
|
+
);
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
function normalizeNativeError(error) {
|
|
3431
|
+
if (error instanceof Error) {
|
|
3432
|
+
return error;
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
if (error && typeof error === 'object') {
|
|
3436
|
+
const code =
|
|
3437
|
+
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3438
|
+
? String(error.code)
|
|
3439
|
+
: null;
|
|
3440
|
+
const message =
|
|
3441
|
+
typeof error.message === 'string' && error.message.length > 0
|
|
3442
|
+
? error.message
|
|
3443
|
+
: typeof error.name === 'string' && error.name.length > 0
|
|
3444
|
+
? error.name
|
|
3445
|
+
: JSON.stringify(error);
|
|
3446
|
+
|
|
3447
|
+
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
return new Error(String(error));
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
async function invokeNative(methodName, operationName, ...args) {
|
|
3454
|
+
try {
|
|
3455
|
+
return await requireNativeModule(operationName)[methodName](...args);
|
|
3456
|
+
} catch (error) {
|
|
3457
|
+
throw normalizeNativeError(error);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
function normalizePermissionResponse(permissionResponse) {
|
|
3462
|
+
return {
|
|
3463
|
+
status: permissionResponse?.status ?? 'undetermined',
|
|
3464
|
+
granted: permissionResponse?.granted === true,
|
|
3465
|
+
canAskAgain: permissionResponse?.canAskAgain !== false,
|
|
3466
|
+
expires: permissionResponse?.expires ?? 'never',
|
|
3467
|
+
android: permissionResponse?.android ?? { accuracy: 'none' },
|
|
3468
|
+
ios: permissionResponse?.ios ?? null,
|
|
3469
|
+
};
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
function normalizeLocationObject(location) {
|
|
3473
|
+
return {
|
|
3474
|
+
coords: {
|
|
3475
|
+
latitude: Number(location?.coords?.latitude ?? 0),
|
|
3476
|
+
longitude: Number(location?.coords?.longitude ?? 0),
|
|
3477
|
+
altitude:
|
|
3478
|
+
typeof location?.coords?.altitude === 'number' ? location.coords.altitude : null,
|
|
3479
|
+
accuracy:
|
|
3480
|
+
typeof location?.coords?.accuracy === 'number' ? location.coords.accuracy : null,
|
|
3481
|
+
altitudeAccuracy:
|
|
3482
|
+
typeof location?.coords?.altitudeAccuracy === 'number'
|
|
3483
|
+
? location.coords.altitudeAccuracy
|
|
3484
|
+
: null,
|
|
3485
|
+
heading: typeof location?.coords?.heading === 'number' ? location.coords.heading : null,
|
|
3486
|
+
speed: typeof location?.coords?.speed === 'number' ? location.coords.speed : null,
|
|
3487
|
+
},
|
|
3488
|
+
timestamp: Number(location?.timestamp ?? Date.now()),
|
|
3489
|
+
mocked: location?.mocked === true,
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
function normalizeProviderStatus(providerStatus) {
|
|
3494
|
+
return {
|
|
3495
|
+
locationServicesEnabled: providerStatus?.locationServicesEnabled === true,
|
|
3496
|
+
backgroundModeEnabled: providerStatus?.backgroundModeEnabled === true,
|
|
3497
|
+
gpsAvailable: providerStatus?.gpsAvailable === true,
|
|
3498
|
+
networkAvailable: providerStatus?.networkAvailable === true,
|
|
3499
|
+
passiveAvailable: providerStatus?.passiveAvailable === true,
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
function normalizeReverseGeocodeResult(address) {
|
|
3504
|
+
return {
|
|
3505
|
+
city: address?.city ?? null,
|
|
3506
|
+
district: address?.district ?? null,
|
|
3507
|
+
streetNumber: address?.streetNumber ?? null,
|
|
3508
|
+
street: address?.street ?? null,
|
|
3509
|
+
region: address?.region ?? null,
|
|
3510
|
+
subregion: address?.subregion ?? null,
|
|
3511
|
+
country: address?.country ?? null,
|
|
3512
|
+
postalCode: address?.postalCode ?? null,
|
|
3513
|
+
name: address?.name ?? null,
|
|
3514
|
+
isoCountryCode: address?.isoCountryCode ?? null,
|
|
3515
|
+
timezone: address?.timezone ?? null,
|
|
3516
|
+
formattedAddress: address?.formattedAddress ?? null,
|
|
3517
|
+
};
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
function normalizeGeocodeInput(address) {
|
|
3521
|
+
if (typeof address === 'string') {
|
|
3522
|
+
return address.trim();
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
if (!address || typeof address !== 'object') {
|
|
3526
|
+
return '';
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
const parts = [
|
|
3530
|
+
address.name,
|
|
3531
|
+
address.streetNumber,
|
|
3532
|
+
address.street,
|
|
3533
|
+
address.district,
|
|
3534
|
+
address.city,
|
|
3535
|
+
address.region,
|
|
3536
|
+
address.postalCode,
|
|
3537
|
+
address.country,
|
|
3538
|
+
]
|
|
3539
|
+
.filter((part) => typeof part === 'string' && part.trim().length > 0)
|
|
3540
|
+
.map((part) => part.trim());
|
|
3541
|
+
|
|
3542
|
+
return parts.join(', ');
|
|
3543
|
+
}
|
|
3544
|
+
|
|
3545
|
+
function normalizeGeocodeResults(results) {
|
|
3546
|
+
if (!Array.isArray(results)) {
|
|
3547
|
+
return [];
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
return results.map((result) => ({
|
|
3551
|
+
latitude: Number(result?.latitude ?? 0),
|
|
3552
|
+
longitude: Number(result?.longitude ?? 0),
|
|
3553
|
+
altitude: typeof result?.altitude === 'number' ? result.altitude : null,
|
|
3554
|
+
accuracy: typeof result?.accuracy === 'number' ? result.accuracy : null,
|
|
3555
|
+
}));
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
function normalizeHeadingObject(heading) {
|
|
3559
|
+
return {
|
|
3560
|
+
magHeading: Number(heading?.magHeading ?? 0),
|
|
3561
|
+
trueHeading:
|
|
3562
|
+
typeof heading?.trueHeading === 'number' && Number.isFinite(heading.trueHeading)
|
|
3563
|
+
? heading.trueHeading
|
|
3564
|
+
: null,
|
|
3565
|
+
accuracy:
|
|
3566
|
+
typeof heading?.accuracy === 'number' && Number.isFinite(heading.accuracy)
|
|
3567
|
+
? heading.accuracy
|
|
3568
|
+
: 0,
|
|
3569
|
+
};
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
function createSubscription(remove) {
|
|
3573
|
+
let active = true;
|
|
3574
|
+
|
|
3575
|
+
return {
|
|
3576
|
+
remove() {
|
|
3577
|
+
if (!active) {
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
active = false;
|
|
3582
|
+
remove();
|
|
3583
|
+
},
|
|
3584
|
+
};
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
module.exports = {
|
|
3588
|
+
Accuracy: {
|
|
3589
|
+
Lowest: 1,
|
|
3590
|
+
Low: 2,
|
|
3591
|
+
Balanced: 3,
|
|
3592
|
+
High: 4,
|
|
3593
|
+
Highest: 5,
|
|
3594
|
+
BestForNavigation: 6,
|
|
3595
|
+
},
|
|
3596
|
+
PermissionStatus: {
|
|
3597
|
+
DENIED: 'denied',
|
|
3598
|
+
GRANTED: 'granted',
|
|
3599
|
+
UNDETERMINED: 'undetermined',
|
|
3600
|
+
},
|
|
3601
|
+
async getForegroundPermissionsAsync() {
|
|
3602
|
+
return normalizePermissionResponse(
|
|
3603
|
+
await invokeNative(
|
|
3604
|
+
'getForegroundPermissionStatus',
|
|
3605
|
+
'getForegroundPermissionsAsync',
|
|
3606
|
+
),
|
|
3607
|
+
);
|
|
3608
|
+
},
|
|
3609
|
+
async requestForegroundPermissionsAsync() {
|
|
3610
|
+
return normalizePermissionResponse(
|
|
3611
|
+
await invokeNative(
|
|
3612
|
+
'requestForegroundPermission',
|
|
3613
|
+
'requestForegroundPermissionsAsync',
|
|
3614
|
+
),
|
|
3615
|
+
);
|
|
3616
|
+
},
|
|
3617
|
+
async getBackgroundPermissionsAsync() {
|
|
3618
|
+
return normalizePermissionResponse(
|
|
3619
|
+
await invokeNative(
|
|
3620
|
+
'getBackgroundPermissionStatus',
|
|
3621
|
+
'getBackgroundPermissionsAsync',
|
|
3622
|
+
),
|
|
3623
|
+
);
|
|
3624
|
+
},
|
|
3625
|
+
async requestBackgroundPermissionsAsync() {
|
|
3626
|
+
return normalizePermissionResponse(
|
|
3627
|
+
await invokeNative(
|
|
3628
|
+
'requestBackgroundPermission',
|
|
3629
|
+
'requestBackgroundPermissionsAsync',
|
|
3630
|
+
),
|
|
3631
|
+
);
|
|
3632
|
+
},
|
|
3633
|
+
async hasServicesEnabledAsync() {
|
|
3634
|
+
return await invokeNative('hasServicesEnabled', 'hasServicesEnabledAsync');
|
|
3635
|
+
},
|
|
3636
|
+
async getProviderStatusAsync() {
|
|
3637
|
+
return normalizeProviderStatus(
|
|
3638
|
+
await invokeNative('getProviderStatus', 'getProviderStatusAsync'),
|
|
3639
|
+
);
|
|
3640
|
+
},
|
|
3641
|
+
async getCurrentPositionAsync(options) {
|
|
3642
|
+
return normalizeLocationObject(
|
|
3643
|
+
await invokeNative('getCurrentPosition', 'getCurrentPositionAsync', options ?? {}),
|
|
3644
|
+
);
|
|
3645
|
+
},
|
|
3646
|
+
async getLastKnownPositionAsync(options) {
|
|
3647
|
+
const location = await invokeNative(
|
|
3648
|
+
'getLastKnownPosition',
|
|
3649
|
+
'getLastKnownPositionAsync',
|
|
3650
|
+
options ?? {},
|
|
3651
|
+
);
|
|
3652
|
+
return location ? normalizeLocationObject(location) : null;
|
|
3653
|
+
},
|
|
3654
|
+
async geocodeAsync(address) {
|
|
3655
|
+
return normalizeGeocodeResults(
|
|
3656
|
+
await invokeNative(
|
|
3657
|
+
'geocode',
|
|
3658
|
+
'geocodeAsync',
|
|
3659
|
+
normalizeGeocodeInput(address),
|
|
3660
|
+
),
|
|
3661
|
+
);
|
|
3662
|
+
},
|
|
3663
|
+
async reverseGeocodeAsync(location) {
|
|
3664
|
+
const results = await invokeNative(
|
|
3665
|
+
'reverseGeocode',
|
|
3666
|
+
'reverseGeocodeAsync',
|
|
3667
|
+
{
|
|
3668
|
+
latitude: Number(location?.latitude ?? 0),
|
|
3669
|
+
longitude: Number(location?.longitude ?? 0),
|
|
3670
|
+
},
|
|
3671
|
+
);
|
|
3672
|
+
|
|
3673
|
+
if (!Array.isArray(results)) {
|
|
3674
|
+
return [];
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
return results.map(normalizeReverseGeocodeResult);
|
|
3678
|
+
},
|
|
3679
|
+
async watchPositionAsync(options, callback, errorHandler) {
|
|
3680
|
+
if (typeof callback !== 'function') {
|
|
3681
|
+
throw createError(
|
|
3682
|
+
'ERR_EXPO_HARMONY_INVALID_LISTENER',
|
|
3683
|
+
'${capability.packageName} expected watchPositionAsync to receive a callback.',
|
|
3684
|
+
);
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
try {
|
|
3688
|
+
const watchResult = await invokeNative(
|
|
3689
|
+
'startWatchPosition',
|
|
3690
|
+
'watchPositionAsync',
|
|
3691
|
+
options ?? {},
|
|
3692
|
+
null,
|
|
3693
|
+
);
|
|
3694
|
+
const watchId =
|
|
3695
|
+
typeof watchResult?.watchId === 'number' ? watchResult.watchId : Number(Date.now());
|
|
3696
|
+
|
|
3697
|
+
if (watchResult?.initialLocation) {
|
|
3698
|
+
callback(normalizeLocationObject(watchResult.initialLocation));
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
return createSubscription(() => {
|
|
3702
|
+
void invokeNative(
|
|
3703
|
+
'stopWatchPosition',
|
|
3704
|
+
'Location.watchPositionAsync.remove',
|
|
3705
|
+
watchId,
|
|
3706
|
+
).catch(() => {});
|
|
3707
|
+
});
|
|
3708
|
+
} catch (error) {
|
|
3709
|
+
if (typeof errorHandler === 'function') {
|
|
3710
|
+
errorHandler(error);
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
throw error;
|
|
3714
|
+
}
|
|
3715
|
+
},
|
|
3716
|
+
async watchHeadingAsync(callback) {
|
|
3717
|
+
if (typeof callback !== 'function') {
|
|
3718
|
+
throw createError(
|
|
3719
|
+
'ERR_EXPO_HARMONY_INVALID_LISTENER',
|
|
3720
|
+
'${capability.packageName} expected watchHeadingAsync to receive a callback.',
|
|
3721
|
+
);
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
const watchResult = await invokeNative(
|
|
3725
|
+
'startWatchHeading',
|
|
3726
|
+
'watchHeadingAsync',
|
|
3727
|
+
null,
|
|
3728
|
+
);
|
|
3729
|
+
const watchId =
|
|
3730
|
+
typeof watchResult?.watchId === 'number' ? watchResult.watchId : Number(Date.now());
|
|
3731
|
+
|
|
3732
|
+
if (watchResult?.initialHeading) {
|
|
3733
|
+
callback(normalizeHeadingObject(watchResult.initialHeading));
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3736
|
+
return createSubscription(() => {
|
|
3737
|
+
void invokeNative(
|
|
3738
|
+
'stopWatchHeading',
|
|
3739
|
+
'Location.watchHeadingAsync.remove',
|
|
3740
|
+
watchId,
|
|
3741
|
+
).catch(() => {});
|
|
3742
|
+
});
|
|
3743
|
+
},
|
|
3744
|
+
async getHeadingAsync() {
|
|
3745
|
+
return normalizeHeadingObject(
|
|
3746
|
+
await invokeNative('getHeading', 'getHeadingAsync'),
|
|
3747
|
+
);
|
|
3748
|
+
},
|
|
3749
|
+
};
|
|
3750
|
+
`;
|
|
3751
|
+
}
|
|
3752
|
+
function renderExpoImagePickerPreviewShim(capability) {
|
|
3753
|
+
return `'use strict';
|
|
3754
|
+
|
|
3755
|
+
const { CodedError } = require('expo-modules-core');
|
|
3756
|
+
|
|
3757
|
+
const PREVIEW_MESSAGE =
|
|
3758
|
+
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, but picker and camera flows still need device-side validation.';
|
|
3759
|
+
|
|
3760
|
+
function createPreviewError(operationName) {
|
|
3761
|
+
return new CodedError(
|
|
3762
|
+
'ERR_EXPO_HARMONY_PREVIEW',
|
|
3763
|
+
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
3764
|
+
);
|
|
3765
|
+
}
|
|
3766
|
+
|
|
3767
|
+
async function unavailable(operationName) {
|
|
3768
|
+
throw createPreviewError(operationName);
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
module.exports = {
|
|
3772
|
+
MediaTypeOptions: {
|
|
3773
|
+
All: 'All',
|
|
3774
|
+
Images: 'Images',
|
|
3775
|
+
Videos: 'Videos',
|
|
3776
|
+
},
|
|
3777
|
+
UIImagePickerPresentationStyle: {
|
|
3778
|
+
AUTOMATIC: 'automatic',
|
|
3779
|
+
FULL_SCREEN: 'fullScreen',
|
|
3780
|
+
PAGE_SHEET: 'pageSheet',
|
|
3781
|
+
FORM_SHEET: 'formSheet',
|
|
3782
|
+
CURRENT_CONTEXT: 'currentContext',
|
|
3783
|
+
OVER_FULL_SCREEN: 'overFullScreen',
|
|
3784
|
+
},
|
|
3785
|
+
requestCameraPermissionsAsync() {
|
|
3786
|
+
return unavailable('requestCameraPermissionsAsync');
|
|
3787
|
+
},
|
|
3788
|
+
requestMediaLibraryPermissionsAsync() {
|
|
3789
|
+
return unavailable('requestMediaLibraryPermissionsAsync');
|
|
3790
|
+
},
|
|
3791
|
+
getCameraPermissionsAsync() {
|
|
3792
|
+
return unavailable('getCameraPermissionsAsync');
|
|
3793
|
+
},
|
|
3794
|
+
getMediaLibraryPermissionsAsync() {
|
|
3795
|
+
return unavailable('getMediaLibraryPermissionsAsync');
|
|
3796
|
+
},
|
|
3797
|
+
launchCameraAsync(options) {
|
|
3798
|
+
return unavailable('launchCameraAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3799
|
+
},
|
|
3800
|
+
launchImageLibraryAsync(options) {
|
|
3801
|
+
return unavailable('launchImageLibraryAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3802
|
+
},
|
|
3803
|
+
};
|
|
3804
|
+
`;
|
|
3805
|
+
}
|
|
3806
|
+
function renderExpoLocationPreviewShim(capability) {
|
|
3807
|
+
return `'use strict';
|
|
3808
|
+
|
|
3809
|
+
const { CodedError } = require('expo-modules-core');
|
|
3810
|
+
|
|
3811
|
+
const PREVIEW_MESSAGE =
|
|
3812
|
+
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, but foreground permission, current-position, and watch flows still need device-side validation.';
|
|
3813
|
+
const DEFAULT_PERMISSION_RESPONSE = {
|
|
3814
|
+
status: 'undetermined',
|
|
3815
|
+
granted: false,
|
|
3816
|
+
canAskAgain: true,
|
|
3817
|
+
expires: 'never',
|
|
3818
|
+
};
|
|
3819
|
+
|
|
3820
|
+
function createPreviewError(operationName) {
|
|
3821
|
+
return new CodedError(
|
|
3822
|
+
'ERR_EXPO_HARMONY_PREVIEW',
|
|
3823
|
+
PREVIEW_MESSAGE + ' Attempted operation: ' + operationName + '.',
|
|
3824
|
+
);
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
async function unavailable(operationName) {
|
|
3828
|
+
throw createPreviewError(operationName);
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
function getPermissionResponse() {
|
|
3832
|
+
return { ...DEFAULT_PERMISSION_RESPONSE };
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
module.exports = {
|
|
3836
|
+
Accuracy: {
|
|
3837
|
+
Lowest: 1,
|
|
3838
|
+
Low: 2,
|
|
3839
|
+
Balanced: 3,
|
|
3840
|
+
High: 4,
|
|
3841
|
+
Highest: 5,
|
|
3842
|
+
BestForNavigation: 6,
|
|
3843
|
+
},
|
|
3844
|
+
PermissionStatus: {
|
|
3845
|
+
DENIED: 'denied',
|
|
3846
|
+
GRANTED: 'granted',
|
|
3847
|
+
UNDETERMINED: 'undetermined',
|
|
3848
|
+
},
|
|
3849
|
+
requestForegroundPermissionsAsync() {
|
|
3850
|
+
return unavailable('requestForegroundPermissionsAsync');
|
|
3851
|
+
},
|
|
3852
|
+
getForegroundPermissionsAsync() {
|
|
3853
|
+
return Promise.resolve(getPermissionResponse());
|
|
3854
|
+
},
|
|
3855
|
+
requestBackgroundPermissionsAsync() {
|
|
3856
|
+
return unavailable('requestBackgroundPermissionsAsync');
|
|
3857
|
+
},
|
|
3858
|
+
getBackgroundPermissionsAsync() {
|
|
3859
|
+
return Promise.resolve(getPermissionResponse());
|
|
3860
|
+
},
|
|
3861
|
+
hasServicesEnabledAsync() {
|
|
3862
|
+
return Promise.resolve(false);
|
|
3863
|
+
},
|
|
3864
|
+
getCurrentPositionAsync(options) {
|
|
3865
|
+
return unavailable('getCurrentPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3866
|
+
},
|
|
3867
|
+
getLastKnownPositionAsync(options) {
|
|
3868
|
+
return unavailable('getLastKnownPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3869
|
+
},
|
|
3870
|
+
watchPositionAsync(options) {
|
|
3871
|
+
return unavailable('watchPositionAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
3872
|
+
},
|
|
3873
|
+
geocodeAsync(address) {
|
|
3874
|
+
return unavailable('geocodeAsync(' + JSON.stringify(address ?? null) + ')');
|
|
3875
|
+
},
|
|
3876
|
+
reverseGeocodeAsync(location) {
|
|
3877
|
+
return unavailable('reverseGeocodeAsync(' + JSON.stringify(location ?? null) + ')');
|
|
3878
|
+
},
|
|
3879
|
+
};
|
|
3880
|
+
`;
|
|
3881
|
+
}
|
|
3882
|
+
function renderExpoCameraHarmonyAdapterShim(capability) {
|
|
3883
|
+
return `'use strict';
|
|
3884
|
+
|
|
3885
|
+
const React = require('react');
|
|
3886
|
+
const { TurboModuleRegistry, requireNativeComponent } = require('react-native');
|
|
3887
|
+
const { CodedError } = require('expo-modules-core');
|
|
3888
|
+
|
|
3889
|
+
const NATIVE_MODULE_NAME = 'ExpoHarmonyCamera';
|
|
3890
|
+
const NATIVE_MODULE = TurboModuleRegistry.get(NATIVE_MODULE_NAME);
|
|
3891
|
+
const NativeCameraView = requireNativeComponent('ExpoHarmonyCameraView');
|
|
3892
|
+
const DEFAULT_PERMISSION_RESPONSE = {
|
|
3893
|
+
status: 'undetermined',
|
|
3894
|
+
granted: false,
|
|
3895
|
+
canAskAgain: true,
|
|
3896
|
+
expires: 'never',
|
|
3897
|
+
};
|
|
3898
|
+
let nextCameraViewId = 1;
|
|
3899
|
+
|
|
3900
|
+
function createError(code, message) {
|
|
3901
|
+
return new CodedError(code, message);
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
function createUnsupportedError(operationName) {
|
|
3905
|
+
return createError(
|
|
3906
|
+
'ERR_EXPO_HARMONY_UNSUPPORTED',
|
|
3907
|
+
'${capability.packageName} does not implement ' + operationName + ' on HarmonyOS yet.',
|
|
3908
|
+
);
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
function requireNativeModule(operationName) {
|
|
3912
|
+
if (NATIVE_MODULE) {
|
|
3913
|
+
return NATIVE_MODULE;
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
throw createError(
|
|
3917
|
+
'ERR_EXPO_HARMONY_NATIVE_MODULE_MISSING',
|
|
3918
|
+
'${capability.packageName} expected the ' +
|
|
3919
|
+
NATIVE_MODULE_NAME +
|
|
3920
|
+
' TurboModule to be registered, but it was missing while running ' +
|
|
3921
|
+
operationName +
|
|
3922
|
+
'.',
|
|
3923
|
+
);
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
function normalizeNativeError(error) {
|
|
3927
|
+
if (error instanceof Error) {
|
|
3928
|
+
return error;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
if (error && typeof error === 'object') {
|
|
3932
|
+
const code =
|
|
3933
|
+
typeof error.code === 'number' || typeof error.code === 'string'
|
|
3934
|
+
? String(error.code)
|
|
3935
|
+
: null;
|
|
3936
|
+
const message =
|
|
3937
|
+
typeof error.message === 'string' && error.message.length > 0
|
|
3938
|
+
? error.message
|
|
3939
|
+
: typeof error.name === 'string' && error.name.length > 0
|
|
3940
|
+
? error.name
|
|
3941
|
+
: JSON.stringify(error);
|
|
3942
|
+
|
|
3943
|
+
return new Error(code ? '[native:' + code + '] ' + message : message);
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
return new Error(String(error));
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
async function invokeNative(methodName, operationName, ...args) {
|
|
3950
|
+
try {
|
|
3951
|
+
return await requireNativeModule(operationName)[methodName](...args);
|
|
3952
|
+
} catch (error) {
|
|
3953
|
+
throw normalizeNativeError(error);
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
function normalizePermissionResponse(permissionResponse) {
|
|
3958
|
+
return {
|
|
3959
|
+
status: permissionResponse?.status ?? DEFAULT_PERMISSION_RESPONSE.status,
|
|
3960
|
+
granted: permissionResponse?.granted === true,
|
|
3961
|
+
canAskAgain: permissionResponse?.canAskAgain !== false,
|
|
3962
|
+
expires: permissionResponse?.expires ?? DEFAULT_PERMISSION_RESPONSE.expires,
|
|
3963
|
+
};
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
function normalizeCameraFacing(facing) {
|
|
3967
|
+
return facing === 'front' ? 'front' : 'back';
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
const CameraView = React.forwardRef(function ExpoHarmonyCameraView(props, ref) {
|
|
3971
|
+
const viewIdRef = React.useRef(null);
|
|
3972
|
+
|
|
3973
|
+
if (!viewIdRef.current) {
|
|
3974
|
+
viewIdRef.current = 'expo-harmony-camera-view-' + String(nextCameraViewId++);
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
const viewId = viewIdRef.current;
|
|
3978
|
+
|
|
3979
|
+
React.useEffect(() => {
|
|
3980
|
+
void invokeNative('createPreview', 'CameraView.mount', {
|
|
3981
|
+
viewId,
|
|
3982
|
+
facing: normalizeCameraFacing(props.facing),
|
|
3983
|
+
mode: props.mode ?? 'picture',
|
|
3984
|
+
}).catch(() => {});
|
|
3985
|
+
|
|
3986
|
+
return () => {
|
|
3987
|
+
void invokeNative('disposePreview', 'CameraView.unmount', {
|
|
3988
|
+
viewId,
|
|
3989
|
+
}).catch(() => {});
|
|
3990
|
+
};
|
|
3991
|
+
}, [props.facing, props.mode, viewId]);
|
|
3992
|
+
|
|
3993
|
+
React.useImperativeHandle(
|
|
3994
|
+
ref,
|
|
3995
|
+
() => ({
|
|
3996
|
+
async takePictureAsync(options) {
|
|
3997
|
+
return invokeNative('takePicture', 'CameraView.takePictureAsync', {
|
|
3998
|
+
viewId,
|
|
3999
|
+
cameraType: normalizeCameraFacing(props.facing),
|
|
4000
|
+
...options,
|
|
4001
|
+
});
|
|
4002
|
+
},
|
|
4003
|
+
async pausePreview() {
|
|
4004
|
+
return invokeNative('pausePreview', 'CameraView.pausePreview', {
|
|
4005
|
+
viewId,
|
|
4006
|
+
});
|
|
4007
|
+
},
|
|
4008
|
+
async resumePreview() {
|
|
4009
|
+
return invokeNative('resumePreview', 'CameraView.resumePreview', {
|
|
4010
|
+
viewId,
|
|
4011
|
+
});
|
|
4012
|
+
},
|
|
4013
|
+
async getAvailablePictureSizesAsync() {
|
|
4014
|
+
throw createUnsupportedError('CameraView.getAvailablePictureSizesAsync');
|
|
4015
|
+
},
|
|
4016
|
+
async getAvailableLensesAsync() {
|
|
4017
|
+
throw createUnsupportedError('CameraView.getAvailableLensesAsync');
|
|
4018
|
+
},
|
|
4019
|
+
async recordAsync(options) {
|
|
4020
|
+
return invokeNative('startRecording', 'CameraView.recordAsync', {
|
|
4021
|
+
viewId,
|
|
4022
|
+
cameraType: normalizeCameraFacing(props.facing),
|
|
4023
|
+
...options,
|
|
4024
|
+
});
|
|
4025
|
+
},
|
|
4026
|
+
async stopRecording() {
|
|
4027
|
+
return invokeNative('stopRecording', 'CameraView.stopRecording', {
|
|
4028
|
+
viewId,
|
|
4029
|
+
});
|
|
4030
|
+
},
|
|
4031
|
+
async toggleRecordingAsync(options) {
|
|
4032
|
+
return invokeNative('toggleRecording', 'CameraView.toggleRecordingAsync', {
|
|
4033
|
+
viewId,
|
|
4034
|
+
cameraType: normalizeCameraFacing(props.facing),
|
|
4035
|
+
...options,
|
|
4036
|
+
});
|
|
4037
|
+
},
|
|
4038
|
+
}),
|
|
4039
|
+
[props.facing, viewId],
|
|
4040
|
+
);
|
|
4041
|
+
|
|
4042
|
+
return React.createElement(NativeCameraView, {
|
|
4043
|
+
...props,
|
|
4044
|
+
viewId,
|
|
4045
|
+
facing: normalizeCameraFacing(props.facing),
|
|
4046
|
+
accessibilityLabel: props.accessibilityLabel ?? 'Expo Harmony embedded camera preview',
|
|
4047
|
+
style: [
|
|
4048
|
+
{
|
|
4049
|
+
minHeight: 220,
|
|
4050
|
+
overflow: 'hidden',
|
|
4051
|
+
backgroundColor: '#111827',
|
|
4052
|
+
},
|
|
4053
|
+
props.style,
|
|
4054
|
+
],
|
|
4055
|
+
});
|
|
4056
|
+
});
|
|
4057
|
+
|
|
4058
|
+
CameraView.displayName = 'ExpoHarmonyCameraView';
|
|
4059
|
+
|
|
4060
|
+
async function getCameraPermissionsAsync() {
|
|
4061
|
+
return normalizePermissionResponse(
|
|
4062
|
+
await invokeNative('getCameraPermissionStatus', 'getCameraPermissionsAsync'),
|
|
4063
|
+
);
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
async function requestCameraPermissionsAsync() {
|
|
4067
|
+
return normalizePermissionResponse(
|
|
4068
|
+
await invokeNative('requestCameraPermission', 'requestCameraPermissionsAsync'),
|
|
4069
|
+
);
|
|
4070
|
+
}
|
|
4071
|
+
|
|
4072
|
+
async function getMicrophonePermissionsAsync() {
|
|
4073
|
+
return normalizePermissionResponse(
|
|
4074
|
+
await invokeNative('getMicrophonePermissionStatus', 'getMicrophonePermissionsAsync'),
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
async function requestMicrophonePermissionsAsync() {
|
|
4079
|
+
return normalizePermissionResponse(
|
|
4080
|
+
await invokeNative('requestMicrophonePermission', 'requestMicrophonePermissionsAsync'),
|
|
4081
|
+
);
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
module.exports = {
|
|
4085
|
+
CameraType: {
|
|
4086
|
+
front: 'front',
|
|
4087
|
+
back: 'back',
|
|
4088
|
+
},
|
|
4089
|
+
FlashMode: {
|
|
4090
|
+
off: 'off',
|
|
4091
|
+
on: 'on',
|
|
4092
|
+
auto: 'auto',
|
|
4093
|
+
torch: 'torch',
|
|
4094
|
+
},
|
|
4095
|
+
CameraView,
|
|
4096
|
+
Camera: {
|
|
4097
|
+
CameraType: {
|
|
4098
|
+
front: 'front',
|
|
4099
|
+
back: 'back',
|
|
4100
|
+
},
|
|
4101
|
+
Constants: {
|
|
4102
|
+
Type: {
|
|
4103
|
+
front: 'front',
|
|
4104
|
+
back: 'back',
|
|
4105
|
+
},
|
|
4106
|
+
FlashMode: {
|
|
4107
|
+
off: 'off',
|
|
4108
|
+
on: 'on',
|
|
4109
|
+
auto: 'auto',
|
|
4110
|
+
torch: 'torch',
|
|
4111
|
+
},
|
|
4112
|
+
},
|
|
4113
|
+
getCameraPermissionsAsync,
|
|
4114
|
+
requestCameraPermissionsAsync,
|
|
4115
|
+
getMicrophonePermissionsAsync,
|
|
4116
|
+
requestMicrophonePermissionsAsync,
|
|
4117
|
+
},
|
|
4118
|
+
getCameraPermissionsAsync,
|
|
4119
|
+
requestCameraPermissionsAsync,
|
|
4120
|
+
getMicrophonePermissionsAsync,
|
|
4121
|
+
requestMicrophonePermissionsAsync,
|
|
4122
|
+
async scanFromURLAsync() {
|
|
4123
|
+
throw createUnsupportedError('scanFromURLAsync');
|
|
4124
|
+
},
|
|
4125
|
+
};
|
|
4126
|
+
`;
|
|
4127
|
+
}
|
|
4128
|
+
function renderExpoCameraPreviewShim(capability) {
|
|
4129
|
+
return `'use strict';
|
|
4130
|
+
|
|
4131
|
+
const React = require('react');
|
|
4132
|
+
const { Text, View } = require('react-native');
|
|
4133
|
+
const { CodedError } = require('expo-modules-core');
|
|
4134
|
+
|
|
4135
|
+
const PREVIEW_MESSAGE =
|
|
1159
4136
|
'${capability.packageName} is routed through the Expo Harmony ${capability.supportTier} bridge. Bundling is supported, and a managed preview surface can render, but device-side camera permission and capture flows still need validation.';
|
|
1160
4137
|
const DEFAULT_PERMISSION_RESPONSE = {
|
|
1161
4138
|
status: 'undetermined',
|
|
@@ -1184,6 +4161,12 @@ const CameraView = React.forwardRef(function ExpoHarmonyCameraPreview(props, ref
|
|
|
1184
4161
|
takePictureAsync(options) {
|
|
1185
4162
|
return unavailable('CameraView.takePictureAsync(' + JSON.stringify(options ?? {}) + ')');
|
|
1186
4163
|
},
|
|
4164
|
+
pausePreview() {
|
|
4165
|
+
return unavailable('CameraView.pausePreview()');
|
|
4166
|
+
},
|
|
4167
|
+
resumePreview() {
|
|
4168
|
+
return unavailable('CameraView.resumePreview()');
|
|
4169
|
+
},
|
|
1187
4170
|
}));
|
|
1188
4171
|
|
|
1189
4172
|
return React.createElement(
|
|
@@ -1246,6 +4229,23 @@ module.exports = {
|
|
|
1246
4229
|
getMicrophonePermissionsAsync() {
|
|
1247
4230
|
return Promise.resolve(getPermissionResponse());
|
|
1248
4231
|
},
|
|
4232
|
+
Camera: {
|
|
4233
|
+
getCameraPermissionsAsync() {
|
|
4234
|
+
return Promise.resolve(getPermissionResponse());
|
|
4235
|
+
},
|
|
4236
|
+
requestCameraPermissionsAsync() {
|
|
4237
|
+
return unavailable('Camera.requestCameraPermissionsAsync');
|
|
4238
|
+
},
|
|
4239
|
+
getMicrophonePermissionsAsync() {
|
|
4240
|
+
return Promise.resolve(getPermissionResponse());
|
|
4241
|
+
},
|
|
4242
|
+
requestMicrophonePermissionsAsync() {
|
|
4243
|
+
return unavailable('Camera.requestMicrophonePermissionsAsync');
|
|
4244
|
+
},
|
|
4245
|
+
},
|
|
4246
|
+
scanFromURLAsync() {
|
|
4247
|
+
return unavailable('scanFromURLAsync');
|
|
4248
|
+
},
|
|
1249
4249
|
};
|
|
1250
4250
|
`;
|
|
1251
4251
|
}
|