@octane-xplat/platform 0.3.0 → 0.5.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/README.md +37 -0
- package/package.json +11 -9
- package/src/a11y.native.ts +9 -5
- package/src/a11y.web.ts +13 -9
- package/src/app-info.native.ts +56 -0
- package/src/app-info.web.ts +10 -0
- package/src/biometrics.native.ts +10 -10
- package/src/biometrics.web.ts +9 -17
- package/src/clipboard.native.ts +8 -8
- package/src/clipboard.web.ts +8 -7
- package/src/connectivity.native.ts +86 -0
- package/src/connectivity.web.ts +62 -0
- package/src/deep-links.native.ts +47 -20
- package/src/deep-links.web.ts +16 -11
- package/src/device.native.ts +3 -3
- package/src/device.web.ts +5 -4
- package/src/files.native.ts +118 -18
- package/src/files.web.ts +20 -19
- package/src/geolocation.native.ts +44 -0
- package/src/geolocation.web.ts +50 -0
- package/src/haptics.native.ts +14 -10
- package/src/haptics.web.ts +5 -4
- package/src/index.ts +35 -19
- package/src/lifecycle.native.ts +1 -1
- package/src/lifecycle.native.tsrx +2 -0
- package/src/lifecycle.web.ts +1 -1
- package/src/lifecycle.web.tsrx +1 -0
- package/src/locale.native.ts +3 -3
- package/src/locale.web.ts +5 -9
- package/src/media.native.ts +70 -32
- package/src/media.web.ts +64 -22
- package/src/notifications.native.ts +7 -7
- package/src/notifications.web.ts +18 -7
- package/src/open-url.native.ts +45 -0
- package/src/open-url.web.ts +15 -0
- package/src/permissions.native.ts +10 -10
- package/src/permissions.web.ts +13 -31
- package/src/safe-area.native.ts +1 -1
- package/src/safe-area.native.tsrx +76 -17
- package/src/safe-area.web.ts +1 -1
- package/src/safe-area.web.tsrx +3 -0
- package/src/screen.native.ts +1 -1
- package/src/screen.native.tsrx +1 -0
- package/src/screen.web.ts +1 -1
- package/src/screen.web.tsrx +1 -0
- package/src/secure-storage.native.ts +5 -5
- package/src/secure-storage.web.ts +2 -2
- package/src/share.native.ts +6 -6
- package/src/share.web.ts +17 -13
- package/src/storage.native.ts +8 -4
- package/src/storage.web.ts +7 -3
- package/src/system-bars.native.ts +23 -12
- package/src/system-bars.web.ts +9 -6
- package/src/types.ts +93 -33
package/src/media.web.ts
CHANGED
|
@@ -1,30 +1,72 @@
|
|
|
1
1
|
// Media picking — web leaf. Keep an object URL for previews and a data URL for
|
|
2
|
-
// APIs that persist image payloads.
|
|
3
|
-
import type { PickedImage } from './types'
|
|
4
|
-
import { files } from './files';
|
|
2
|
+
// APIs that persist image payloads. The file input supports multiple photos.
|
|
3
|
+
import type { MediaImpl, MediaPermissionKind, PermissionResult, PickedImage } from './types'
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
function selectFiles(multiple: boolean): Promise<File[]> {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const input = document.createElement('input')
|
|
8
|
+
input.type = 'file'
|
|
9
|
+
input.accept = 'image/*'
|
|
10
|
+
input.multiple = multiple
|
|
11
|
+
input.onchange = () => resolve(Array.from(input.files ?? []))
|
|
12
|
+
input.oncancel = () => resolve([])
|
|
13
|
+
input.click()
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readDataUrl(file: File): Promise<string> {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const reader = new FileReader()
|
|
20
|
+
reader.onload = () => {
|
|
21
|
+
if (typeof reader.result === 'string') {
|
|
22
|
+
resolve(reader.result)
|
|
23
|
+
} else {
|
|
24
|
+
reject(new Error('Could not read the selected image'))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
reader.onerror = () => reject(reader.error ?? new Error('Could not read the selected image'))
|
|
29
|
+
|
|
30
|
+
reader.readAsDataURL(file)
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function pickFiles(multiple: boolean): Promise<PickedImage[]> {
|
|
35
|
+
const selected = await selectFiles(multiple)
|
|
36
|
+
return Promise.all(
|
|
37
|
+
selected.map(async (file) => ({
|
|
38
|
+
name: file.name,
|
|
39
|
+
uri: URL.createObjectURL(file),
|
|
40
|
+
dataUrl: await readDataUrl(file),
|
|
41
|
+
})),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const media: MediaImpl = {
|
|
7
46
|
/** Opens the browser image picker; returns null when selection is canceled. */
|
|
8
47
|
async pickImage(): Promise<PickedImage | null> {
|
|
9
|
-
const
|
|
10
|
-
|
|
48
|
+
const [image] = await pickFiles(false)
|
|
49
|
+
return image ?? null
|
|
50
|
+
},
|
|
51
|
+
/** Opens the browser image picker with multiple selection enabled. */
|
|
52
|
+
pickImages() {
|
|
53
|
+
return pickFiles(true)
|
|
54
|
+
},
|
|
55
|
+
async ensure(kind: MediaPermissionKind): Promise<PermissionResult> {
|
|
56
|
+
if (kind === 'photos') {
|
|
57
|
+
return typeof document !== 'undefined' ? 'granted' : 'unsupported'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!navigator.mediaDevices?.getUserMedia) {
|
|
61
|
+
return 'unsupported'
|
|
62
|
+
}
|
|
11
63
|
|
|
12
64
|
try {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
else reject(new Error('Could not read the selected image'));
|
|
19
|
-
};
|
|
20
|
-
reader.onerror = () =>
|
|
21
|
-
reject(reader.error ?? new Error('Could not read the selected image'));
|
|
22
|
-
reader.readAsDataURL(blob);
|
|
23
|
-
});
|
|
24
|
-
return { ...file, dataUrl };
|
|
25
|
-
} catch (error) {
|
|
26
|
-
files.release(file);
|
|
27
|
-
throw error;
|
|
65
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true })
|
|
66
|
+
stream.getTracks().forEach((track) => track.stop())
|
|
67
|
+
return 'granted'
|
|
68
|
+
} catch {
|
|
69
|
+
return 'denied'
|
|
28
70
|
}
|
|
29
71
|
},
|
|
30
|
-
}
|
|
72
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// Notifications — local notifications via plugin; push (APNs/FCM) is an
|
|
2
2
|
// app-level concern, not this seam.
|
|
3
|
-
import { LocalNotifications } from '@nativescript/local-notifications'
|
|
4
|
-
import type { Capability } from './types'
|
|
5
|
-
import type { NotificationsImpl } from './types'
|
|
3
|
+
import { LocalNotifications } from '@nativescript/local-notifications'
|
|
4
|
+
import type { Capability } from './types'
|
|
5
|
+
import type { NotificationsImpl } from './types'
|
|
6
6
|
|
|
7
7
|
export const notifications: Capability<NotificationsImpl> = {
|
|
8
8
|
supported: true,
|
|
9
9
|
async ensure() {
|
|
10
|
-
const granted = await LocalNotifications.requestPermission()
|
|
11
|
-
return granted ? 'granted' : 'denied'
|
|
10
|
+
const granted = await LocalNotifications.requestPermission()
|
|
11
|
+
return granted ? 'granted' : 'denied'
|
|
12
12
|
},
|
|
13
13
|
impl: {
|
|
14
14
|
notify(title, body) {
|
|
15
|
-
LocalNotifications.schedule([{ id: Date.now() % 2147483647, title, body: body ?? '' }])
|
|
15
|
+
LocalNotifications.schedule([{ id: Date.now() % 2147483647, title, body: body ?? '' }])
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
|
-
}
|
|
18
|
+
}
|
package/src/notifications.web.ts
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
1
|
// Notifications — Web Notification API. ensure() maps permission state;
|
|
2
2
|
// notify() posts an immediate local notification (no push).
|
|
3
|
-
import type { Capability, NotificationsImpl } from './types'
|
|
3
|
+
import type { Capability, NotificationsImpl } from './types'
|
|
4
4
|
|
|
5
5
|
export const notifications: Capability<NotificationsImpl> = {
|
|
6
6
|
supported: typeof Notification !== 'undefined',
|
|
7
7
|
async ensure() {
|
|
8
|
-
if (typeof Notification === 'undefined')
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
if (typeof Notification === 'undefined') {
|
|
9
|
+
return 'unsupported'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (Notification.permission === 'granted') {
|
|
13
|
+
return 'granted'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (Notification.permission === 'denied') {
|
|
17
|
+
return 'denied'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (await Notification.requestPermission()) === 'granted' ? 'granted' : 'denied'
|
|
12
21
|
},
|
|
13
22
|
impl: {
|
|
14
23
|
notify(title, body) {
|
|
15
|
-
if (Notification.permission === 'granted')
|
|
24
|
+
if (Notification.permission === 'granted') {
|
|
25
|
+
new Notification(title, { body })
|
|
26
|
+
}
|
|
16
27
|
},
|
|
17
28
|
},
|
|
18
|
-
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Outbound links and app settings — native leaf. Settings use the public
|
|
2
|
+
// per-app settings route on each platform, not private iOS prefs URLs.
|
|
3
|
+
import { Application, Utils } from '@nativescript/core'
|
|
4
|
+
import type { Capability, OpenSettingsImpl } from './types'
|
|
5
|
+
|
|
6
|
+
export function openUrl(url: string): boolean {
|
|
7
|
+
return Utils.openUrl(url)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function openSettingsPage(): boolean {
|
|
11
|
+
try {
|
|
12
|
+
if (Application.ios) {
|
|
13
|
+
const settingsUrl = (globalThis as any).UIApplicationOpenSettingsURLString ?? 'app-settings:'
|
|
14
|
+
return Utils.openUrl(settingsUrl)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (Application.android) {
|
|
18
|
+
const activity = Utils.android.getCurrentActivity()
|
|
19
|
+
|
|
20
|
+
if (!activity) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const intent = new android.content.Intent(
|
|
25
|
+
(android.provider.Settings as any).ACTION_APPLICATION_DETAILS_SETTINGS,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
intent.setData(android.net.Uri.parse(`package:${Utils.android.getPackageName()}`))
|
|
29
|
+
activity.startActivity(intent)
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const openSettings: Capability<OpenSettingsImpl> = {
|
|
40
|
+
supported: true,
|
|
41
|
+
async ensure() {
|
|
42
|
+
return 'granted'
|
|
43
|
+
},
|
|
44
|
+
impl: { open: openSettingsPage },
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Outbound links — web leaf. Settings are an app-level concept and are
|
|
2
|
+
// explicitly unsupported in a browser.
|
|
3
|
+
import type { Capability, OpenSettingsImpl } from './types'
|
|
4
|
+
|
|
5
|
+
export function openUrl(url: string): boolean {
|
|
6
|
+
return window.open(url, '_blank', 'noopener,noreferrer') !== null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const openSettings: Capability<OpenSettingsImpl> = {
|
|
10
|
+
supported: false,
|
|
11
|
+
async ensure() {
|
|
12
|
+
return 'unsupported'
|
|
13
|
+
},
|
|
14
|
+
impl: null,
|
|
15
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
// Runtime permissions — native leaf.
|
|
2
|
-
//
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
// Runtime permissions — native leaf. Delegate each kind to the service that
|
|
2
|
+
// owns its plugin request, so this generic seam cannot drift from reality.
|
|
3
|
+
import { geolocation } from './geolocation'
|
|
4
|
+
import { media } from './media'
|
|
5
|
+
import { notifications } from './notifications'
|
|
6
|
+
import type { PermissionKind } from './types'
|
|
5
7
|
|
|
6
8
|
export const permissions = {
|
|
7
9
|
async ensure(kind: PermissionKind): Promise<'granted' | 'denied' | 'unsupported'> {
|
|
8
10
|
switch (kind) {
|
|
9
11
|
case 'notifications':
|
|
10
|
-
return
|
|
11
|
-
// camera/photos/location have dedicated plugins — the owning
|
|
12
|
-
// service leaf (media, geolocation) owns its own ensure(); this
|
|
13
|
-
// generic seam reports them unsupported until wired.
|
|
12
|
+
return notifications.ensure()
|
|
14
13
|
case 'camera':
|
|
15
14
|
case 'photos':
|
|
15
|
+
return media.ensure(kind)
|
|
16
16
|
case 'location':
|
|
17
|
-
return
|
|
17
|
+
return geolocation.ensure()
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
-
}
|
|
20
|
+
}
|
package/src/permissions.web.ts
CHANGED
|
@@ -1,38 +1,20 @@
|
|
|
1
|
-
// Runtime permissions — web leaf.
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// Runtime permissions — web leaf. Each kind delegates to its owning service;
|
|
2
|
+
// browser feature detection and prompts therefore stay at the platform edge.
|
|
3
|
+
import { geolocation } from './geolocation'
|
|
4
|
+
import { media } from './media'
|
|
5
|
+
import { notifications } from './notifications'
|
|
6
|
+
import type { PermissionKind } from './types'
|
|
5
7
|
|
|
6
8
|
export const permissions = {
|
|
7
9
|
async ensure(kind: PermissionKind): Promise<'granted' | 'denied' | 'unsupported'> {
|
|
8
10
|
switch (kind) {
|
|
9
|
-
case 'notifications':
|
|
10
|
-
|
|
11
|
-
if (Notification.permission === 'granted') return 'granted';
|
|
12
|
-
if (Notification.permission === 'denied') return 'denied';
|
|
13
|
-
return (await Notification.requestPermission()) === 'granted' ? 'granted' : 'denied';
|
|
14
|
-
}
|
|
11
|
+
case 'notifications':
|
|
12
|
+
return notifications.ensure()
|
|
15
13
|
case 'camera':
|
|
16
|
-
case 'photos':
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
s.getTracks().forEach((t) => t.stop());
|
|
21
|
-
return 'granted';
|
|
22
|
-
} catch {
|
|
23
|
-
return 'denied';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
case 'location': {
|
|
27
|
-
if (!('geolocation' in navigator)) return 'unsupported';
|
|
28
|
-
return new Promise((resolve) =>
|
|
29
|
-
navigator.geolocation.getCurrentPosition(
|
|
30
|
-
() => resolve('granted'),
|
|
31
|
-
(e) => resolve(e.code === e.PERMISSION_DENIED ? 'denied' : 'unsupported'),
|
|
32
|
-
{ timeout: 15000 },
|
|
33
|
-
),
|
|
34
|
-
);
|
|
35
|
-
}
|
|
14
|
+
case 'photos':
|
|
15
|
+
return media.ensure(kind)
|
|
16
|
+
case 'location':
|
|
17
|
+
return geolocation.ensure()
|
|
36
18
|
}
|
|
37
19
|
},
|
|
38
|
-
}
|
|
20
|
+
}
|
package/src/safe-area.native.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useSafeAreaInsets } from './safe-area.native.tsrx'
|
|
1
|
+
export { useSafeAreaInsets } from './safe-area.native.tsrx'
|
|
@@ -1,27 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
import { Application } from '@nativescript/core'
|
|
1
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
2
|
+
// Safe-area insets — native leaf. iOS reads UIView.safeAreaInsets; Android
|
|
3
|
+
// reads the foreground activity's root WindowInsets in device-independent px.
|
|
4
|
+
import { useEffect, useState } from 'octane'
|
|
5
|
+
import { Application, Screen } from '@nativescript/core'
|
|
6
|
+
import type { Insets } from './types'
|
|
6
7
|
|
|
8
|
+
const zeroInsets = (): Insets => ({ top: 0, bottom: 0, left: 0, right: 0 })
|
|
9
|
+
|
|
10
|
+
function androidActivity(): any {
|
|
11
|
+
return Application.android?.foregroundActivity ?? Application.android?.startActivity
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readAndroidInsets(windowInsets?: any): Insets {
|
|
15
|
+
const activity = androidActivity()
|
|
16
|
+
const decorView = activity?.getWindow?.()?.getDecorView?.()
|
|
17
|
+
const insets = windowInsets ?? decorView?.getRootWindowInsets?.()
|
|
18
|
+
if (!insets) return zeroInsets()
|
|
19
|
+
|
|
20
|
+
const scale = Screen.mainScreen.scale || 1
|
|
21
|
+
if (android.os.Build.VERSION.SDK_INT >= 30) {
|
|
22
|
+
const systemBars =
|
|
23
|
+
android.view.WindowInsets.Type.systemBars() | android.view.WindowInsets.Type.displayCutout()
|
|
24
|
+
|
|
25
|
+
const i = insets.getInsets(systemBars)
|
|
26
|
+
return {
|
|
27
|
+
top: i.top / scale,
|
|
28
|
+
bottom: i.bottom / scale,
|
|
29
|
+
left: i.left / scale,
|
|
30
|
+
right: i.right / scale,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
top: insets.getSystemWindowInsetTop() / scale,
|
|
36
|
+
bottom: insets.getSystemWindowInsetBottom() / scale,
|
|
37
|
+
left: insets.getSystemWindowInsetLeft() / scale,
|
|
38
|
+
right: insets.getSystemWindowInsetRight() / scale,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
7
41
|
|
|
8
42
|
function readInsets(): Insets {
|
|
9
43
|
if (Application.ios) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
if (i) return { top: i.top, bottom: i.bottom, left: i.left, right: i.right };
|
|
44
|
+
const i = Application.ios.rootController?.view?.safeAreaInsets
|
|
45
|
+
if (i) return { top: i.top, bottom: i.bottom, left: i.left, right: i.right }
|
|
13
46
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return
|
|
47
|
+
|
|
48
|
+
if (Application.android) return readAndroidInsets()
|
|
49
|
+
return zeroInsets()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function watchAndroidInsets(update: (insets: Insets) => void): () => void {
|
|
53
|
+
const decorView = androidActivity()?.getWindow?.()?.getDecorView?.()
|
|
54
|
+
if (!decorView) return () => {}
|
|
55
|
+
|
|
56
|
+
const listener = new android.view.View.OnApplyWindowInsetsListener({
|
|
57
|
+
onApplyWindowInsets(_view: any, insets: any) {
|
|
58
|
+
update(readAndroidInsets(insets))
|
|
59
|
+
return insets
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
decorView.setOnApplyWindowInsetsListener(listener)
|
|
64
|
+
decorView.requestApplyInsets?.()
|
|
65
|
+
|
|
66
|
+
return () => decorView.setOnApplyWindowInsetsListener(null)
|
|
17
67
|
}
|
|
18
68
|
|
|
19
69
|
export function useSafeAreaInsets(): Insets {
|
|
20
|
-
const [insets, setInsets] = useState<Insets>(readInsets)
|
|
70
|
+
const [insets, setInsets] = useState<Insets>(readInsets)
|
|
21
71
|
useEffect(() => {
|
|
22
|
-
const update = () => setInsets(readInsets())
|
|
23
|
-
Application.on(Application.orientationChangedEvent, update)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
72
|
+
const update = () => setInsets(readInsets())
|
|
73
|
+
Application.on(Application.orientationChangedEvent, update)
|
|
74
|
+
Application.on(Application.resumeEvent, update)
|
|
75
|
+
const stopWatchingAndroid = Application.android ? watchAndroidInsets(setInsets) : () => {}
|
|
76
|
+
update()
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
Application.off(Application.orientationChangedEvent, update)
|
|
80
|
+
Application.off(Application.resumeEvent, update)
|
|
81
|
+
stopWatchingAndroid()
|
|
82
|
+
}
|
|
83
|
+
}, [])
|
|
84
|
+
|
|
85
|
+
return insets
|
|
27
86
|
}
|
package/src/safe-area.web.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useSafeAreaInsets } from './safe-area.web.tsrx'
|
|
1
|
+
export { useSafeAreaInsets } from './safe-area.web.tsrx'
|
package/src/safe-area.web.tsrx
CHANGED
|
@@ -10,6 +10,7 @@ function measure(): Insets {
|
|
|
10
10
|
'position:fixed;top:0;left:0;pointer-events:none;visibility:hidden;' +
|
|
11
11
|
'padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom);' +
|
|
12
12
|
'padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right);';
|
|
13
|
+
|
|
13
14
|
document.body.appendChild(probe);
|
|
14
15
|
const cs = getComputedStyle(probe);
|
|
15
16
|
const insets = {
|
|
@@ -18,6 +19,7 @@ function measure(): Insets {
|
|
|
18
19
|
left: parseFloat(cs.paddingLeft) || 0,
|
|
19
20
|
right: parseFloat(cs.paddingRight) || 0,
|
|
20
21
|
};
|
|
22
|
+
|
|
21
23
|
probe.remove();
|
|
22
24
|
return insets;
|
|
23
25
|
}
|
|
@@ -30,5 +32,6 @@ export function useSafeAreaInsets(): Insets {
|
|
|
30
32
|
window.addEventListener('resize', update);
|
|
31
33
|
return () => window.removeEventListener('resize', update);
|
|
32
34
|
}, []);
|
|
35
|
+
|
|
33
36
|
return insets;
|
|
34
37
|
}
|
package/src/screen.native.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useWindowSize } from './screen.native.tsrx'
|
|
1
|
+
export { useWindowSize } from './screen.native.tsrx'
|
package/src/screen.native.tsrx
CHANGED
package/src/screen.web.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { useWindowSize } from './screen.web.tsrx'
|
|
1
|
+
export { useWindowSize } from './screen.web.tsrx'
|
package/src/screen.web.tsrx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Secure storage — Keychain (iOS) / Keystore (Android) via plugin.
|
|
2
|
-
import { SecureStorage } from '@nativescript/secure-storage'
|
|
3
|
-
import type { Capability } from './types'
|
|
4
|
-
import type { SecureStore } from './types'
|
|
2
|
+
import { SecureStorage } from '@nativescript/secure-storage'
|
|
3
|
+
import type { Capability } from './types'
|
|
4
|
+
import type { SecureStore } from './types'
|
|
5
5
|
|
|
6
|
-
const store = new SecureStorage()
|
|
6
|
+
const store = new SecureStorage()
|
|
7
7
|
|
|
8
8
|
export const secureStorage: Capability<SecureStore> = {
|
|
9
9
|
supported: true,
|
|
@@ -14,4 +14,4 @@ export const secureStorage: Capability<SecureStore> = {
|
|
|
14
14
|
set: (key, value) => store.set({ key, value }),
|
|
15
15
|
remove: (key) => store.remove({ key }),
|
|
16
16
|
},
|
|
17
|
-
}
|
|
17
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Secure storage — web has no equivalent trust boundary (IndexedDB is not
|
|
2
2
|
// secure enclave storage). Declared unsupported per the Capability contract;
|
|
3
3
|
// callers must branch on `supported` rather than catching.
|
|
4
|
-
import type { Capability, SecureStore } from './types'
|
|
4
|
+
import type { Capability, SecureStore } from './types'
|
|
5
5
|
|
|
6
6
|
export const secureStorage: Capability<SecureStore> = {
|
|
7
7
|
supported: false,
|
|
8
8
|
ensure: async () => 'unsupported',
|
|
9
9
|
impl: null,
|
|
10
|
-
}
|
|
10
|
+
}
|
package/src/share.native.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Share — native share sheet via @nativescript/social-share.
|
|
2
|
-
import { shareText, shareUrl } from '@nativescript/social-share'
|
|
2
|
+
import { shareText, shareUrl } from '@nativescript/social-share'
|
|
3
3
|
|
|
4
4
|
export const share = {
|
|
5
5
|
async text(text: string, subject?: string): Promise<'shared'> {
|
|
6
|
-
shareText(text, subject)
|
|
7
|
-
return 'shared'
|
|
6
|
+
shareText(text, subject)
|
|
7
|
+
return 'shared'
|
|
8
8
|
},
|
|
9
9
|
async url(url: string, title?: string): Promise<'shared'> {
|
|
10
|
-
shareUrl(url, title ?? url)
|
|
11
|
-
return 'shared'
|
|
10
|
+
shareUrl(url, title ?? url)
|
|
11
|
+
return 'shared'
|
|
12
12
|
},
|
|
13
|
-
}
|
|
13
|
+
}
|
package/src/share.web.ts
CHANGED
|
@@ -2,27 +2,31 @@
|
|
|
2
2
|
// desktop degrades to clipboard copy so the call still does something useful.
|
|
3
3
|
export const share = {
|
|
4
4
|
async text(text: string, subject?: string): Promise<'shared' | 'copied' | 'unavailable'> {
|
|
5
|
-
const nav = navigator as any
|
|
5
|
+
const nav = navigator as any
|
|
6
6
|
if (nav.share) {
|
|
7
|
-
await nav.share({ text, title: subject })
|
|
8
|
-
return 'shared'
|
|
7
|
+
await nav.share({ text, title: subject })
|
|
8
|
+
return 'shared'
|
|
9
9
|
}
|
|
10
|
+
|
|
10
11
|
if (nav.clipboard) {
|
|
11
|
-
await nav.clipboard.writeText(text)
|
|
12
|
-
return 'copied'
|
|
12
|
+
await nav.clipboard.writeText(text)
|
|
13
|
+
return 'copied'
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
return 'unavailable'
|
|
15
17
|
},
|
|
16
18
|
async url(url: string, title?: string): Promise<'shared' | 'copied' | 'unavailable'> {
|
|
17
|
-
const nav = navigator as any
|
|
19
|
+
const nav = navigator as any
|
|
18
20
|
if (nav.share) {
|
|
19
|
-
await nav.share({ url, title })
|
|
20
|
-
return 'shared'
|
|
21
|
+
await nav.share({ url, title })
|
|
22
|
+
return 'shared'
|
|
21
23
|
}
|
|
24
|
+
|
|
22
25
|
if (nav.clipboard) {
|
|
23
|
-
await nav.clipboard.writeText(url)
|
|
24
|
-
return 'copied'
|
|
26
|
+
await nav.clipboard.writeText(url)
|
|
27
|
+
return 'copied'
|
|
25
28
|
}
|
|
26
|
-
|
|
29
|
+
|
|
30
|
+
return 'unavailable'
|
|
27
31
|
},
|
|
28
|
-
}
|
|
32
|
+
}
|
package/src/storage.native.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { ApplicationSettings } from '@nativescript/core'
|
|
1
|
+
import { ApplicationSettings } from '@nativescript/core'
|
|
2
2
|
|
|
3
3
|
/** Sync KV seam — NS ApplicationSettings (NSUserDefaults) on native,
|
|
4
4
|
* the web leaf uses the DOM's string store. String-only for v1;
|
|
5
5
|
* serialize objects at the edge. */
|
|
6
6
|
export const storage = {
|
|
7
7
|
getString: (k: string): string | null => ApplicationSettings.getString(k) ?? null,
|
|
8
|
-
setString: (k: string, v: string): void => {
|
|
9
|
-
|
|
10
|
-
}
|
|
8
|
+
setString: (k: string, v: string): void => {
|
|
9
|
+
ApplicationSettings.setString(k, v)
|
|
10
|
+
},
|
|
11
|
+
remove: (k: string): void => {
|
|
12
|
+
ApplicationSettings.remove(k)
|
|
13
|
+
},
|
|
14
|
+
}
|
package/src/storage.web.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/** Sync KV seam — localStorage on web. */
|
|
2
2
|
export const storage = {
|
|
3
3
|
getString: (k: string): string | null => localStorage.getItem(k),
|
|
4
|
-
setString: (k: string, v: string): void => {
|
|
5
|
-
|
|
6
|
-
}
|
|
4
|
+
setString: (k: string, v: string): void => {
|
|
5
|
+
localStorage.setItem(k, v)
|
|
6
|
+
},
|
|
7
|
+
remove: (k: string): void => {
|
|
8
|
+
localStorage.removeItem(k)
|
|
9
|
+
},
|
|
10
|
+
}
|
|
@@ -1,22 +1,33 @@
|
|
|
1
|
-
// Status/nav bars — native leaf.
|
|
2
|
-
//
|
|
3
|
-
import { Application, Color } from '@nativescript/core'
|
|
1
|
+
// Status/nav bars — native leaf. Status-bar icon style on both platforms +
|
|
2
|
+
// Android status/nav-bar tint via the application window.
|
|
3
|
+
import { Application, Color } from '@nativescript/core'
|
|
4
4
|
|
|
5
5
|
export const systemBars = {
|
|
6
6
|
setStatusBarStyle(style: 'light' | 'dark'): void {
|
|
7
|
+
// 'light'|'dark' name the icon appearance: 'light' = light content
|
|
8
|
+
// for dark backgrounds. NS View.statusBarStyle uses the same
|
|
9
|
+
// convention on both platforms (Android → APPEARANCE_LIGHT_*).
|
|
10
|
+
const root = Application.getRootView()
|
|
11
|
+
if (root) {
|
|
12
|
+
root.statusBarStyle = style
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
if (Application.ios) {
|
|
8
|
-
const app = Application.ios.nativeApp
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
|
|
16
|
+
const app = Application.ios.nativeApp
|
|
17
|
+
|
|
18
|
+
// NS 9: per-page statusBarStyle is the preferred seam — the
|
|
19
|
+
// root-view write above drives it; keep the app-level setter as
|
|
20
|
+
// fallback for controllers outside the root's hierarchy.
|
|
21
|
+
|
|
22
|
+
;(app as any)?.setStatusBarStyle?.(style === 'light' ? 1 : 0)
|
|
12
23
|
}
|
|
13
24
|
},
|
|
14
25
|
setColor(color: string): void {
|
|
15
|
-
const c = new Color(color)
|
|
26
|
+
const c = new Color(color)
|
|
16
27
|
if (Application.android?.startActivity) {
|
|
17
|
-
const win = Application.android.startActivity.getWindow()
|
|
18
|
-
win.setStatusBarColor(c.android)
|
|
19
|
-
win.setNavigationBarColor(c.android)
|
|
28
|
+
const win = Application.android.startActivity.getWindow()
|
|
29
|
+
win.setStatusBarColor(c.android)
|
|
30
|
+
win.setNavigationBarColor(c.android)
|
|
20
31
|
}
|
|
21
32
|
},
|
|
22
|
-
}
|
|
33
|
+
}
|