@applicaster/quick-brick-core 13.0.0-rc.14 → 13.0.0-rc.140
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/App/ActionsProvider/__tests__/__snapshots__/ActionsProvider.test.js.snap +7 -7
- package/App/AssetCache/__tests__/__snapshots__/assetCache.test.ts.snap +103 -103
- package/App/AssetCache/__tests__/assetCache.test.ts +3 -2
- package/App/DeepLinking/URLSchemeHandler/SchemeHandlerHooks/__tests__/__snapshots__/schemeHandlerHooks.test.ts.snap +1 -1
- package/App/DeepLinking/URLSchemeHandler/SchemeHandlerHooks/__tests__/useOpenSchemeHandler.test.tsx +45 -116
- package/App/DeepLinking/URLSchemeHandler/__tests__/__snapshots__/URLSchemeHandler.test.tsx.snap +3 -3
- package/App/DeepLinking/URLSchemeListener/URLSchemeContextProvider.tsx +30 -3
- package/App/DeepLinking/URLSchemeListener/__tests__/URLSchemeListener.test.js +40 -0
- package/App/DeepLinking/URLSchemeListener/index.tsx +1 -1
- package/App/ErrorBoundary/__tests__/errorBoundary.test.jsx +232 -0
- package/App/ErrorBoundary/__tests__/store.test.js +143 -0
- package/App/ErrorBoundary/index.tsx +45 -21
- package/App/ErrorBoundary/store.ts +26 -0
- package/App/LoadingOverlay/LoadingOverlayManager.tsx +27 -0
- package/App/LoadingOverlay/index.tsx +49 -0
- package/App/LoadingOverlay/useLoadingOverlayVisibility.tsx +18 -0
- package/App/ModalProvider/ModalBottomSheet/DraggableBottomSheet/index.tsx +25 -3
- package/App/ModalProvider/ModalBottomSheet/ModalBottomSheetFrame.tsx +68 -11
- package/App/ModalProvider/ModalBottomSheet/Overlay.tsx +5 -7
- package/App/ModalProvider/ModalBottomSheet/index.tsx +18 -46
- package/App/ModalProvider/ModalPresenter.tsx +1 -1
- package/App/ModalProvider/index.tsx +6 -3
- package/App/NavigationProvider/NavigationProvider.tsx +57 -45
- package/App/NavigationProvider/__tests__/navigationProvider.test.tsx +0 -44
- package/App/NavigationProvider/navigator/__tests__/__snapshots__/reducer.test.ts.snap +189 -189
- package/App/NetworkStatusProvider/utils.ts +0 -1
- package/App/RouteManager/__tests__/__snapshots__/routeManager.test.js.snap +2 -2
- package/App/StoreConnector/__tests__/storeConnector.test.js +17 -11
- package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.tsx +146 -0
- package/App/appLifeCycleManager/index.tsx +6 -7
- package/App/index.tsx +6 -3
- package/App/remoteContextReloader/__tests__/remoteContextReloader.test.js +11 -21
- package/App/remoteContextReloader/getRemoteContextData/getNativeRemoteContextData.ts +14 -0
- package/createZappApp/__tests__/__snapshots__/createZappApp.test.js.snap +5 -128
- package/createZappApp/__tests__/createZappApp.test.js +7 -20
- package/defaults/__tests__/defaults.test.js +3 -4
- package/index.d.ts +1 -1
- package/package.json +26 -14
- package/App/DeepLinking/URLSchemeListener/__tests__/URLSchemeListenerTest.js +0 -177
- package/App/ErrorBoundary/__tests__/__snapshots__/errorBoundary.test.js.snap +0 -9
- package/App/ErrorBoundary/__tests__/errorBoundary.test.js +0 -72
- package/App/ModalProvider/ModalChildrenWrapper/index.ios.ts +0 -1
- package/App/ModalProvider/ModalChildrenWrapper/index.web.ts +0 -1
- package/App/NavigationProvider/__tests__/__snapshots__/navigationProvider.test.tsx.snap +0 -68
- package/App/StoreConnector/__tests__/__snapshots__/storeConnector.test.js.snap +0 -35
- package/App/appLifeCycleManager/__tests__/__snapshots__/appLifeCycleManager.test.js.snap +0 -30
- package/App/appLifeCycleManager/__tests__/appLifeCycleManager.test.js +0 -134
- package/App/remoteContextReloader/__tests__/__snapshots__/remoteContextReloader.test.js.snap +0 -43
- /package/App/ModalProvider/ModalChildrenWrapper/{index.android.tsx → index.tsx} +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { View, Text, TouchableOpacity } from "react-native";
|
|
3
|
+
import { render, fireEvent, act } from "@testing-library/react-native";
|
|
4
|
+
import { useErrorStore } from "../store";
|
|
5
|
+
import { ErrorBoundary } from "../index";
|
|
6
|
+
|
|
7
|
+
jest.mock(
|
|
8
|
+
"@applicaster/zapp-react-native-ui-components/Components/ErrorScreen",
|
|
9
|
+
() => ({
|
|
10
|
+
// eslint-disable-next-line react/prop-types
|
|
11
|
+
ErrorScreen: ({ dismissError, message, buttons }) => (
|
|
12
|
+
<View testID="error-screen">
|
|
13
|
+
<Text testID="error-message">{message || "Default error message"}</Text>
|
|
14
|
+
{/* eslint-disable-next-line react/prop-types */}
|
|
15
|
+
{buttons?.map((button, index) => (
|
|
16
|
+
<TouchableOpacity
|
|
17
|
+
key={index}
|
|
18
|
+
onPress={button.onPress}
|
|
19
|
+
testID={`button-${index}`}
|
|
20
|
+
>
|
|
21
|
+
<Text>{button.text}</Text>
|
|
22
|
+
</TouchableOpacity>
|
|
23
|
+
))}
|
|
24
|
+
|
|
25
|
+
{dismissError ? (
|
|
26
|
+
<TouchableOpacity onPress={dismissError} testID="dismiss-button">
|
|
27
|
+
<Text>Dismiss Error</Text>
|
|
28
|
+
</TouchableOpacity>
|
|
29
|
+
) : null}
|
|
30
|
+
</View>
|
|
31
|
+
),
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
jest.mock("@applicaster/zapp-react-native-bridge/QuickBrick", () => ({
|
|
36
|
+
sendQuickBrickEvent: jest.fn(),
|
|
37
|
+
QUICK_BRICK_EVENTS: {
|
|
38
|
+
FORCE_APP_RELOAD: "force_app_reload",
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
jest.mock("@applicaster/zapp-react-native-utils/logger", () => ({
|
|
43
|
+
coreLogger: {
|
|
44
|
+
addSubsystem: () => ({
|
|
45
|
+
error: jest.fn(),
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line react/prop-types
|
|
51
|
+
const TestComponent = ({ error, errorMessage = "error !!" }) => {
|
|
52
|
+
if (error) {
|
|
53
|
+
throw new Error(errorMessage);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View>
|
|
58
|
+
<Text>all is good</Text>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
describe("<ErrorBoundary />", () => {
|
|
64
|
+
// Clear all error states between tests
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
useErrorStore.getState().dismissError();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("when there is no error", () => {
|
|
70
|
+
it("renders children correctly", () => {
|
|
71
|
+
const { getByText } = render(
|
|
72
|
+
<ErrorBoundary>
|
|
73
|
+
<TestComponent />
|
|
74
|
+
</ErrorBoundary>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(getByText("all is good")).toBeTruthy();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("when an error is caught", () => {
|
|
82
|
+
const _consoleLogs = {
|
|
83
|
+
error: console.error,
|
|
84
|
+
log: console.log,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
beforeAll(() => {
|
|
88
|
+
console.error = jest.fn();
|
|
89
|
+
console.log = jest.fn();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterAll(() => {
|
|
93
|
+
console.error = _consoleLogs.error;
|
|
94
|
+
console.log = _consoleLogs.log;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("renders error state with default values (recoverable error)", () => {
|
|
98
|
+
const { getByTestId } = render(
|
|
99
|
+
<ErrorBoundary>
|
|
100
|
+
<TestComponent error />
|
|
101
|
+
</ErrorBoundary>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
105
|
+
expect(getByTestId("dismiss-button")).toBeTruthy(); // Should be recoverable by default
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("handles non-recoverable errors (no dismiss button)", () => {
|
|
109
|
+
const { getByTestId, queryByTestId } = render(
|
|
110
|
+
<ErrorBoundary recoverable={false}>
|
|
111
|
+
<TestComponent error />
|
|
112
|
+
</ErrorBoundary>
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
116
|
+
expect(queryByTestId("dismiss-button")).toBeFalsy(); // Should not be recoverable
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("shows custom error message from caught error", () => {
|
|
120
|
+
const customErrorMessage = "Custom error message for testing";
|
|
121
|
+
|
|
122
|
+
const { getByTestId } = render(
|
|
123
|
+
<ErrorBoundary>
|
|
124
|
+
<TestComponent error errorMessage={customErrorMessage} />
|
|
125
|
+
</ErrorBoundary>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// The error info should be passed to the ErrorScreen
|
|
129
|
+
expect(getByTestId("error-message").props.children).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("can dismiss the error and restore the UI", () => {
|
|
133
|
+
// First render with error
|
|
134
|
+
const { getByTestId, queryByText, rerender } = render(
|
|
135
|
+
<ErrorBoundary>
|
|
136
|
+
<TestComponent error />
|
|
137
|
+
</ErrorBoundary>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Verify error screen is shown
|
|
141
|
+
expect(getByTestId("error-screen")).toBeTruthy();
|
|
142
|
+
|
|
143
|
+
// Dismiss the error
|
|
144
|
+
fireEvent.press(getByTestId("dismiss-button"));
|
|
145
|
+
|
|
146
|
+
// Force rerender to see the change
|
|
147
|
+
rerender(
|
|
148
|
+
<ErrorBoundary>
|
|
149
|
+
<TestComponent />
|
|
150
|
+
</ErrorBoundary>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Now the regular content should be visible
|
|
154
|
+
expect(queryByText("all is good")).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("error store functionality", () => {
|
|
159
|
+
it("properly sets error state", () => {
|
|
160
|
+
const error = new Error("Test error");
|
|
161
|
+
const errorInfo = "Test error info";
|
|
162
|
+
|
|
163
|
+
act(() => {
|
|
164
|
+
useErrorStore.getState().setError(error, errorInfo, true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const state = useErrorStore.getState();
|
|
168
|
+
expect(state.hasError).toBe(true);
|
|
169
|
+
expect(state.error).toBe(error);
|
|
170
|
+
expect(state.info).toBe(errorInfo);
|
|
171
|
+
expect(state.recoverable).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("properly resets error state", () => {
|
|
175
|
+
// First set an error
|
|
176
|
+
act(() => {
|
|
177
|
+
useErrorStore
|
|
178
|
+
.getState()
|
|
179
|
+
.setError(new Error("Test error"), "Info", true);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Then dismiss it
|
|
183
|
+
act(() => {
|
|
184
|
+
useErrorStore.getState().dismissError();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const state = useErrorStore.getState();
|
|
188
|
+
expect(state.hasError).toBe(false);
|
|
189
|
+
expect(state.error).toBeUndefined();
|
|
190
|
+
expect(state.info).toBeUndefined();
|
|
191
|
+
expect(state.recoverable).toBe(true); // Default value when reset
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("remembers recoverable state", () => {
|
|
195
|
+
act(() => {
|
|
196
|
+
useErrorStore
|
|
197
|
+
.getState()
|
|
198
|
+
.setError(new Error("Test error"), "Info", false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const state = useErrorStore.getState();
|
|
202
|
+
expect(state.recoverable).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("integration with ErrorScreen", () => {
|
|
207
|
+
it("renders restart app button for non-recoverable errors", () => {
|
|
208
|
+
const { getAllByTestId } = render(
|
|
209
|
+
<ErrorBoundary recoverable={false}>
|
|
210
|
+
<TestComponent error />
|
|
211
|
+
</ErrorBoundary>
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Should have the restart app button
|
|
215
|
+
const buttons = getAllByTestId(/button-/);
|
|
216
|
+
expect(buttons.length).toBeGreaterThan(0);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("passes error info to the ErrorScreen component", () => {
|
|
220
|
+
const customErrorMessage = "Very specific error message";
|
|
221
|
+
|
|
222
|
+
const { getByTestId } = render(
|
|
223
|
+
<ErrorBoundary>
|
|
224
|
+
<TestComponent error errorMessage={customErrorMessage} />
|
|
225
|
+
</ErrorBoundary>
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// The error info should be visible in the UI
|
|
229
|
+
expect(getByTestId("error-message")).toBeTruthy();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react-hooks";
|
|
2
|
+
import { useErrorStore } from "../store";
|
|
3
|
+
|
|
4
|
+
describe("Error Store", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
act(() => {
|
|
7
|
+
useErrorStore.getState().dismissError();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("Initial state", () => {
|
|
12
|
+
it("should have correct default values", () => {
|
|
13
|
+
const { result } = renderHook(() => useErrorStore());
|
|
14
|
+
|
|
15
|
+
expect(result.current).toMatchObject({
|
|
16
|
+
hasError: false,
|
|
17
|
+
error: undefined,
|
|
18
|
+
info: undefined,
|
|
19
|
+
recoverable: true,
|
|
20
|
+
setError: expect.any(Function),
|
|
21
|
+
dismissError: expect.any(Function),
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("setError action", () => {
|
|
27
|
+
it("should set error state with provided values", () => {
|
|
28
|
+
const testError = new Error("Test error");
|
|
29
|
+
const testInfo = "Test error info";
|
|
30
|
+
const isRecoverable = true;
|
|
31
|
+
|
|
32
|
+
const { result } = renderHook(() => useErrorStore());
|
|
33
|
+
|
|
34
|
+
act(() => {
|
|
35
|
+
result.current.setError(testError, testInfo, isRecoverable);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(result.current).toMatchObject({
|
|
39
|
+
hasError: true,
|
|
40
|
+
error: testError,
|
|
41
|
+
info: testInfo,
|
|
42
|
+
recoverable: isRecoverable,
|
|
43
|
+
setError: expect.any(Function),
|
|
44
|
+
dismissError: expect.any(Function),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should set non-recoverable error when specified", () => {
|
|
49
|
+
const testError = new Error("Test error");
|
|
50
|
+
const testInfo = "Test error info";
|
|
51
|
+
const isRecoverable = false;
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(() => useErrorStore());
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current.setError(testError, testInfo, isRecoverable);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(result.current).toMatchObject({
|
|
60
|
+
recoverable: false,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should default to recoverable error when not specified", () => {
|
|
65
|
+
const testError = new Error("Test error");
|
|
66
|
+
const testInfo = "Test error info";
|
|
67
|
+
|
|
68
|
+
const { result } = renderHook(() => useErrorStore());
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
result.current.setError(testError, testInfo);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.current).toMatchObject({
|
|
75
|
+
recoverable: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("dismissError action", () => {
|
|
81
|
+
it("should reset error state to defaults", () => {
|
|
82
|
+
const testError = new Error("Test error");
|
|
83
|
+
const { result } = renderHook(() => useErrorStore());
|
|
84
|
+
|
|
85
|
+
act(() => {
|
|
86
|
+
result.current.setError(testError, "info", false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(result.current).toMatchObject({
|
|
90
|
+
hasError: true,
|
|
91
|
+
recoverable: false,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
act(() => {
|
|
95
|
+
result.current.dismissError();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(result.current).toMatchObject({
|
|
99
|
+
hasError: false,
|
|
100
|
+
error: undefined,
|
|
101
|
+
info: undefined,
|
|
102
|
+
recoverable: true,
|
|
103
|
+
setError: expect.any(Function),
|
|
104
|
+
dismissError: expect.any(Function),
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("Direct store access via API", () => {
|
|
110
|
+
it("should allow direct access to store outside React components", () => {
|
|
111
|
+
act(() => {
|
|
112
|
+
useErrorStore
|
|
113
|
+
.getState()
|
|
114
|
+
.setError(new Error("API error"), "API info", false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const { result } = renderHook(() => useErrorStore());
|
|
118
|
+
|
|
119
|
+
expect(result.current).toMatchObject({
|
|
120
|
+
hasError: true,
|
|
121
|
+
error: expect.objectContaining({ message: "API error" }),
|
|
122
|
+
info: "API info",
|
|
123
|
+
recoverable: false,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should allow clearing errors via API", () => {
|
|
128
|
+
act(() => {
|
|
129
|
+
useErrorStore.getState().setError(new Error("API error"), "API info");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
useErrorStore.getState().dismissError();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const { result } = renderHook(() => useErrorStore());
|
|
137
|
+
|
|
138
|
+
expect(result.current).toMatchObject({
|
|
139
|
+
hasError: false,
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -1,42 +1,66 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import pick from "lodash/pick";
|
|
2
3
|
|
|
3
4
|
import { coreLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
4
5
|
import { ErrorScreen } from "@applicaster/zapp-react-native-ui-components/Components/ErrorScreen";
|
|
5
6
|
|
|
7
|
+
import { useErrorStore } from "./store";
|
|
8
|
+
import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickBrick";
|
|
9
|
+
|
|
6
10
|
const errorBoundaryLogger = coreLogger.addSubsystem("react_error-boundary");
|
|
7
11
|
|
|
8
12
|
type Props = {
|
|
9
13
|
children: React.ReactNode;
|
|
14
|
+
recoverable?: boolean;
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
error?: Error;
|
|
15
|
-
info?: string;
|
|
17
|
+
type ErrorBoundaryContentProps = {
|
|
18
|
+
children: React.ReactNode;
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const restartApp = () => {
|
|
22
|
+
QuickBrickManager.sendQuickBrickEvent(
|
|
23
|
+
QuickBrickManager.QUICK_BRICK_EVENTS.FORCE_APP_RELOAD
|
|
24
|
+
);
|
|
25
|
+
};
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
const restartButton = {
|
|
28
|
+
text: "Restart the app",
|
|
29
|
+
onPress: restartApp,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const buttons = [restartButton];
|
|
33
|
+
|
|
34
|
+
const ErrorBoundaryContent: React.FC<ErrorBoundaryContentProps> = ({
|
|
35
|
+
children,
|
|
36
|
+
}) => {
|
|
37
|
+
const { hasError, dismissError, info } = pick(
|
|
38
|
+
useErrorStore((state) => state),
|
|
39
|
+
["hasError", "dismissError", "info"]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (hasError) {
|
|
43
|
+
return (
|
|
44
|
+
<ErrorScreen
|
|
45
|
+
dismissError={dismissError}
|
|
46
|
+
message={info}
|
|
47
|
+
buttons={buttons}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
27
50
|
}
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
52
|
+
return <>{children}</>;
|
|
53
|
+
};
|
|
32
54
|
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
export class ErrorBoundary extends React.Component<Props> {
|
|
56
|
+
componentDidCatch(error, info) {
|
|
57
|
+
errorBoundaryLogger.error({ error, data: info, message: error.message });
|
|
58
|
+
const { recoverable = true } = this.props;
|
|
35
59
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
60
|
+
useErrorStore.getState().setError(error, info, recoverable);
|
|
61
|
+
}
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
render() {
|
|
64
|
+
return <ErrorBoundaryContent>{this.props.children}</ErrorBoundaryContent>;
|
|
41
65
|
}
|
|
42
66
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
type ErrorState = {
|
|
4
|
+
hasError: boolean;
|
|
5
|
+
error?: Error;
|
|
6
|
+
info?: string;
|
|
7
|
+
recoverable: boolean;
|
|
8
|
+
setError: (error: Error, info: string, recoverable?: boolean) => void;
|
|
9
|
+
dismissError: () => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useErrorStore = create<ErrorState>((set) => ({
|
|
13
|
+
hasError: false,
|
|
14
|
+
error: undefined,
|
|
15
|
+
info: undefined,
|
|
16
|
+
recoverable: true,
|
|
17
|
+
setError: (error: Error, info: string, recoverable = true) =>
|
|
18
|
+
set({ hasError: true, error, info, recoverable }),
|
|
19
|
+
dismissError: () =>
|
|
20
|
+
set({
|
|
21
|
+
hasError: false,
|
|
22
|
+
error: undefined,
|
|
23
|
+
info: undefined,
|
|
24
|
+
recoverable: true,
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { BehaviorSubject } from "rxjs";
|
|
2
|
+
|
|
3
|
+
class LoadingOverlayManager {
|
|
4
|
+
visibilitySubject: BehaviorSubject<boolean>;
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.visibilitySubject = new BehaviorSubject(false);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
show() {
|
|
11
|
+
this.visibilitySubject.next(true);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
hide() {
|
|
15
|
+
this.visibilitySubject.next(false);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
onVisibilityStateChange(cb: (value: boolean) => void) {
|
|
19
|
+
return this.visibilitySubject.subscribe(cb);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get visibility(): boolean {
|
|
23
|
+
return this.visibilitySubject.value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const loadingOverlayManager = new LoadingOverlayManager();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
4
|
+
import { isVizioPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
5
|
+
import { Spinner } from "@applicaster/zapp-react-native-ui-components/Components/Spinner";
|
|
6
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
7
|
+
|
|
8
|
+
import { useLoadingOverlayVisibility } from "./useLoadingOverlayVisibility";
|
|
9
|
+
|
|
10
|
+
export { loadingOverlayManager } from "./LoadingOverlayManager";
|
|
11
|
+
|
|
12
|
+
const styles = StyleSheet.create({
|
|
13
|
+
container: {
|
|
14
|
+
opacity: 0.5,
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
justifyContent: "center",
|
|
17
|
+
zIndex: 100,
|
|
18
|
+
flex: 1,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const LoadingOverlayComponent = () => {
|
|
23
|
+
const visible = useLoadingOverlayVisibility();
|
|
24
|
+
const theme = useTheme();
|
|
25
|
+
|
|
26
|
+
const backgroundColor = React.useMemo(() => {
|
|
27
|
+
return theme?.app_background_color || "black";
|
|
28
|
+
}, [theme?.app_background_color]);
|
|
29
|
+
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (visible) {
|
|
32
|
+
focusManager.disableFocus();
|
|
33
|
+
} else {
|
|
34
|
+
focusManager.enableFocus();
|
|
35
|
+
}
|
|
36
|
+
}, [visible]);
|
|
37
|
+
|
|
38
|
+
return visible ? (
|
|
39
|
+
<View
|
|
40
|
+
style={[StyleSheet.absoluteFill, styles.container, { backgroundColor }]}
|
|
41
|
+
>
|
|
42
|
+
<Spinner />
|
|
43
|
+
</View>
|
|
44
|
+
) : null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const LoadingOverlay = isVizioPlatform()
|
|
48
|
+
? LoadingOverlayComponent
|
|
49
|
+
: () => null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { loadingOverlayManager } from "./LoadingOverlayManager";
|
|
3
|
+
|
|
4
|
+
export const useLoadingOverlayVisibility = () => {
|
|
5
|
+
const [visible, setVisible] = React.useState(
|
|
6
|
+
loadingOverlayManager.visibility
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
const listener = loadingOverlayManager.onVisibilityStateChange(setVisible);
|
|
11
|
+
|
|
12
|
+
return () => {
|
|
13
|
+
listener.unsubscribe();
|
|
14
|
+
};
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
return visible;
|
|
18
|
+
};
|
|
@@ -56,6 +56,7 @@ export class DraggableBottomSheet extends Component<Props, ComponentState> {
|
|
|
56
56
|
_translateYOffset: Animated.Value;
|
|
57
57
|
_translateY: Animated.AnimatedAddition;
|
|
58
58
|
_snapPoints: SnapPoints;
|
|
59
|
+
_prevContentHeight = 0;
|
|
59
60
|
|
|
60
61
|
_callWhenMounted: () => void | null;
|
|
61
62
|
|
|
@@ -89,6 +90,14 @@ export class DraggableBottomSheet extends Component<Props, ComponentState> {
|
|
|
89
90
|
this._lastScrollY
|
|
90
91
|
);
|
|
91
92
|
|
|
93
|
+
const initialSnapPoints = new SnapPoints(props.height, props.sheetHeight);
|
|
94
|
+
|
|
95
|
+
this._translateYOffset = new Animated.Value(
|
|
96
|
+
props.visible ? initialSnapPoints.top : initialSnapPoints.bottom
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
this._prevContentHeight = initialSnapPoints.contentHeight;
|
|
100
|
+
|
|
92
101
|
this.state = {
|
|
93
102
|
lastSnap: props.height,
|
|
94
103
|
dragging: false,
|
|
@@ -150,12 +159,25 @@ export class DraggableBottomSheet extends Component<Props, ComponentState> {
|
|
|
150
159
|
|
|
151
160
|
if (!this._snapPoints) {
|
|
152
161
|
this._snapPoints = new SnapPoints(windowHeight, totalSheetHeight);
|
|
162
|
+
this._prevContentHeight = totalSheetHeight;
|
|
153
163
|
} else {
|
|
154
164
|
this._snapPoints.windowHeight = windowHeight;
|
|
155
165
|
this._snapPoints.contentHeight = totalSheetHeight;
|
|
156
|
-
}
|
|
157
166
|
|
|
158
|
-
|
|
167
|
+
if (this.props.visible && this._isMounted) {
|
|
168
|
+
Animated.timing(this._translateYOffset, {
|
|
169
|
+
toValue: this._snapPoints.top,
|
|
170
|
+
duration: ANIMATION_DURATION,
|
|
171
|
+
easing: ANIMATION_EASING,
|
|
172
|
+
delay: ANIMATION_DELAY,
|
|
173
|
+
useNativeDriver: USE_NATIVE_DRIVER,
|
|
174
|
+
}).start();
|
|
175
|
+
} else {
|
|
176
|
+
this._translateYOffset.setValue(this._snapPoints.top);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this._prevContentHeight = totalSheetHeight;
|
|
180
|
+
}
|
|
159
181
|
|
|
160
182
|
this._translateY = Animated.add(
|
|
161
183
|
this._translateYOffset,
|
|
@@ -254,6 +276,7 @@ export class DraggableBottomSheet extends Component<Props, ComponentState> {
|
|
|
254
276
|
getDraggableAreaStyles() {
|
|
255
277
|
return {
|
|
256
278
|
width: this.props.maxWidth,
|
|
279
|
+
top: 0,
|
|
257
280
|
left: (this.props.width - this.props.maxWidth) / 2 || 0,
|
|
258
281
|
};
|
|
259
282
|
}
|
|
@@ -300,7 +323,6 @@ export class DraggableBottomSheet extends Component<Props, ComponentState> {
|
|
|
300
323
|
simultaneousHandlers={this.drawer}
|
|
301
324
|
>
|
|
302
325
|
<Animated.ScrollView
|
|
303
|
-
style={{ marginBottom: this._snapPoints.top }}
|
|
304
326
|
bounces={false}
|
|
305
327
|
onScrollBeginDrag={this._onRegisterLastScroll}
|
|
306
328
|
scrollEventThrottle={1}
|