@lowdefy/shell 3.23.3 → 7.2.0
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/LICENSE +183 -183
- package/README.md +364 -2
- package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
- package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
- package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
- package/app/+native-intent.js +39 -0
- package/app/_layout.js +36 -0
- package/app.config.ts +183 -0
- package/appLinks.js +65 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +172 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native/LowdefyAppShortcuts.swift.template +84 -0
- package/native/withAppShortcuts.js +60 -0
- package/native-fingerprint.json +16 -0
- package/package.json +120 -40
- package/profiles.js +607 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +112 -0
- package/scripts/checkReleaseBundle.mjs +358 -0
- package/scripts/createDayjsLocales.mjs +67 -0
- package/scripts/createShellAssets.mjs +54 -0
- package/scripts/createShellVersion.mjs +83 -0
- package/scripts/createTypeManifest.mjs +205 -0
- package/shell.dev.json +11 -0
- package/shellVariant.js +126 -0
- package/src/ShellContext.js +32 -0
- package/src/actions/CopyToClipboard.js +29 -0
- package/src/actions/CopyToClipboard.test.js +33 -0
- package/src/actions/GeolocationCurrentPosition.js +61 -0
- package/src/actions/GeolocationCurrentPosition.test.js +79 -0
- package/src/actions/GetLocalStorage.js +36 -0
- package/src/actions/RemoveLocalStorage.js +31 -0
- package/src/actions/ScrollTo.js +66 -0
- package/src/actions/ScrollTo.test.js +83 -0
- package/src/actions/SetFocus.js +33 -0
- package/src/actions/SetFocus.test.js +49 -0
- package/src/actions/SetLocalStorage.js +34 -0
- package/src/actions/localStorage.test.js +68 -0
- package/src/actions/measureInScrollView.js +36 -0
- package/src/actions/nativeActions.js +41 -0
- package/src/auth/AuthButton.js +44 -0
- package/src/auth/AuthGate.js +57 -0
- package/src/auth/AuthLink.js +43 -0
- package/src/auth/LockScreen.js +67 -0
- package/src/auth/LockScreen.test.js +37 -0
- package/src/auth/SignInScreen.js +268 -0
- package/src/auth/SignInScreen.test.js +187 -0
- package/src/auth/createAppAuth.js +325 -0
- package/src/auth/createAppAuth.test.js +424 -0
- package/src/auth/createAuthApi.js +115 -0
- package/src/auth/createAuthApi.test.js +138 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -0
- package/src/auth/createDeviceKey.js +62 -0
- package/src/auth/createDeviceKey.test.js +54 -0
- package/src/auth/createSessionStores.js +45 -0
- package/src/auth/createTokenStore.js +103 -0
- package/src/auth/createTokenStore.test.js +116 -0
- package/src/auth/getAuthSettings.js +24 -0
- package/src/auth/getAuthView.js +39 -0
- package/src/auth/getAuthView.test.js +52 -0
- package/src/auth/getSheetReturnUrl.js +35 -0
- package/src/auth/getTokenStoreOptions.js +46 -0
- package/src/auth/isBackgroundShell.js +26 -0
- package/src/auth/isSheetReturnLink.js +35 -0
- package/src/auth/parseQuery.js +39 -0
- package/src/auth/purgeAppData.js +80 -0
- package/src/auth/purgeAppData.test.js +104 -0
- package/src/auth/shouldLock.js +26 -0
- package/src/auth/signInWithSheet.js +97 -0
- package/src/auth/signInWithSheet.test.js +205 -0
- package/src/auth/styles.js +78 -0
- package/src/auth/unregisterPushToken.js +62 -0
- package/src/auth/unregisterPushToken.test.js +67 -0
- package/src/auth/usesBiometricUnlock.js +23 -0
- package/src/auth/watchSession.js +47 -0
- package/src/auth/withoutCookies.js +26 -0
- package/src/cache/createSqliteCacheStore.js +113 -0
- package/src/cache/createSqliteCacheStore.test.js +93 -0
- package/src/cache/getCacheKey.js +25 -0
- package/src/cache/getLastCacheKeyName.js +22 -0
- package/src/components/AppProvider.js +118 -0
- package/src/components/Frame.js +116 -0
- package/src/components/Frame.test.js +101 -0
- package/src/components/LoadingScreen.js +35 -0
- package/src/components/MenuButton.js +121 -0
- package/src/components/MenuButton.test.js +74 -0
- package/src/components/MessageScreen.js +51 -0
- package/src/components/NavigationTheme.js +33 -0
- package/src/components/NoConnectionBanner.js +51 -0
- package/src/components/NoConnectionBanner.test.js +73 -0
- package/src/components/OtherPageScreen.js +95 -0
- package/src/components/OtherPageScreen.test.js +74 -0
- package/src/components/PageScreen.js +254 -0
- package/src/components/PageScreen.test.js +492 -0
- package/src/components/RouteErrorScreen.js +47 -0
- package/src/components/ShellRoot.js +34 -0
- package/src/components/ShellRoot.test.js +62 -0
- package/src/components/ShellThemeProvider.js +45 -0
- package/src/components/SwitchToTab.js +36 -0
- package/src/components/SwitchToTab.test.js +63 -0
- package/src/components/TabIconImages.js +72 -0
- package/src/components/UpdateScreen.js +40 -0
- package/src/components/getNavigationTheme.js +44 -0
- package/src/components/getNavigationTheme.test.js +41 -0
- package/src/components/styles.js +50 -0
- package/src/components/useSurfaceTokens.js +47 -0
- package/src/config/getAppApiBase.js +34 -0
- package/src/config/getShellConfig.js +37 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +126 -0
- package/src/dev/DevShell.js +239 -0
- package/src/dev/DevShell.test.js +233 -0
- package/src/dev/connect/ConfirmSheet.js +71 -0
- package/src/dev/connect/ConnectScreen.js +117 -0
- package/src/dev/connect/QrScanner.js +89 -0
- package/src/dev/connection/createDevShellConfig.js +47 -0
- package/src/dev/connection/createDevTransport.js +51 -0
- package/src/dev/connection/createDevTransport.test.js +87 -0
- package/src/dev/connection/createServerList.js +48 -0
- package/src/dev/connection/isLoopbackHost.js +25 -0
- package/src/dev/connection/pairedShellApp.test.js +104 -0
- package/src/dev/connection/parseDevLink.js +66 -0
- package/src/dev/connection/parseDevLink.test.js +97 -0
- package/src/dev/connection/parseQuery.js +33 -0
- package/src/dev/connection/parseServerInput.js +40 -0
- package/src/dev/connection/parseServerUrl.js +45 -0
- package/src/dev/devStyles.js +97 -0
- package/src/dev/index.js +21 -0
- package/src/dev/inject/createDeviceInjections.js +188 -0
- package/src/dev/inject/createDeviceInjections.test.js +224 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -0
- package/src/dev/inject/injectPush.js +60 -0
- package/src/dev/inject/injectShare.js +55 -0
- package/src/dev/inject/withInjectedDevice.js +98 -0
- package/src/dev/menu/BlockGallery.js +33 -0
- package/src/dev/menu/DevMenu.js +121 -0
- package/src/dev/menu/createLogBuffer.js +83 -0
- package/src/dev/menu/watchShake.js +52 -0
- package/src/dev/menu/watchShake.test.js +61 -0
- package/src/dev/recorder/createNativeRecorder.js +204 -0
- package/src/dev/recorder/createNativeRecorder.test.js +307 -0
- package/src/dev/redirectDevLink.js +30 -0
- package/src/dev/redirectDevLink.test.js +49 -0
- package/src/dev/steps/createStepRunner.js +184 -0
- package/src/dev/steps/createStepRunner.test.js +262 -0
- package/src/dev/steps/findBlock.js +78 -0
- package/src/dev/steps/isContextSettled.js +37 -0
- package/src/dev/steps/waitForSettle.js +39 -0
- package/src/dev/steps/waitUntil.js +40 -0
- package/src/dev/tab/DevTabClient.js +97 -0
- package/src/dev/tab/answerEval.js +39 -0
- package/src/dev/tab/answerInspect.js +45 -0
- package/src/dev/tab/createDevTabClient.js +272 -0
- package/src/dev/tab/createDevTabClient.test.js +317 -0
- package/src/dev/tab/describeTabError.js +35 -0
- package/src/dev/tab/devClientMarker.js +22 -0
- package/src/dev/tab/devClientMarker.test.js +23 -0
- package/src/dev/tab/findPageContext.js +43 -0
- package/src/dev/tab/getShownPage.js +42 -0
- package/src/dev/tab/readEventStream.js +68 -0
- package/src/dev/tab/reloadApp.js +42 -0
- package/src/dev/tab/reloadApp.test.js +115 -0
- package/src/device/DeviceHosts.js +32 -0
- package/src/device/basic/createFilesFeature.js +41 -0
- package/src/device/basic/createHapticsFeature.js +46 -0
- package/src/device/basic/createKeepAwakeFeature.js +37 -0
- package/src/device/basic/createPhotosFeature.js +40 -0
- package/src/device/basic/createRequestQueueFeature.js +37 -0
- package/src/device/basic/createShareFeature.js +63 -0
- package/src/device/basic/createShareFeature.test.js +47 -0
- package/src/device/basic/createSpeechFeature.js +43 -0
- package/src/device/basic/createUploadFeature.js +41 -0
- package/src/device/basic/toImagePickerOptions.js +41 -0
- package/src/device/basic/toPickedFile.js +54 -0
- package/src/device/calendar/index.js +68 -0
- package/src/device/contacts/index.js +67 -0
- package/src/device/createDevice.js +65 -0
- package/src/device/device.js +27 -0
- package/src/device/field/MapView.js +93 -0
- package/src/device/field/ScanCodeSheet.js +86 -0
- package/src/device/field/ScannerView.js +52 -0
- package/src/device/field/createAudioFeature.js +70 -0
- package/src/device/field/createBrightnessFeature.js +40 -0
- package/src/device/field/createCameraFeature.js +56 -0
- package/src/device/field/createDocumentScannerFeature.js +51 -0
- package/src/device/field/createLocationFeature.js +39 -0
- package/src/device/field/createScanRequests.js +55 -0
- package/src/device/field/index.js +39 -0
- package/src/device/field/scanRequests.js +22 -0
- package/src/device/injected.js +60 -0
- package/src/device/injected.test.js +39 -0
- package/src/device/nfc/index.js +89 -0
- package/src/device/nfc/toNfcRecord.js +44 -0
- package/src/device/nfc/toNfcRecord.test.js +48 -0
- package/src/device/tracking/createBacklog.js +63 -0
- package/src/device/tracking/createBacklog.test.js +47 -0
- package/src/device/tracking/index.js +211 -0
- package/src/device/tracking/index.test.js +110 -0
- package/src/host/createDeviceStore.js +32 -0
- package/src/host/createMemoryStore.js +32 -0
- package/src/host/createNetworkStatus.js +94 -0
- package/src/host/createNetworkStatus.test.js +94 -0
- package/src/host/createRouterAdapter.js +33 -0
- package/src/host/createShellFetch.js +62 -0
- package/src/host/createShellFetch.test.js +165 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +83 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +31 -0
- package/src/host/isNetworkError.js +24 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -0
- package/src/host/readServerError.js +45 -0
- package/src/host/readServerError.test.js +85 -0
- package/src/host/reportShellError.js +47 -0
- package/src/host/reportShellError.test.js +79 -0
- package/src/host/withNetworkErrors.js +38 -0
- package/src/host/withNetworkErrors.test.js +61 -0
- package/src/host/withShellHeader.js +25 -0
- package/src/host/withShellHeader.test.js +28 -0
- package/src/js/evaluateJsEntries.js +43 -0
- package/src/js/evaluateJsEntries.test.js +48 -0
- package/src/locale/createShellLocale.js +112 -0
- package/src/locale/createShellLocale.test.js +74 -0
- package/src/locale/dayjsLocales.js +165 -0
- package/src/locale/getDeviceLocales.js +23 -0
- package/src/locale/loadDayjsLocale.js +32 -0
- package/src/locale/resolveActiveLocale.js +46 -0
- package/src/locale/resolveActiveLocale.test.js +40 -0
- package/src/native/backgroundFetch.js +97 -0
- package/src/native/callEndpoint.js +63 -0
- package/src/native/createAppEventTrigger.js +70 -0
- package/src/native/currentApp.js +49 -0
- package/src/native/getPagePath.js +32 -0
- package/src/native/openPath.js +55 -0
- package/src/native/openPath.test.js +39 -0
- package/src/native/parseShellLink.js +38 -0
- package/src/native/parseShellLink.test.js +29 -0
- package/src/native/startAppServices.js +94 -0
- package/src/native/startAppServices.test.js +97 -0
- package/src/native/startShareIn.js +59 -0
- package/src/native/startShortcuts.js +88 -0
- package/src/native/startShortcuts.test.js +59 -0
- package/src/native/toSharePayload.js +47 -0
- package/src/native/toSharePayload.test.js +47 -0
- package/src/native/whenAppReady.js +48 -0
- package/src/navigation/SlotContext.js +24 -0
- package/src/navigation/frameSlots.js +27 -0
- package/src/navigation/getFrame.js +30 -0
- package/src/navigation/getInitialScreenOptions.js +63 -0
- package/src/navigation/getInitialScreenOptions.test.js +66 -0
- package/src/navigation/getPresentation.js +28 -0
- package/src/navigation/getSlotRootPath.js +27 -0
- package/src/navigation/getStackOptions.js +185 -0
- package/src/navigation/getTabHref.js +45 -0
- package/src/navigation/getTabIcons.js +43 -0
- package/src/navigation/isPageUnderHeader.js +46 -0
- package/src/navigation/navigation.test.js +342 -0
- package/src/navigation/toAppLinkPath.js +43 -0
- package/src/navigation/toAppLinkPath.test.js +53 -0
- package/src/navigation/toRouterHref.js +29 -0
- package/src/notifications/createNotificationsFeature.js +61 -0
- package/src/notifications/getCategoryAction.js +31 -0
- package/src/notifications/handleNotificationResponse.js +82 -0
- package/src/notifications/registerPushToken.js +71 -0
- package/src/notifications/startNotifications.js +114 -0
- package/src/notifications/toCategoryActions.js +42 -0
- package/src/notifications/toNotificationEvent.js +36 -0
- package/src/notifications/toScheduleRequest.js +62 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +15 -0
- package/src/plugins/typeManifest.js +789 -0
- package/src/plugins/types.js +64 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/queue/createRequestQueue.js +198 -0
- package/src/queue/createRequestQueue.test.js +215 -0
- package/src/queue/createSqliteQueueStore.js +121 -0
- package/src/queue/summarizeQueue.js +39 -0
- package/src/session/createAppSession.js +323 -0
- package/src/session/createAppSession.test.js +391 -0
- package/src/session/getPageView.js +59 -0
- package/src/session/getPageView.test.js +137 -0
- package/src/sync/encodePagePath.js +27 -0
- package/src/sync/fetchPage.js +72 -0
- package/src/sync/fetchPage.test.js +84 -0
- package/src/sync/fetchRoot.js +43 -0
- package/src/sync/fetchUser.js +33 -0
- package/src/sync/runLimited.js +36 -0
- package/src/sync/toRootConfig.js +35 -0
- package/src/test/createFakeNetwork.js +40 -0
- package/src/test/createFakeSecureStore.js +40 -0
- package/src/test/createFakeServer.js +98 -0
- package/src/test/createMemoryCacheStore.js +59 -0
- package/src/test/createNetworkError.js +24 -0
- package/src/test/expoImage.js +42 -0
- package/src/test/expoNotifications.js +40 -0
- package/src/test/expoQuickActions.js +22 -0
- package/src/test/expoSpeechRecognition.js +31 -0
- package/src/test/expoTaskManager.js +25 -0
- package/src/test/expoUi.js +55 -0
- package/src/test/expoUiBottomSheet.js +64 -0
- package/src/test/expoUiDateTimePicker.js +26 -0
- package/src/test/expoUiMenu.js +46 -0
- package/src/test/expoUiSegmentedControl.js +47 -0
- package/src/test/expoUiSwiftUi.js +62 -0
- package/src/test/expoUiSwiftUiModifiers.js +23 -0
- package/src/test/expoWebBrowser.js +23 -0
- package/src/test/fixtures.js +98 -0
- package/src/test/nativeModule.js +33 -0
- package/src/test/renderPageScreen.js +109 -0
- package/src/types/checkTypeSet.js +39 -0
- package/src/types/collectPageTypes.js +49 -0
- package/src/types/collectPageTypes.test.js +90 -0
- package/src/types/findMissingPageTypes.js +40 -0
- package/src/version/compareVersions.js +38 -0
- package/src/version/isShellTooOld.js +30 -0
- package/src/version/shellVersion.js +21 -0
- package/src/version/shellVersion.test.js +32 -0
- package/src/widgets/fetchWidgetData.js +59 -0
- package/src/widgets/index.js +156 -0
- package/src/widgets/refreshWidgets.js +31 -0
- package/withExcludedNativeModules.js +93 -0
- package/dist/location.js +0 -33
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/icon-32.png +0 -0
- package/dist/public/icon-512.png +0 -0
- package/dist/public/icon.svg +0 -17
- package/dist/public/logo-dark-theme.png +0 -0
- package/dist/public/logo-light-theme.png +0 -0
- package/dist/public/logo-square-dark-theme.png +0 -0
- package/dist/public/logo-square-light-theme.png +0 -0
- package/dist/public/manifest.webmanifest +0 -16
- package/dist/shell/246.93432ff68186aae6c87b.js +0 -2
- package/dist/shell/246.93432ff68186aae6c87b.js.LICENSE.txt +0 -8
- package/dist/shell/454.b597ca21366afda000af.js +0 -2
- package/dist/shell/454.b597ca21366afda000af.js.LICENSE.txt +0 -14
- package/dist/shell/484.d645e6c09079f45ce044.js +0 -1
- package/dist/shell/706.a66931e02a549fd269bd.js +0 -2
- package/dist/shell/706.a66931e02a549fd269bd.js.LICENSE.txt +0 -8
- package/dist/shell/9.78be8c1b6c3986f404fc.js +0 -2
- package/dist/shell/9.78be8c1b6c3986f404fc.js.LICENSE.txt +0 -45
- package/dist/shell/922.43b7a3ebe22ef05b3049.js +0 -2
- package/dist/shell/922.43b7a3ebe22ef05b3049.js.LICENSE.txt +0 -14
- package/dist/shell/index.html +0 -57
- package/dist/shell/main.f01501a75909f38ccc32.js +0 -1
- package/dist/shell/runtime.e50f6c3a5f7c74a0a717.js +0 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { getCalendars, listEvents, requestCalendarPermissions } from 'expo-calendar';
|
|
18
|
+
import { createEventInCalendarAsync } from 'expo-calendar/legacy';
|
|
19
|
+
import { UserError } from '@lowdefy/errors';
|
|
20
|
+
|
|
21
|
+
const HOUR = 60 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The calendar device feature (the calendar extension): AddCalendarEvent opens the system event
|
|
25
|
+
* editor filled in, for the person to save; GetCalendarEvents reads events between two dates from
|
|
26
|
+
* every calendar, after asking for calendar access.
|
|
27
|
+
*/
|
|
28
|
+
const calendar = {
|
|
29
|
+
async add({ allDay, end, location, notes, start, title, url }) {
|
|
30
|
+
const startDate = new Date(start);
|
|
31
|
+
// The system editor needs no calendar permission; SDK 58 keeps it under expo-calendar/legacy.
|
|
32
|
+
const result = await createEventInCalendarAsync({
|
|
33
|
+
allDay: allDay === true,
|
|
34
|
+
endDate: end ? new Date(end) : new Date(startDate.getTime() + HOUR),
|
|
35
|
+
location,
|
|
36
|
+
notes,
|
|
37
|
+
startDate,
|
|
38
|
+
title,
|
|
39
|
+
url,
|
|
40
|
+
});
|
|
41
|
+
return result.action === 'saved' || result.action === 'done' ? { id: result.id ?? null } : null;
|
|
42
|
+
},
|
|
43
|
+
async getEvents({ end, start }) {
|
|
44
|
+
const { granted } = await requestCalendarPermissions();
|
|
45
|
+
if (!granted) {
|
|
46
|
+
throw new UserError('Calendar permission was denied.');
|
|
47
|
+
}
|
|
48
|
+
const calendars = await getCalendars();
|
|
49
|
+
const byId = Object.fromEntries(calendars.map((item) => [item.id, item.title]));
|
|
50
|
+
const events = await listEvents(calendars, new Date(start), new Date(end));
|
|
51
|
+
return events.map((event) => ({
|
|
52
|
+
id: event.id,
|
|
53
|
+
title: event.title,
|
|
54
|
+
start: new Date(event.startDate).toISOString(),
|
|
55
|
+
end: new Date(event.endDate).toISOString(),
|
|
56
|
+
allDay: event.allDay === true,
|
|
57
|
+
location: event.location ?? null,
|
|
58
|
+
notes: event.notes ?? null,
|
|
59
|
+
calendar: byId[event.calendarId] ?? null,
|
|
60
|
+
}));
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// The calendar extension's device features (profiles.js CALENDAR_ENTRY).
|
|
65
|
+
const features = { calendar };
|
|
66
|
+
const hosts = [];
|
|
67
|
+
|
|
68
|
+
export { features, hosts };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Contact, ContactField } from 'expo-contacts';
|
|
18
|
+
|
|
19
|
+
const FIELDS = [
|
|
20
|
+
ContactField.FULL_NAME,
|
|
21
|
+
ContactField.GIVEN_NAME,
|
|
22
|
+
ContactField.FAMILY_NAME,
|
|
23
|
+
ContactField.COMPANY,
|
|
24
|
+
ContactField.EMAILS,
|
|
25
|
+
ContactField.PHONES,
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
async function toContact(contact) {
|
|
29
|
+
const details = await contact.getDetails(FIELDS);
|
|
30
|
+
return {
|
|
31
|
+
id: contact.id,
|
|
32
|
+
name: details.fullName ?? null,
|
|
33
|
+
firstName: details.givenName ?? null,
|
|
34
|
+
lastName: details.familyName ?? null,
|
|
35
|
+
company: details.company ?? null,
|
|
36
|
+
emails: (details.emails ?? []).map((entry) => entry.address),
|
|
37
|
+
phoneNumbers: (details.phones ?? []).map((entry) => entry.number),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The contacts device feature (the contacts extension): PickContact opens the system contact
|
|
43
|
+
* picker; AddContact opens the new-contact form filled in for the person to save. Returns
|
|
44
|
+
* { saved } since the form does not say which contact it made.
|
|
45
|
+
*/
|
|
46
|
+
const contacts = {
|
|
47
|
+
async pick() {
|
|
48
|
+
const contact = await Contact.presentPicker();
|
|
49
|
+
return contact === null ? null : toContact(contact);
|
|
50
|
+
},
|
|
51
|
+
async add({ company, emails = [], firstName, lastName, phoneNumbers = [] }) {
|
|
52
|
+
const saved = await Contact.presentCreateForm({
|
|
53
|
+
company,
|
|
54
|
+
emails: emails.map((address) => ({ address, label: 'work' })),
|
|
55
|
+
familyName: lastName,
|
|
56
|
+
givenName: firstName,
|
|
57
|
+
phones: phoneNumbers.map((number) => ({ number, label: 'mobile' })),
|
|
58
|
+
});
|
|
59
|
+
return saved ? { saved: true } : null;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// The contacts extension's device features (profiles.js CONTACTS_ENTRY).
|
|
64
|
+
const features = { contacts };
|
|
65
|
+
const hosts = [];
|
|
66
|
+
|
|
67
|
+
export { features, hosts };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as calendarEntry from './calendar/index.js';
|
|
18
|
+
import * as contactsEntry from './contacts/index.js';
|
|
19
|
+
import createFilesFeature from './basic/createFilesFeature.js';
|
|
20
|
+
import createHapticsFeature from './basic/createHapticsFeature.js';
|
|
21
|
+
import createKeepAwakeFeature from './basic/createKeepAwakeFeature.js';
|
|
22
|
+
import createNotificationsFeature from '../notifications/createNotificationsFeature.js';
|
|
23
|
+
import createPhotosFeature from './basic/createPhotosFeature.js';
|
|
24
|
+
import createRequestQueueFeature from './basic/createRequestQueueFeature.js';
|
|
25
|
+
import createShareFeature from './basic/createShareFeature.js';
|
|
26
|
+
import createSpeechFeature from './basic/createSpeechFeature.js';
|
|
27
|
+
import createUploadFeature from './basic/createUploadFeature.js';
|
|
28
|
+
import * as fieldEntry from './field/index.js';
|
|
29
|
+
import * as nfcEntry from './nfc/index.js';
|
|
30
|
+
import * as trackingEntry from './tracking/index.js';
|
|
31
|
+
import * as widgetsEntry from '../widgets/index.js';
|
|
32
|
+
|
|
33
|
+
// The feature modules of profiles and extensions (profiles.js jsEntries). A shell that does not
|
|
34
|
+
// carry one resolves it to an empty module (metro.config.js), which has no `features`.
|
|
35
|
+
const ENTRIES = [fieldEntry, nfcEntry, trackingEntry, contactsEntry, calendarEntry, widgetsEntry];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The device features this shell carries, for blocks-native's device registry (installDevice): the
|
|
39
|
+
* ones every shell has, and those of the profile and extensions it was built with. `hosts` are the
|
|
40
|
+
* views features open at the root, such as the ScanCode sheet (DeviceHosts).
|
|
41
|
+
*
|
|
42
|
+
* @returns {{ features: Object, hosts: Function[] }}
|
|
43
|
+
*/
|
|
44
|
+
function createDevice() {
|
|
45
|
+
const carried = ENTRIES.filter((entry) => entry.features !== undefined);
|
|
46
|
+
return {
|
|
47
|
+
features: Object.assign(
|
|
48
|
+
{
|
|
49
|
+
files: createFilesFeature(),
|
|
50
|
+
haptics: createHapticsFeature(),
|
|
51
|
+
keepAwake: createKeepAwakeFeature(),
|
|
52
|
+
notifications: createNotificationsFeature(),
|
|
53
|
+
photos: createPhotosFeature(),
|
|
54
|
+
requestQueue: createRequestQueueFeature(),
|
|
55
|
+
share: createShareFeature(),
|
|
56
|
+
speech: createSpeechFeature(),
|
|
57
|
+
upload: createUploadFeature(),
|
|
58
|
+
},
|
|
59
|
+
...carried.map((entry) => entry.features)
|
|
60
|
+
),
|
|
61
|
+
hosts: carried.flatMap((entry) => entry.hosts),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default createDevice;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { installDevice } from '@lowdefy/blocks-native/device';
|
|
18
|
+
|
|
19
|
+
import createDevice from './createDevice.js';
|
|
20
|
+
|
|
21
|
+
// The shell's one device: built and installed when the bundle loads, before any page runs an
|
|
22
|
+
// action, so background tasks launched with no screen find it too. Lowdefy Dev installs a wrapped
|
|
23
|
+
// copy that answers injected results first (src/dev/inject/withInjectedDevice.js).
|
|
24
|
+
const device = createDevice();
|
|
25
|
+
installDevice(device.features);
|
|
26
|
+
|
|
27
|
+
export default device;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import { Platform } from 'react-native';
|
|
19
|
+
import { AppleMaps, GoogleMaps } from 'expo-maps';
|
|
20
|
+
|
|
21
|
+
const APPLE_MAP_TYPES = { standard: 'STANDARD', satellite: 'IMAGERY', hybrid: 'HYBRID' };
|
|
22
|
+
const GOOGLE_MAP_TYPES = { standard: 'NORMAL', satellite: 'SATELLITE', hybrid: 'HYBRID' };
|
|
23
|
+
|
|
24
|
+
function toCoordinates({ latitude, longitude }) {
|
|
25
|
+
return { latitude, longitude };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The map the Map block draws (profile field): Apple Maps on iOS, Google Maps on Android
|
|
30
|
+
* (expo-maps), from the block's markers, lines and camera. Taps and camera moves come back in the
|
|
31
|
+
* block's terms.
|
|
32
|
+
*/
|
|
33
|
+
function MapView({
|
|
34
|
+
center,
|
|
35
|
+
mapType,
|
|
36
|
+
markerColor,
|
|
37
|
+
markers,
|
|
38
|
+
onMapClick,
|
|
39
|
+
onMarkerClick,
|
|
40
|
+
onRegionChange,
|
|
41
|
+
polylines,
|
|
42
|
+
showsUserLocation,
|
|
43
|
+
style,
|
|
44
|
+
zoom,
|
|
45
|
+
}) {
|
|
46
|
+
const cameraPosition = center === null ? undefined : { coordinates: toCoordinates(center), zoom };
|
|
47
|
+
const mapMarkers = markers.map((marker, index) => ({
|
|
48
|
+
id: marker.id ?? String(index),
|
|
49
|
+
coordinates: toCoordinates(marker),
|
|
50
|
+
title: marker.title,
|
|
51
|
+
snippet: marker.description,
|
|
52
|
+
tintColor: marker.color ?? markerColor,
|
|
53
|
+
}));
|
|
54
|
+
const lines = polylines.map((line, index) => ({
|
|
55
|
+
id: line.id ?? String(index),
|
|
56
|
+
coordinates: line.coordinates.map(toCoordinates),
|
|
57
|
+
color: line.color ?? markerColor,
|
|
58
|
+
width: line.width ?? 4,
|
|
59
|
+
}));
|
|
60
|
+
const events = {
|
|
61
|
+
onMapClick: ({ coordinates }) => onMapClick(coordinates),
|
|
62
|
+
onMarkerClick: (clicked) => {
|
|
63
|
+
const index = mapMarkers.findIndex((marker) => marker.id === clicked.id);
|
|
64
|
+
onMarkerClick(markers[index] ?? clicked);
|
|
65
|
+
},
|
|
66
|
+
onCameraMove: ({ coordinates, zoom: nextZoom }) =>
|
|
67
|
+
onRegionChange({ center: coordinates, zoom: nextZoom }),
|
|
68
|
+
};
|
|
69
|
+
if (Platform.OS === 'ios') {
|
|
70
|
+
return (
|
|
71
|
+
<AppleMaps.View
|
|
72
|
+
cameraPosition={cameraPosition}
|
|
73
|
+
markers={mapMarkers}
|
|
74
|
+
polylines={lines}
|
|
75
|
+
properties={{ isMyLocationEnabled: showsUserLocation, mapType: APPLE_MAP_TYPES[mapType] }}
|
|
76
|
+
style={style}
|
|
77
|
+
{...events}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return (
|
|
82
|
+
<GoogleMaps.View
|
|
83
|
+
cameraPosition={cameraPosition}
|
|
84
|
+
markers={mapMarkers}
|
|
85
|
+
polylines={lines}
|
|
86
|
+
properties={{ isMyLocationEnabled: showsUserLocation, mapType: GOOGLE_MAP_TYPES[mapType] }}
|
|
87
|
+
style={style}
|
|
88
|
+
{...events}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default MapView;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React, { useEffect, useState } from 'react';
|
|
18
|
+
import { Modal, Pressable, Text, View } from 'react-native';
|
|
19
|
+
|
|
20
|
+
import ScannerView from './ScannerView.js';
|
|
21
|
+
import scanRequests from './scanRequests.js';
|
|
22
|
+
|
|
23
|
+
// The full-screen camera the ScanCode action opens: the first code read answers it; Cancel answers
|
|
24
|
+
// null. Mounted once at the root (DeviceHosts).
|
|
25
|
+
function ScanCodeSheet() {
|
|
26
|
+
const [request, setRequest] = useState(scanRequests.get());
|
|
27
|
+
const [torch, setTorch] = useState(false);
|
|
28
|
+
useEffect(() => scanRequests.subscribe(setRequest), []);
|
|
29
|
+
useEffect(() => setTorch(request?.params.torch === true), [request]);
|
|
30
|
+
if (request === null) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return (
|
|
34
|
+
<Modal
|
|
35
|
+
animationType="slide"
|
|
36
|
+
onRequestClose={() => scanRequests.finish(null)}
|
|
37
|
+
presentationStyle="fullScreen"
|
|
38
|
+
visible
|
|
39
|
+
>
|
|
40
|
+
<View style={{ backgroundColor: '#000000', flex: 1 }}>
|
|
41
|
+
<ScannerView
|
|
42
|
+
onScan={(read) => scanRequests.finish(read)}
|
|
43
|
+
style={{ flex: 1 }}
|
|
44
|
+
torch={torch}
|
|
45
|
+
types={request.params.types}
|
|
46
|
+
/>
|
|
47
|
+
{request.params.prompt ? (
|
|
48
|
+
<Text
|
|
49
|
+
style={{
|
|
50
|
+
color: '#FFFFFF',
|
|
51
|
+
fontSize: 17,
|
|
52
|
+
left: 24,
|
|
53
|
+
position: 'absolute',
|
|
54
|
+
right: 24,
|
|
55
|
+
textAlign: 'center',
|
|
56
|
+
top: 80,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{request.params.prompt}
|
|
60
|
+
</Text>
|
|
61
|
+
) : null}
|
|
62
|
+
<View
|
|
63
|
+
style={{
|
|
64
|
+
bottom: 48,
|
|
65
|
+
flexDirection: 'row',
|
|
66
|
+
justifyContent: 'space-between',
|
|
67
|
+
left: 24,
|
|
68
|
+
position: 'absolute',
|
|
69
|
+
right: 24,
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<Pressable accessibilityRole="button" onPress={() => scanRequests.finish(null)}>
|
|
73
|
+
<Text style={{ color: '#FFFFFF', fontSize: 17 }}>Cancel</Text>
|
|
74
|
+
</Pressable>
|
|
75
|
+
<Pressable accessibilityRole="button" onPress={() => setTorch((on) => !on)}>
|
|
76
|
+
<Text style={{ color: '#FFFFFF', fontSize: 17 }}>
|
|
77
|
+
{torch ? 'Torch off' : 'Torch on'}
|
|
78
|
+
</Text>
|
|
79
|
+
</Pressable>
|
|
80
|
+
</View>
|
|
81
|
+
</View>
|
|
82
|
+
</Modal>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default ScanCodeSheet;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import React from 'react';
|
|
18
|
+
import { CameraView } from 'expo-camera';
|
|
19
|
+
|
|
20
|
+
const ALL_TYPES = [
|
|
21
|
+
'qr',
|
|
22
|
+
'ean13',
|
|
23
|
+
'ean8',
|
|
24
|
+
'code128',
|
|
25
|
+
'code39',
|
|
26
|
+
'code93',
|
|
27
|
+
'upc_a',
|
|
28
|
+
'upc_e',
|
|
29
|
+
'pdf417',
|
|
30
|
+
'datamatrix',
|
|
31
|
+
'aztec',
|
|
32
|
+
'itf14',
|
|
33
|
+
'codabar',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The live code scanner the QRScanner block and the ScanCode sheet draw: expo-camera's camera with
|
|
38
|
+
* barcode scanning (DataScanner on iOS, ML Kit on Android). Each read calls onScan({ data, type }).
|
|
39
|
+
*/
|
|
40
|
+
function ScannerView({ onScan, style, torch, types }) {
|
|
41
|
+
return (
|
|
42
|
+
<CameraView
|
|
43
|
+
barcodeScannerSettings={{ barcodeTypes: types ?? ALL_TYPES }}
|
|
44
|
+
enableTorch={torch === true}
|
|
45
|
+
facing="back"
|
|
46
|
+
onBarcodeScanned={({ data, type }) => onScan({ data, type })}
|
|
47
|
+
style={style}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default ScannerView;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
AudioModule,
|
|
19
|
+
RecordingPresets,
|
|
20
|
+
requestRecordingPermissionsAsync,
|
|
21
|
+
setAudioModeAsync,
|
|
22
|
+
} from 'expo-audio';
|
|
23
|
+
import { UserError } from '@lowdefy/errors';
|
|
24
|
+
|
|
25
|
+
import toPickedFile from '../basic/toPickedFile.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The audio device feature (profile field): one voice note at a time (StartRecording,
|
|
29
|
+
* StopRecording). Stop returns the recording as a file with its duration, or null when nothing was
|
|
30
|
+
* recording.
|
|
31
|
+
*/
|
|
32
|
+
function createAudioFeature() {
|
|
33
|
+
let recorder = null;
|
|
34
|
+
let startedAt = null;
|
|
35
|
+
return {
|
|
36
|
+
async start({ quality = 'high' } = {}) {
|
|
37
|
+
const { granted } = await requestRecordingPermissionsAsync();
|
|
38
|
+
if (!granted) {
|
|
39
|
+
throw new UserError('Microphone permission was denied.');
|
|
40
|
+
}
|
|
41
|
+
if (recorder !== null) {
|
|
42
|
+
await recorder.stop();
|
|
43
|
+
}
|
|
44
|
+
await setAudioModeAsync({ allowsRecording: true, playsInSilentMode: true });
|
|
45
|
+
recorder = new AudioModule.AudioRecorder(
|
|
46
|
+
quality === 'low' ? RecordingPresets.LOW_QUALITY : RecordingPresets.HIGH_QUALITY
|
|
47
|
+
);
|
|
48
|
+
await recorder.prepareToRecordAsync();
|
|
49
|
+
recorder.record();
|
|
50
|
+
startedAt = Date.now();
|
|
51
|
+
return { recording: true };
|
|
52
|
+
},
|
|
53
|
+
async stop({ base64 = false } = {}) {
|
|
54
|
+
if (recorder === null) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const current = recorder;
|
|
58
|
+
recorder = null;
|
|
59
|
+
await current.stop();
|
|
60
|
+
await setAudioModeAsync({ allowsRecording: false });
|
|
61
|
+
const file = await toPickedFile({
|
|
62
|
+
asset: { mimeType: 'audio/mp4', name: `voice-note-${startedAt}.m4a`, uri: current.uri },
|
|
63
|
+
base64,
|
|
64
|
+
});
|
|
65
|
+
return { ...file, duration: (Date.now() - startedAt) / 1000 };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default createAudioFeature;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as Brightness from 'expo-brightness';
|
|
18
|
+
import { type } from '@lowdefy/helpers';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The brightness device feature (profile field): SetBrightness sets the app's screen brightness,
|
|
22
|
+
* for a scan screen, and gives it back to the system with `reset`. The app's brightness ends when it
|
|
23
|
+
* goes to the background.
|
|
24
|
+
*/
|
|
25
|
+
function createBrightnessFeature() {
|
|
26
|
+
return {
|
|
27
|
+
async set({ level, reset }) {
|
|
28
|
+
if (reset === true) {
|
|
29
|
+
await Brightness.restoreSystemBrightnessAsync();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!type.isNumber(level)) {
|
|
33
|
+
throw new Error('SetBrightness requires "level" from 0 to 1, or "reset: true".');
|
|
34
|
+
}
|
|
35
|
+
await Brightness.setBrightnessAsync(Math.min(1, Math.max(0, level)));
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default createBrightnessFeature;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Camera } from 'expo-camera';
|
|
18
|
+
import * as ImagePicker from 'expo-image-picker';
|
|
19
|
+
import { UserError } from '@lowdefy/errors';
|
|
20
|
+
|
|
21
|
+
import scanRequests from './scanRequests.js';
|
|
22
|
+
import ScannerView from './ScannerView.js';
|
|
23
|
+
import toImagePickerOptions from '../basic/toImagePickerOptions.js';
|
|
24
|
+
import toPickedFile from '../basic/toPickedFile.js';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The camera device feature (profile field): ScanCode's sheet, TakePhoto's capture and the live
|
|
28
|
+
* ScannerView the QRScanner block draws. Each asks for the camera the first time; a refusal throws.
|
|
29
|
+
*/
|
|
30
|
+
function createCameraFeature() {
|
|
31
|
+
async function allowCamera() {
|
|
32
|
+
const { granted } = await Camera.requestCameraPermissionsAsync();
|
|
33
|
+
if (!granted) {
|
|
34
|
+
throw new UserError('Camera permission was denied.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
ScannerView,
|
|
39
|
+
async scanCode(params) {
|
|
40
|
+
await allowCamera();
|
|
41
|
+
return scanRequests.open(params);
|
|
42
|
+
},
|
|
43
|
+
async takePhoto(params) {
|
|
44
|
+
await allowCamera();
|
|
45
|
+
const result = await ImagePicker.launchCameraAsync(toImagePickerOptions(params));
|
|
46
|
+
if (result.canceled) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
return Promise.all(
|
|
50
|
+
result.assets.map((asset) => toPickedFile({ asset, base64: params.base64 === true }))
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default createCameraFeature;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import DocumentScanner, { ResponseType } from 'react-native-document-scanner-plugin';
|
|
18
|
+
|
|
19
|
+
import toPickedFile from '../basic/toPickedFile.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The documentScanner device feature (profile field): the system document scanner (VisionKit on
|
|
23
|
+
* iOS, ML Kit on Android) finds each page's edges, crops and straightens it. Pages come back as
|
|
24
|
+
* JPEG files; [] when cancelled.
|
|
25
|
+
*/
|
|
26
|
+
function createDocumentScannerFeature() {
|
|
27
|
+
return async function scanDocument({ base64 = false, maxPages, quality = 0.8 }) {
|
|
28
|
+
const { scannedImages, status } = await DocumentScanner.scanDocument({
|
|
29
|
+
croppedImageQuality: Math.round(quality * 100),
|
|
30
|
+
maxNumDocuments: maxPages,
|
|
31
|
+
responseType: ResponseType.ImageFilePath,
|
|
32
|
+
});
|
|
33
|
+
if (status === 'cancel' || !Array.isArray(scannedImages)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
return Promise.all(
|
|
37
|
+
scannedImages.map((uri, index) =>
|
|
38
|
+
toPickedFile({
|
|
39
|
+
asset: {
|
|
40
|
+
mimeType: 'image/jpeg',
|
|
41
|
+
name: `scan-${Date.now()}-${index + 1}.jpg`,
|
|
42
|
+
uri: uri.startsWith('file://') ? uri : `file://${uri}`,
|
|
43
|
+
},
|
|
44
|
+
base64,
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default createDocumentScannerFeature;
|