flowboard-react 0.6.2 → 0.6.3
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 +168 -52
- package/app.plugin.js +341 -0
- package/bin/setup.js +427 -0
- package/lib/module/FlowboardProvider.js +1 -1
- package/lib/module/FlowboardProvider.js.map +1 -1
- package/lib/module/components/FlowboardFlow.js +6 -6
- package/lib/module/components/FlowboardFlow.js.map +1 -1
- package/lib/module/components/FlowboardRenderer.js +6 -6
- package/lib/module/components/FlowboardRenderer.js.map +1 -1
- package/lib/module/core/clientContext.js +10 -29
- package/lib/module/core/clientContext.js.map +1 -1
- package/lib/module/core/fontAwesome.js +2 -9
- package/lib/module/core/fontAwesome.js.map +1 -1
- package/lib/module/core/onboardingRepository.js +13 -13
- package/lib/module/core/onboardingRepository.js.map +1 -1
- package/lib/module/native/asyncStorage.js +55 -0
- package/lib/module/native/asyncStorage.js.map +1 -0
- package/lib/module/native/deviceInfo.js +46 -0
- package/lib/module/native/deviceInfo.js.map +1 -0
- package/lib/module/native/inAppReview.js +17 -0
- package/lib/module/native/inAppReview.js.map +1 -0
- package/lib/module/native/linearGradient.js +29 -0
- package/lib/module/native/linearGradient.js.map +1 -0
- package/lib/module/native/lottie.js +20 -0
- package/lib/module/native/lottie.js.map +1 -0
- package/lib/module/native/maskedView.js +26 -0
- package/lib/module/native/maskedView.js.map +1 -0
- package/lib/module/native/pagerView.js +79 -0
- package/lib/module/native/pagerView.js.map +1 -0
- package/lib/module/native/permissions.js +30 -0
- package/lib/module/native/permissions.js.map +1 -0
- package/lib/module/native/runtime.js +81 -0
- package/lib/module/native/runtime.js.map +1 -0
- package/lib/module/native/safeAreaContext.js +43 -0
- package/lib/module/native/safeAreaContext.js.map +1 -0
- package/lib/module/native/svg.js +83 -0
- package/lib/module/native/svg.js.map +1 -0
- package/lib/module/native/vectorIcons.js +41 -0
- package/lib/module/native/vectorIcons.js.map +1 -0
- package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
- package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
- package/lib/typescript/src/core/clientContext.d.ts.map +1 -1
- package/lib/typescript/src/core/fontAwesome.d.ts +1 -1
- package/lib/typescript/src/core/fontAwesome.d.ts.map +1 -1
- package/lib/typescript/src/core/onboardingRepository.d.ts.map +1 -1
- package/lib/typescript/src/native/asyncStorage.d.ts +16 -0
- package/lib/typescript/src/native/asyncStorage.d.ts.map +1 -0
- package/lib/typescript/src/native/deviceInfo.d.ts +11 -0
- package/lib/typescript/src/native/deviceInfo.d.ts.map +1 -0
- package/lib/typescript/src/native/inAppReview.d.ts +4 -0
- package/lib/typescript/src/native/inAppReview.d.ts.map +1 -0
- package/lib/typescript/src/native/linearGradient.d.ts +3 -0
- package/lib/typescript/src/native/linearGradient.d.ts.map +1 -0
- package/lib/typescript/src/native/lottie.d.ts +3 -0
- package/lib/typescript/src/native/lottie.d.ts.map +1 -0
- package/lib/typescript/src/native/maskedView.d.ts +3 -0
- package/lib/typescript/src/native/maskedView.d.ts.map +1 -0
- package/lib/typescript/src/native/pagerView.d.ts +14 -0
- package/lib/typescript/src/native/pagerView.d.ts.map +1 -0
- package/lib/typescript/src/native/permissions.d.ts +9 -0
- package/lib/typescript/src/native/permissions.d.ts.map +1 -0
- package/lib/typescript/src/native/runtime.d.ts +13 -0
- package/lib/typescript/src/native/runtime.d.ts.map +1 -0
- package/lib/typescript/src/native/safeAreaContext.d.ts +15 -0
- package/lib/typescript/src/native/safeAreaContext.d.ts.map +1 -0
- package/lib/typescript/src/native/svg.d.ts +8 -0
- package/lib/typescript/src/native/svg.d.ts.map +1 -0
- package/lib/typescript/src/native/vectorIcons.d.ts +4 -0
- package/lib/typescript/src/native/vectorIcons.d.ts.map +1 -0
- package/package.json +20 -14
- package/src/FlowboardProvider.tsx +1 -1
- package/src/components/FlowboardFlow.tsx +10 -7
- package/src/components/FlowboardRenderer.tsx +8 -13
- package/src/core/clientContext.ts +10 -32
- package/src/core/fontAwesome.ts +2 -9
- package/src/core/onboardingRepository.ts +18 -13
- package/src/native/asyncStorage.ts +99 -0
- package/src/native/deviceInfo.ts +88 -0
- package/src/native/inAppReview.ts +34 -0
- package/src/native/linearGradient.tsx +24 -0
- package/src/native/lottie.tsx +17 -0
- package/src/native/maskedView.tsx +19 -0
- package/src/native/pagerView.tsx +95 -0
- package/src/native/permissions.ts +59 -0
- package/src/native/runtime.ts +110 -0
- package/src/native/safeAreaContext.tsx +44 -0
- package/src/native/svg.tsx +82 -0
- package/src/native/vectorIcons.tsx +50 -0
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { Alert, Keyboard, Platform, StyleSheet, View } from 'react-native';
|
|
3
|
-
import PagerView from 'react-native-pager-view';
|
|
4
3
|
import { Linking } from 'react-native';
|
|
5
|
-
import InAppReview from 'react-native-in-app-review';
|
|
6
|
-
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
7
4
|
import {
|
|
8
5
|
openSettings,
|
|
9
6
|
PERMISSIONS,
|
|
10
7
|
request,
|
|
11
8
|
requestNotifications,
|
|
12
9
|
RESULTS,
|
|
13
|
-
} from '
|
|
10
|
+
} from '../native/permissions';
|
|
14
11
|
import { AssetPreloader } from '../core/assetPreloader';
|
|
15
12
|
import { AnalyticsManager, OnboardingOutcome } from '../core/analyticsManager';
|
|
16
13
|
import { OnboardingRepository } from '../core/onboardingRepository';
|
|
14
|
+
import {
|
|
15
|
+
requestInAppReview,
|
|
16
|
+
isInAppReviewAvailable,
|
|
17
|
+
} from '../native/inAppReview';
|
|
18
|
+
import PagerView, { type PagerViewHandle } from '../native/pagerView';
|
|
19
|
+
import { SafeAreaProvider } from '../native/safeAreaContext';
|
|
17
20
|
import FlowboardRenderer from './FlowboardRenderer';
|
|
18
21
|
import {
|
|
19
22
|
SliderRegistry,
|
|
@@ -62,7 +65,7 @@ export default function FlowboardFlow(props: FlowboardFlowProps) {
|
|
|
62
65
|
const repository = useMemo(() => new OnboardingRepository(), []);
|
|
63
66
|
const assetPreloader = useMemo(() => new AssetPreloader(), []);
|
|
64
67
|
const sliderRegistry = useMemo(() => new SliderRegistry(), []);
|
|
65
|
-
const pagerRef = useRef<
|
|
68
|
+
const pagerRef = useRef<PagerViewHandle | null>(null);
|
|
66
69
|
const [currentIndex, setCurrentIndex] = useState(() =>
|
|
67
70
|
resolveInitialPage(initialStep, screens.length)
|
|
68
71
|
);
|
|
@@ -327,8 +330,8 @@ export default function FlowboardFlow(props: FlowboardFlowProps) {
|
|
|
327
330
|
|
|
328
331
|
const handleRateAppAction = async (index: number, totalScreens: number) => {
|
|
329
332
|
try {
|
|
330
|
-
if (
|
|
331
|
-
|
|
333
|
+
if (isInAppReviewAvailable()) {
|
|
334
|
+
await requestInAppReview();
|
|
332
335
|
}
|
|
333
336
|
} catch {
|
|
334
337
|
// ignore
|
|
@@ -14,21 +14,16 @@ import {
|
|
|
14
14
|
type TextStyle,
|
|
15
15
|
type ViewStyle,
|
|
16
16
|
} from 'react-native';
|
|
17
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
18
|
-
import MaskedView from '@react-native-masked-view/masked-view';
|
|
19
|
-
import LinearGradient from 'react-native-linear-gradient';
|
|
20
|
-
import LottieView from 'lottie-react-native';
|
|
21
17
|
import MaskInput from 'react-native-mask-input';
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Line,
|
|
26
|
-
Polygon,
|
|
27
|
-
Text as SvgText,
|
|
28
|
-
} from 'react-native-svg';
|
|
18
|
+
import LinearGradient from '../native/linearGradient';
|
|
19
|
+
import LottieView from '../native/lottie';
|
|
20
|
+
import MaskedView from '../native/maskedView';
|
|
29
21
|
import PagerView, {
|
|
22
|
+
type PagerViewHandle,
|
|
30
23
|
type PagerViewOnPageSelectedEvent,
|
|
31
|
-
} from '
|
|
24
|
+
} from '../native/pagerView';
|
|
25
|
+
import { useSafeAreaInsets } from '../native/safeAreaContext';
|
|
26
|
+
import Svg, { Circle, G, Line, Polygon, SvgText } from '../native/svg';
|
|
32
27
|
import {
|
|
33
28
|
insetsToStyle,
|
|
34
29
|
insetsToMarginStyle,
|
|
@@ -3950,7 +3945,7 @@ function SliderWidget({
|
|
|
3950
3945
|
children: React.ReactNode[];
|
|
3951
3946
|
}) {
|
|
3952
3947
|
const registry = useSliderRegistry();
|
|
3953
|
-
const pagerRef = React.useRef<
|
|
3948
|
+
const pagerRef = React.useRef<PagerViewHandle | null>(null);
|
|
3954
3949
|
const pageLength = children.length;
|
|
3955
3950
|
const pageCount = pageLength;
|
|
3956
3951
|
const [currentPage, setCurrentPage] = React.useState(0);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
-
import DeviceInfo from 'react-native-device-info';
|
|
3
1
|
import { Platform } from 'react-native';
|
|
4
2
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
3
|
import 'react-native-get-random-values';
|
|
4
|
+
import { FlowboardAsyncStorage } from '../native/asyncStorage';
|
|
5
|
+
import { FlowboardDeviceInfo } from '../native/deviceInfo';
|
|
6
6
|
|
|
7
7
|
export type ClientContextData = {
|
|
8
8
|
appVersion: string;
|
|
@@ -44,16 +44,16 @@ export class ClientContext {
|
|
|
44
44
|
static async create(): Promise<ClientContext> {
|
|
45
45
|
const installId = await getOrCreateInstallId();
|
|
46
46
|
|
|
47
|
-
const appVersion =
|
|
48
|
-
const buildNumber =
|
|
49
|
-
const bundleId =
|
|
47
|
+
const appVersion = FlowboardDeviceInfo.getVersion();
|
|
48
|
+
const buildNumber = FlowboardDeviceInfo.getBuildNumber();
|
|
49
|
+
const bundleId = FlowboardDeviceInfo.getBundleId();
|
|
50
50
|
|
|
51
|
-
const locale = await getDeviceLocale();
|
|
51
|
+
const locale = await FlowboardDeviceInfo.getDeviceLocale();
|
|
52
52
|
const country = locale.includes('_') ? locale.split('_').pop() ?? '' : '';
|
|
53
53
|
|
|
54
54
|
const os = Platform.OS;
|
|
55
|
-
const osVersion =
|
|
56
|
-
const deviceType = mapDeviceType(await
|
|
55
|
+
const osVersion = FlowboardDeviceInfo.getSystemVersion();
|
|
56
|
+
const deviceType = mapDeviceType(await FlowboardDeviceInfo.getDeviceType());
|
|
57
57
|
|
|
58
58
|
return new ClientContext({
|
|
59
59
|
appVersion,
|
|
@@ -87,35 +87,13 @@ export class ClientContext {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
async function getOrCreateInstallId(): Promise<string> {
|
|
90
|
-
const stored = await
|
|
90
|
+
const stored = await FlowboardAsyncStorage.getItem(INSTALL_ID_KEY);
|
|
91
91
|
if (stored) return stored;
|
|
92
92
|
const installId = uuidv4();
|
|
93
|
-
await
|
|
93
|
+
await FlowboardAsyncStorage.setItem(INSTALL_ID_KEY, installId);
|
|
94
94
|
return installId;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
async function getDeviceLocale(): Promise<string> {
|
|
98
|
-
const deviceInfoAny = DeviceInfo as any;
|
|
99
|
-
if (typeof deviceInfoAny.getDeviceLocale === 'function') {
|
|
100
|
-
const locale = await deviceInfoAny.getDeviceLocale();
|
|
101
|
-
if (locale) return locale;
|
|
102
|
-
}
|
|
103
|
-
if (typeof deviceInfoAny.getDeviceLocales === 'function') {
|
|
104
|
-
const locales = await deviceInfoAny.getDeviceLocales();
|
|
105
|
-
if (Array.isArray(locales) && locales.length > 0) {
|
|
106
|
-
const first = locales[0];
|
|
107
|
-
if (typeof first === 'string') return first;
|
|
108
|
-
if (first?.languageTag) return first.languageTag;
|
|
109
|
-
if (first?.languageCode) {
|
|
110
|
-
return first.countryCode
|
|
111
|
-
? `${first.languageCode}_${first.countryCode}`
|
|
112
|
-
: first.languageCode;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
return 'en_US';
|
|
117
|
-
}
|
|
118
|
-
|
|
119
97
|
function mapDeviceType(deviceType: string): string {
|
|
120
98
|
switch (deviceType?.toLowerCase()) {
|
|
121
99
|
case 'handset':
|
package/src/core/fontAwesome.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import FontAwesome6 from '
|
|
1
|
+
import { FontAwesome6, getFontAwesomeGlyphMap } from '../native/vectorIcons';
|
|
2
2
|
|
|
3
3
|
const hexRegex = /^(0x)?[a-fA-F0-9]{4,5}$/;
|
|
4
4
|
|
|
@@ -50,14 +50,7 @@ export function resolveFontAwesomeIcon(
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function getGlyphMap(): Record<string, number> | null {
|
|
53
|
-
|
|
54
|
-
if (typeof anyIcon.getRawGlyphMap === 'function') {
|
|
55
|
-
return anyIcon.getRawGlyphMap();
|
|
56
|
-
}
|
|
57
|
-
if (anyIcon.glyphMap) {
|
|
58
|
-
return anyIcon.glyphMap as Record<string, number>;
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
53
|
+
return getFontAwesomeGlyphMap();
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
function styleProps(style: string): Record<string, any> {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
1
|
import type { FlowboardData } from '../types/flowboard';
|
|
2
|
+
import { FlowboardAsyncStorage } from '../native/asyncStorage';
|
|
3
3
|
|
|
4
4
|
const STORAGE_KEY = 'flowboard_onboarding_json';
|
|
5
5
|
const FETCH_TIME_KEY = 'flowboard_fetch_time';
|
|
@@ -9,12 +9,12 @@ const PROGRESS_FORM_DATA_KEY = 'flowboard_progress_form_data';
|
|
|
9
9
|
|
|
10
10
|
export class OnboardingRepository {
|
|
11
11
|
async saveOnboardingJson(json: FlowboardData): Promise<void> {
|
|
12
|
-
await
|
|
13
|
-
await
|
|
12
|
+
await FlowboardAsyncStorage.setItem(STORAGE_KEY, JSON.stringify(json));
|
|
13
|
+
await FlowboardAsyncStorage.setItem(FETCH_TIME_KEY, Date.now().toString());
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async getOnboardingJson(): Promise<FlowboardData | null> {
|
|
17
|
-
const jsonString = await
|
|
17
|
+
const jsonString = await FlowboardAsyncStorage.getItem(STORAGE_KEY);
|
|
18
18
|
if (!jsonString) return null;
|
|
19
19
|
try {
|
|
20
20
|
return JSON.parse(jsonString) as FlowboardData;
|
|
@@ -25,7 +25,7 @@ export class OnboardingRepository {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async clearOnboardingJson(): Promise<void> {
|
|
28
|
-
await
|
|
28
|
+
await FlowboardAsyncStorage.multiRemove([STORAGE_KEY, FETCH_TIME_KEY]);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
async saveProgress(params: {
|
|
@@ -33,18 +33,21 @@ export class OnboardingRepository {
|
|
|
33
33
|
stepIndex: number;
|
|
34
34
|
formData: Record<string, any>;
|
|
35
35
|
}): Promise<void> {
|
|
36
|
-
await
|
|
37
|
-
await
|
|
38
|
-
|
|
36
|
+
await FlowboardAsyncStorage.setItem(PROGRESS_FLOW_KEY, params.flowId);
|
|
37
|
+
await FlowboardAsyncStorage.setItem(
|
|
38
|
+
PROGRESS_STEP_KEY,
|
|
39
|
+
params.stepIndex.toString()
|
|
40
|
+
);
|
|
41
|
+
await FlowboardAsyncStorage.setItem(
|
|
39
42
|
PROGRESS_FORM_DATA_KEY,
|
|
40
43
|
JSON.stringify(params.formData)
|
|
41
44
|
);
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
async getProgressStepForFlow(flowId: string): Promise<number | null> {
|
|
45
|
-
const storedFlowId = await
|
|
48
|
+
const storedFlowId = await FlowboardAsyncStorage.getItem(PROGRESS_FLOW_KEY);
|
|
46
49
|
if (storedFlowId !== flowId) return null;
|
|
47
|
-
const value = await
|
|
50
|
+
const value = await FlowboardAsyncStorage.getItem(PROGRESS_STEP_KEY);
|
|
48
51
|
if (!value) return null;
|
|
49
52
|
const parsed = Number(value);
|
|
50
53
|
return Number.isNaN(parsed) ? null : parsed;
|
|
@@ -53,9 +56,11 @@ export class OnboardingRepository {
|
|
|
53
56
|
async getProgressFormDataForFlow(
|
|
54
57
|
flowId: string
|
|
55
58
|
): Promise<Record<string, any> | null> {
|
|
56
|
-
const storedFlowId = await
|
|
59
|
+
const storedFlowId = await FlowboardAsyncStorage.getItem(PROGRESS_FLOW_KEY);
|
|
57
60
|
if (storedFlowId !== flowId) return null;
|
|
58
|
-
const jsonString = await
|
|
61
|
+
const jsonString = await FlowboardAsyncStorage.getItem(
|
|
62
|
+
PROGRESS_FORM_DATA_KEY
|
|
63
|
+
);
|
|
59
64
|
if (!jsonString) return null;
|
|
60
65
|
try {
|
|
61
66
|
const decoded = JSON.parse(jsonString);
|
|
@@ -70,7 +75,7 @@ export class OnboardingRepository {
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
async clearProgress(): Promise<void> {
|
|
73
|
-
await
|
|
78
|
+
await FlowboardAsyncStorage.multiRemove([
|
|
74
79
|
PROGRESS_FLOW_KEY,
|
|
75
80
|
PROGRESS_STEP_KEY,
|
|
76
81
|
PROGRESS_FORM_DATA_KEY,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
import { hasNativeModule, safeAsync } from './runtime';
|
|
3
|
+
|
|
4
|
+
const memoryStore = new Map<string, string>();
|
|
5
|
+
|
|
6
|
+
const warning =
|
|
7
|
+
'AsyncStorage is unavailable. Flowboard will fall back to in-memory storage until native setup is fixed.';
|
|
8
|
+
|
|
9
|
+
export const asyncStorageAvailable = hasNativeModule(
|
|
10
|
+
'RNCAsyncStorage',
|
|
11
|
+
'RNC_AsyncSQLiteDBStorage',
|
|
12
|
+
'AsyncSQLiteDBStorage',
|
|
13
|
+
'AsyncLocalStorage',
|
|
14
|
+
'PlatformLocalStorage'
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
function getMemoryValue(key: string): string | null {
|
|
18
|
+
return memoryStore.has(key) ? memoryStore.get(key) ?? null : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getItem(key: string): Promise<string | null> {
|
|
22
|
+
const fallback = getMemoryValue(key);
|
|
23
|
+
return safeAsync(
|
|
24
|
+
'native-async-storage-get',
|
|
25
|
+
asyncStorageAvailable,
|
|
26
|
+
async () => {
|
|
27
|
+
const value = await AsyncStorage.getItem(key);
|
|
28
|
+
if (typeof value === 'string') {
|
|
29
|
+
memoryStore.set(key, value);
|
|
30
|
+
}
|
|
31
|
+
return value ?? fallback;
|
|
32
|
+
},
|
|
33
|
+
fallback,
|
|
34
|
+
warning
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function setItem(key: string, value: string): Promise<void> {
|
|
39
|
+
memoryStore.set(key, value);
|
|
40
|
+
await safeAsync(
|
|
41
|
+
'native-async-storage-set',
|
|
42
|
+
asyncStorageAvailable,
|
|
43
|
+
async () => {
|
|
44
|
+
await AsyncStorage.setItem(key, value);
|
|
45
|
+
},
|
|
46
|
+
undefined,
|
|
47
|
+
warning
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function removeItem(key: string): Promise<void> {
|
|
52
|
+
memoryStore.delete(key);
|
|
53
|
+
await safeAsync(
|
|
54
|
+
'native-async-storage-remove',
|
|
55
|
+
asyncStorageAvailable,
|
|
56
|
+
async () => {
|
|
57
|
+
await AsyncStorage.removeItem(key);
|
|
58
|
+
},
|
|
59
|
+
undefined,
|
|
60
|
+
warning
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function multiRemove(keys: string[]): Promise<void> {
|
|
65
|
+
keys.forEach((key) => memoryStore.delete(key));
|
|
66
|
+
await safeAsync(
|
|
67
|
+
'native-async-storage-multi-remove',
|
|
68
|
+
asyncStorageAvailable,
|
|
69
|
+
async () => {
|
|
70
|
+
await AsyncStorage.multiRemove(keys);
|
|
71
|
+
},
|
|
72
|
+
undefined,
|
|
73
|
+
warning
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function clear(): Promise<void> {
|
|
78
|
+
memoryStore.clear();
|
|
79
|
+
await safeAsync(
|
|
80
|
+
'native-async-storage-clear',
|
|
81
|
+
asyncStorageAvailable,
|
|
82
|
+
async () => {
|
|
83
|
+
if (typeof (AsyncStorage as any).clear === 'function') {
|
|
84
|
+
await (AsyncStorage as any).clear();
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
undefined,
|
|
88
|
+
warning
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const FlowboardAsyncStorage = {
|
|
93
|
+
available: asyncStorageAvailable,
|
|
94
|
+
clear,
|
|
95
|
+
getItem,
|
|
96
|
+
multiRemove,
|
|
97
|
+
removeItem,
|
|
98
|
+
setItem,
|
|
99
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import DeviceInfo from 'react-native-device-info';
|
|
2
|
+
import { hasNativeModule, safeAsync, safeSync } from './runtime';
|
|
3
|
+
|
|
4
|
+
const warning =
|
|
5
|
+
'react-native-device-info is unavailable. Flowboard will use generic device metadata until native setup is fixed.';
|
|
6
|
+
const nativeDeviceInfo = DeviceInfo as any;
|
|
7
|
+
|
|
8
|
+
export const deviceInfoAvailable = hasNativeModule('RNDeviceInfo');
|
|
9
|
+
|
|
10
|
+
export const FlowboardDeviceInfo = {
|
|
11
|
+
available: deviceInfoAvailable,
|
|
12
|
+
getBuildNumber(): string {
|
|
13
|
+
return safeSync(
|
|
14
|
+
'native-device-info-build-number',
|
|
15
|
+
deviceInfoAvailable,
|
|
16
|
+
() => String(nativeDeviceInfo.getBuildNumber?.() ?? '1'),
|
|
17
|
+
'1',
|
|
18
|
+
warning
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
async getDeviceLocale(): Promise<string> {
|
|
22
|
+
return safeAsync(
|
|
23
|
+
'native-device-info-locale',
|
|
24
|
+
deviceInfoAvailable,
|
|
25
|
+
async () => {
|
|
26
|
+
if (typeof nativeDeviceInfo.getDeviceLocale === 'function') {
|
|
27
|
+
const locale = await nativeDeviceInfo.getDeviceLocale();
|
|
28
|
+
if (locale) return String(locale);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof nativeDeviceInfo.getDeviceLocales === 'function') {
|
|
32
|
+
const locales = await nativeDeviceInfo.getDeviceLocales();
|
|
33
|
+
if (Array.isArray(locales) && locales.length > 0) {
|
|
34
|
+
const first = locales[0];
|
|
35
|
+
if (typeof first === 'string') return first;
|
|
36
|
+
if (first?.languageTag) return String(first.languageTag);
|
|
37
|
+
if (first?.languageCode) {
|
|
38
|
+
return first.countryCode
|
|
39
|
+
? `${first.languageCode}_${first.countryCode}`
|
|
40
|
+
: String(first.languageCode);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 'en_US';
|
|
46
|
+
},
|
|
47
|
+
'en_US',
|
|
48
|
+
warning
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
async getDeviceType(): Promise<string> {
|
|
52
|
+
return safeAsync(
|
|
53
|
+
'native-device-info-device-type',
|
|
54
|
+
deviceInfoAvailable,
|
|
55
|
+
async () =>
|
|
56
|
+
String((await nativeDeviceInfo.getDeviceType?.()) ?? 'Handset'),
|
|
57
|
+
'Handset',
|
|
58
|
+
warning
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
getBundleId(): string {
|
|
62
|
+
return safeSync(
|
|
63
|
+
'native-device-info-bundle-id',
|
|
64
|
+
deviceInfoAvailable,
|
|
65
|
+
() => String(nativeDeviceInfo.getBundleId?.() ?? 'unknown.bundle'),
|
|
66
|
+
'unknown.bundle',
|
|
67
|
+
warning
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
getSystemVersion(): string {
|
|
71
|
+
return safeSync(
|
|
72
|
+
'native-device-info-system-version',
|
|
73
|
+
deviceInfoAvailable,
|
|
74
|
+
() => String(nativeDeviceInfo.getSystemVersion?.() ?? '0'),
|
|
75
|
+
'0',
|
|
76
|
+
warning
|
|
77
|
+
);
|
|
78
|
+
},
|
|
79
|
+
getVersion(): string {
|
|
80
|
+
return safeSync(
|
|
81
|
+
'native-device-info-version',
|
|
82
|
+
deviceInfoAvailable,
|
|
83
|
+
() => String(nativeDeviceInfo.getVersion?.() ?? '0.0.0'),
|
|
84
|
+
'0.0.0',
|
|
85
|
+
warning
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import InAppReview from 'react-native-in-app-review';
|
|
2
|
+
import { hasNativeModule, safeAsync, safeSync } from './runtime';
|
|
3
|
+
|
|
4
|
+
const warning =
|
|
5
|
+
'react-native-in-app-review is unavailable. Flowboard will skip in-app review prompts until native setup is fixed.';
|
|
6
|
+
const nativeInAppReview = InAppReview as any;
|
|
7
|
+
|
|
8
|
+
export const inAppReviewAvailable = hasNativeModule(
|
|
9
|
+
'InAppReviewModule',
|
|
10
|
+
'RNInAppReviewIOS'
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export function isInAppReviewAvailable(): boolean {
|
|
14
|
+
return safeSync(
|
|
15
|
+
'native-in-app-review-available',
|
|
16
|
+
inAppReviewAvailable,
|
|
17
|
+
() => Boolean(nativeInAppReview.isAvailable?.()),
|
|
18
|
+
false,
|
|
19
|
+
warning
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function requestInAppReview(): Promise<boolean> {
|
|
24
|
+
return safeAsync(
|
|
25
|
+
'native-in-app-review-request',
|
|
26
|
+
inAppReviewAvailable,
|
|
27
|
+
async () => {
|
|
28
|
+
const result = nativeInAppReview.RequestInAppReview?.();
|
|
29
|
+
return typeof result === 'boolean' ? result : Boolean(await result);
|
|
30
|
+
},
|
|
31
|
+
false,
|
|
32
|
+
warning
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import NativeLinearGradient from 'react-native-linear-gradient';
|
|
3
|
+
import { hasViewManager, warnOnce } from './runtime';
|
|
4
|
+
|
|
5
|
+
const warning =
|
|
6
|
+
'react-native-linear-gradient is unavailable. Flowboard will fall back to solid backgrounds until native setup is fixed.';
|
|
7
|
+
|
|
8
|
+
export const linearGradientAvailable = hasViewManager('BVLinearGradient');
|
|
9
|
+
|
|
10
|
+
export default function LinearGradient(props: any) {
|
|
11
|
+
if (!linearGradientAvailable) {
|
|
12
|
+
warnOnce('native-linear-gradient', warning);
|
|
13
|
+
const { children, colors, style } = props;
|
|
14
|
+
const backgroundColor =
|
|
15
|
+
Array.isArray(colors) && colors.length > 0 ? colors[0] : undefined;
|
|
16
|
+
return (
|
|
17
|
+
<View style={[style, backgroundColor ? { backgroundColor } : null]}>
|
|
18
|
+
{children}
|
|
19
|
+
</View>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return <NativeLinearGradient {...props} />;
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import NativeLottieView from 'lottie-react-native';
|
|
3
|
+
import { hasViewManager, warnOnce } from './runtime';
|
|
4
|
+
|
|
5
|
+
const warning =
|
|
6
|
+
'lottie-react-native is unavailable. Flowboard will reserve space for animations but skip playback until native setup is fixed.';
|
|
7
|
+
|
|
8
|
+
export const lottieAvailable = hasViewManager('LottieAnimationView');
|
|
9
|
+
|
|
10
|
+
export default function LottieView(props: any) {
|
|
11
|
+
if (!lottieAvailable) {
|
|
12
|
+
warnOnce('native-lottie', warning);
|
|
13
|
+
return <View style={props.style} />;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return <NativeLottieView {...props} />;
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
import NativeMaskedView from '@react-native-masked-view/masked-view';
|
|
3
|
+
import { hasViewManager, warnOnce } from './runtime';
|
|
4
|
+
|
|
5
|
+
const warning =
|
|
6
|
+
'@react-native-masked-view/masked-view is unavailable. Flowboard will render masked content without the mask until native setup is fixed.';
|
|
7
|
+
|
|
8
|
+
export const maskedViewAvailable = hasViewManager('RNCMaskedView');
|
|
9
|
+
|
|
10
|
+
export default function MaskedView(props: any) {
|
|
11
|
+
if (!maskedViewAvailable) {
|
|
12
|
+
warnOnce('native-masked-view', warning);
|
|
13
|
+
const { children, ...viewProps } = props;
|
|
14
|
+
delete viewProps.maskElement;
|
|
15
|
+
return <View {...viewProps}>{children}</View>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return <NativeMaskedView {...props} />;
|
|
19
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import NativePagerView from 'react-native-pager-view';
|
|
4
|
+
import { hasViewManager, warnOnce } from './runtime';
|
|
5
|
+
|
|
6
|
+
export type PagerViewHandle = {
|
|
7
|
+
setPage: (index: number) => void;
|
|
8
|
+
setPageWithoutAnimation: (index: number) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type PagerViewOnPageSelectedEvent = {
|
|
12
|
+
nativeEvent: {
|
|
13
|
+
position: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const warning =
|
|
18
|
+
'react-native-pager-view is unavailable. Flowboard will render one page at a time without native paging until setup is fixed.';
|
|
19
|
+
|
|
20
|
+
export const pagerViewAvailable = hasViewManager('RNCViewPager');
|
|
21
|
+
|
|
22
|
+
function clampPage(index: number, totalPages: number): number {
|
|
23
|
+
if (totalPages <= 0) return 0;
|
|
24
|
+
if (index < 0) return 0;
|
|
25
|
+
if (index >= totalPages) return totalPages - 1;
|
|
26
|
+
return index;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class PagerViewFallback extends React.Component<any, { page: number }> {
|
|
30
|
+
constructor(props: any) {
|
|
31
|
+
super(props);
|
|
32
|
+
this.state = {
|
|
33
|
+
page: clampPage(
|
|
34
|
+
Number(props.initialPage ?? 0),
|
|
35
|
+
React.Children.count(props.children)
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
componentDidUpdate(prevProps: any): void {
|
|
41
|
+
if (prevProps.initialPage !== this.props.initialPage) {
|
|
42
|
+
this.setPageWithoutAnimation(Number(this.props.initialPage ?? 0));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const totalPages = React.Children.count(this.props.children);
|
|
47
|
+
const nextPage = clampPage(this.state.page, totalPages);
|
|
48
|
+
if (nextPage !== this.state.page) {
|
|
49
|
+
this.setState({ page: nextPage });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setPage = (index: number) => {
|
|
54
|
+
this.updatePage(index);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
setPageWithoutAnimation = (index: number) => {
|
|
58
|
+
this.updatePage(index);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
private updatePage(index: number): void {
|
|
62
|
+
warnOnce('native-pager-view', warning);
|
|
63
|
+
const totalPages = React.Children.count(this.props.children);
|
|
64
|
+
const nextPage = clampPage(index, totalPages);
|
|
65
|
+
if (nextPage === this.state.page) return;
|
|
66
|
+
|
|
67
|
+
this.setState({ page: nextPage });
|
|
68
|
+
this.props.onPageSelected?.({
|
|
69
|
+
nativeEvent: { position: nextPage },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
render() {
|
|
74
|
+
const viewProps = { ...this.props } as Record<string, any>;
|
|
75
|
+
const { children, style } = viewProps;
|
|
76
|
+
delete viewProps.initialPage;
|
|
77
|
+
delete viewProps.onPageSelected;
|
|
78
|
+
delete viewProps.orientation;
|
|
79
|
+
delete viewProps.overdrag;
|
|
80
|
+
delete viewProps.pageMargin;
|
|
81
|
+
|
|
82
|
+
const pages = React.Children.toArray(children);
|
|
83
|
+
return (
|
|
84
|
+
<View {...viewProps} style={style}>
|
|
85
|
+
{pages[this.state.page] ?? null}
|
|
86
|
+
</View>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const PagerViewComponent = pagerViewAvailable
|
|
92
|
+
? NativePagerView
|
|
93
|
+
: PagerViewFallback;
|
|
94
|
+
|
|
95
|
+
export default PagerViewComponent as React.ComponentType<any>;
|