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.
Files changed (88) hide show
  1. package/README.md +168 -52
  2. package/app.plugin.js +341 -0
  3. package/bin/setup.js +427 -0
  4. package/lib/module/FlowboardProvider.js +1 -1
  5. package/lib/module/FlowboardProvider.js.map +1 -1
  6. package/lib/module/components/FlowboardFlow.js +6 -6
  7. package/lib/module/components/FlowboardFlow.js.map +1 -1
  8. package/lib/module/components/FlowboardRenderer.js +6 -6
  9. package/lib/module/components/FlowboardRenderer.js.map +1 -1
  10. package/lib/module/core/clientContext.js +10 -29
  11. package/lib/module/core/clientContext.js.map +1 -1
  12. package/lib/module/core/fontAwesome.js +2 -9
  13. package/lib/module/core/fontAwesome.js.map +1 -1
  14. package/lib/module/core/onboardingRepository.js +13 -13
  15. package/lib/module/core/onboardingRepository.js.map +1 -1
  16. package/lib/module/native/asyncStorage.js +55 -0
  17. package/lib/module/native/asyncStorage.js.map +1 -0
  18. package/lib/module/native/deviceInfo.js +46 -0
  19. package/lib/module/native/deviceInfo.js.map +1 -0
  20. package/lib/module/native/inAppReview.js +17 -0
  21. package/lib/module/native/inAppReview.js.map +1 -0
  22. package/lib/module/native/linearGradient.js +29 -0
  23. package/lib/module/native/linearGradient.js.map +1 -0
  24. package/lib/module/native/lottie.js +20 -0
  25. package/lib/module/native/lottie.js.map +1 -0
  26. package/lib/module/native/maskedView.js +26 -0
  27. package/lib/module/native/maskedView.js.map +1 -0
  28. package/lib/module/native/pagerView.js +79 -0
  29. package/lib/module/native/pagerView.js.map +1 -0
  30. package/lib/module/native/permissions.js +30 -0
  31. package/lib/module/native/permissions.js.map +1 -0
  32. package/lib/module/native/runtime.js +81 -0
  33. package/lib/module/native/runtime.js.map +1 -0
  34. package/lib/module/native/safeAreaContext.js +43 -0
  35. package/lib/module/native/safeAreaContext.js.map +1 -0
  36. package/lib/module/native/svg.js +83 -0
  37. package/lib/module/native/svg.js.map +1 -0
  38. package/lib/module/native/vectorIcons.js +41 -0
  39. package/lib/module/native/vectorIcons.js.map +1 -0
  40. package/lib/typescript/src/components/FlowboardFlow.d.ts.map +1 -1
  41. package/lib/typescript/src/components/FlowboardRenderer.d.ts.map +1 -1
  42. package/lib/typescript/src/core/clientContext.d.ts.map +1 -1
  43. package/lib/typescript/src/core/fontAwesome.d.ts +1 -1
  44. package/lib/typescript/src/core/fontAwesome.d.ts.map +1 -1
  45. package/lib/typescript/src/core/onboardingRepository.d.ts.map +1 -1
  46. package/lib/typescript/src/native/asyncStorage.d.ts +16 -0
  47. package/lib/typescript/src/native/asyncStorage.d.ts.map +1 -0
  48. package/lib/typescript/src/native/deviceInfo.d.ts +11 -0
  49. package/lib/typescript/src/native/deviceInfo.d.ts.map +1 -0
  50. package/lib/typescript/src/native/inAppReview.d.ts +4 -0
  51. package/lib/typescript/src/native/inAppReview.d.ts.map +1 -0
  52. package/lib/typescript/src/native/linearGradient.d.ts +3 -0
  53. package/lib/typescript/src/native/linearGradient.d.ts.map +1 -0
  54. package/lib/typescript/src/native/lottie.d.ts +3 -0
  55. package/lib/typescript/src/native/lottie.d.ts.map +1 -0
  56. package/lib/typescript/src/native/maskedView.d.ts +3 -0
  57. package/lib/typescript/src/native/maskedView.d.ts.map +1 -0
  58. package/lib/typescript/src/native/pagerView.d.ts +14 -0
  59. package/lib/typescript/src/native/pagerView.d.ts.map +1 -0
  60. package/lib/typescript/src/native/permissions.d.ts +9 -0
  61. package/lib/typescript/src/native/permissions.d.ts.map +1 -0
  62. package/lib/typescript/src/native/runtime.d.ts +13 -0
  63. package/lib/typescript/src/native/runtime.d.ts.map +1 -0
  64. package/lib/typescript/src/native/safeAreaContext.d.ts +15 -0
  65. package/lib/typescript/src/native/safeAreaContext.d.ts.map +1 -0
  66. package/lib/typescript/src/native/svg.d.ts +8 -0
  67. package/lib/typescript/src/native/svg.d.ts.map +1 -0
  68. package/lib/typescript/src/native/vectorIcons.d.ts +4 -0
  69. package/lib/typescript/src/native/vectorIcons.d.ts.map +1 -0
  70. package/package.json +20 -14
  71. package/src/FlowboardProvider.tsx +1 -1
  72. package/src/components/FlowboardFlow.tsx +10 -7
  73. package/src/components/FlowboardRenderer.tsx +8 -13
  74. package/src/core/clientContext.ts +10 -32
  75. package/src/core/fontAwesome.ts +2 -9
  76. package/src/core/onboardingRepository.ts +18 -13
  77. package/src/native/asyncStorage.ts +99 -0
  78. package/src/native/deviceInfo.ts +88 -0
  79. package/src/native/inAppReview.ts +34 -0
  80. package/src/native/linearGradient.tsx +24 -0
  81. package/src/native/lottie.tsx +17 -0
  82. package/src/native/maskedView.tsx +19 -0
  83. package/src/native/pagerView.tsx +95 -0
  84. package/src/native/permissions.ts +59 -0
  85. package/src/native/runtime.ts +110 -0
  86. package/src/native/safeAreaContext.tsx +44 -0
  87. package/src/native/svg.tsx +82 -0
  88. 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 'react-native-permissions';
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<PagerView>(null);
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 (InAppReview.isAvailable()) {
331
- InAppReview.RequestInAppReview();
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 Svg, {
23
- Circle,
24
- G,
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 'react-native-pager-view';
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<PagerView>(null);
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 = DeviceInfo.getVersion();
48
- const buildNumber = DeviceInfo.getBuildNumber();
49
- const bundleId = DeviceInfo.getBundleId();
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 = DeviceInfo.getSystemVersion();
56
- const deviceType = mapDeviceType(await DeviceInfo.getDeviceType());
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 AsyncStorage.getItem(INSTALL_ID_KEY);
90
+ const stored = await FlowboardAsyncStorage.getItem(INSTALL_ID_KEY);
91
91
  if (stored) return stored;
92
92
  const installId = uuidv4();
93
- await AsyncStorage.setItem(INSTALL_ID_KEY, installId);
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':
@@ -1,4 +1,4 @@
1
- import FontAwesome6 from 'react-native-vector-icons/FontAwesome6';
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
- const anyIcon = FontAwesome6 as any;
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 AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(json));
13
- await AsyncStorage.setItem(FETCH_TIME_KEY, Date.now().toString());
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 AsyncStorage.getItem(STORAGE_KEY);
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 AsyncStorage.multiRemove([STORAGE_KEY, FETCH_TIME_KEY]);
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 AsyncStorage.setItem(PROGRESS_FLOW_KEY, params.flowId);
37
- await AsyncStorage.setItem(PROGRESS_STEP_KEY, params.stepIndex.toString());
38
- await AsyncStorage.setItem(
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 AsyncStorage.getItem(PROGRESS_FLOW_KEY);
48
+ const storedFlowId = await FlowboardAsyncStorage.getItem(PROGRESS_FLOW_KEY);
46
49
  if (storedFlowId !== flowId) return null;
47
- const value = await AsyncStorage.getItem(PROGRESS_STEP_KEY);
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 AsyncStorage.getItem(PROGRESS_FLOW_KEY);
59
+ const storedFlowId = await FlowboardAsyncStorage.getItem(PROGRESS_FLOW_KEY);
57
60
  if (storedFlowId !== flowId) return null;
58
- const jsonString = await AsyncStorage.getItem(PROGRESS_FORM_DATA_KEY);
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 AsyncStorage.multiRemove([
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>;