@applicaster/zapp-react-native-utils 15.0.0-alpha.2463014505 → 15.0.0-alpha.2598273579
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/analyticsUtils/AnalyticPlayerListener.ts +5 -2
- package/appUtils/accessibilityManager/hooks.ts +8 -6
- package/appUtils/accessibilityManager/index.ts +28 -1
- package/appUtils/accessibilityManager/utils.ts +36 -5
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
- package/appUtils/focusManager/index.ios.ts +18 -1
- package/appUtils/focusManagerAux/utils/index.ts +18 -0
- package/appUtils/focusManagerAux/utils/utils.ios.ts +35 -0
- package/appUtils/platform/platformUtils.ts +116 -17
- package/appUtils/playerManager/conts.ts +21 -0
- package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
- package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
- package/arrayUtils/index.ts +5 -0
- package/cellUtils/index.ts +32 -0
- package/manifestUtils/keys.js +21 -0
- package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
- package/navigationUtils/index.ts +19 -16
- package/package.json +2 -2
- package/playerUtils/usePlayerTTS.ts +5 -2
- package/reactHooks/feed/useBatchLoading.ts +7 -1
- package/reactHooks/feed/useFeedLoader.tsx +0 -9
- package/reactHooks/feed/useInflatedUrl.ts +23 -29
- package/reactHooks/feed/usePipesCacheReset.ts +3 -1
- package/reactHooks/layout/index.ts +1 -1
- package/utils/__tests__/mapAccum.test.ts +73 -0
- package/utils/index.ts +7 -0
- package/utils/mapAccum.ts +23 -0
- package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +60 -31
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
- package/zappFrameworkUtils/HookCallback/useCallbackActions.ts +1 -7
|
@@ -35,8 +35,11 @@ export class AnalyticPlayerListener
|
|
|
35
35
|
this.handleAnalyticEvent(PLAYBACK_EVENT.complete);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
onError = (err:
|
|
39
|
-
this.handleAnalyticEvent(
|
|
38
|
+
onError = (err: QuickBrickPlayer.PlayerErrorI) => {
|
|
39
|
+
this.handleAnalyticEvent(
|
|
40
|
+
PLAYBACK_EVENT.error,
|
|
41
|
+
err.toObject?.() || { message: err.message }
|
|
42
|
+
);
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
onPlayerPause = (event) => {
|
|
@@ -17,6 +17,9 @@ export const useAccessibilityManager = (
|
|
|
17
17
|
return AccessibilityManager.getInstance();
|
|
18
18
|
}, []);
|
|
19
19
|
|
|
20
|
+
const [accessibilityManagerState, setAccessibilityManagerState] =
|
|
21
|
+
useState<AccessibilityState>(accessibilityManager.getState());
|
|
22
|
+
|
|
20
23
|
useEffect(() => {
|
|
21
24
|
if (pluginConfiguration) {
|
|
22
25
|
accessibilityManager.updateLocalizations(pluginConfiguration);
|
|
@@ -25,18 +28,17 @@ export const useAccessibilityManager = (
|
|
|
25
28
|
|
|
26
29
|
useEffect(() => {
|
|
27
30
|
const subscription = accessibilityManager.getStateAsObservable().subscribe({
|
|
28
|
-
next: () => {
|
|
29
|
-
|
|
30
|
-
// screenReaderEnabled: false
|
|
31
|
-
// reduceMotionEnabled: false
|
|
32
|
-
// boldTextEnabled: false
|
|
31
|
+
next: (newState) => {
|
|
32
|
+
setAccessibilityManagerState(newState);
|
|
33
33
|
},
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
return () => subscription.unsubscribe();
|
|
37
37
|
}, [accessibilityManager]);
|
|
38
38
|
|
|
39
|
-
return accessibilityManager
|
|
39
|
+
return Object.assign(accessibilityManager, {
|
|
40
|
+
accessibilityManagerState,
|
|
41
|
+
});
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
export const useInitialAnnouncementReady = (
|
|
@@ -36,7 +36,20 @@ export class AccessibilityManager {
|
|
|
36
36
|
false
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
-
private constructor() {
|
|
39
|
+
private constructor() {
|
|
40
|
+
this.ttsManager
|
|
41
|
+
.getScreenReaderEnabledAsObservable()
|
|
42
|
+
.subscribe((enabled) => {
|
|
43
|
+
const state = this.state$.getValue();
|
|
44
|
+
|
|
45
|
+
if (state.screenReaderEnabled !== enabled) {
|
|
46
|
+
this.state$.next({
|
|
47
|
+
...state,
|
|
48
|
+
screenReaderEnabled: enabled,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
public static getInstance(): AccessibilityManager {
|
|
42
55
|
if (!AccessibilityManager._instance) {
|
|
@@ -92,8 +105,15 @@ export class AccessibilityManager {
|
|
|
92
105
|
/**
|
|
93
106
|
* Adds a heading to the queue, headings will be read before the next text
|
|
94
107
|
* Each heading will be read once and removed from the queue
|
|
108
|
+
* Does nothing if screen reader is not enabled
|
|
95
109
|
*/
|
|
96
110
|
public addHeading(heading: string) {
|
|
111
|
+
const state = this.state$.getValue();
|
|
112
|
+
|
|
113
|
+
if (!state.screenReaderEnabled) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
97
117
|
if (!this.pendingFocusId) {
|
|
98
118
|
this.pendingFocusId = Date.now().toString();
|
|
99
119
|
}
|
|
@@ -108,6 +128,7 @@ export class AccessibilityManager {
|
|
|
108
128
|
*
|
|
109
129
|
* Implements a delay mechanism to reduce noise during rapid navigation.
|
|
110
130
|
* Only the most recent announcement will be read after the delay period.
|
|
131
|
+
* Does nothing if screen reader is not enabled
|
|
111
132
|
*/
|
|
112
133
|
public readText({
|
|
113
134
|
text,
|
|
@@ -116,6 +137,12 @@ export class AccessibilityManager {
|
|
|
116
137
|
text: string;
|
|
117
138
|
keyOfLocalizedText?: string;
|
|
118
139
|
}) {
|
|
140
|
+
const state = this.state$.getValue();
|
|
141
|
+
|
|
142
|
+
if (!state.screenReaderEnabled) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
119
146
|
let textToRead = text;
|
|
120
147
|
|
|
121
148
|
if (keyOfLocalizedText) {
|
|
@@ -12,13 +12,44 @@ export function calculateReadingTime(
|
|
|
12
12
|
minimumPause: number = 500,
|
|
13
13
|
announcementDelay: number = 700
|
|
14
14
|
): number {
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const trimmed = text.trim();
|
|
16
|
+
|
|
17
|
+
// Count words (split on whitespace and punctuation, keep alnum boundaries)
|
|
18
|
+
const words = trimmed
|
|
17
19
|
.split(/(?<=\d)(?=[a-zA-Z])|(?<=[a-zA-Z])(?=\d)|[^\w\s]+|\s+/)
|
|
18
20
|
.filter(Boolean).length;
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// Count spaces - multiple consecutive spaces add extra pause time
|
|
23
|
+
const spaceMatches: string[] = trimmed.match(/\s+/g) || [];
|
|
24
|
+
|
|
25
|
+
const totalSpaces = spaceMatches.reduce(
|
|
26
|
+
(sum: number, match: string) => sum + match.length,
|
|
27
|
+
0
|
|
23
28
|
);
|
|
29
|
+
|
|
30
|
+
const extraSpaces = Math.max(0, totalSpaces - (words - 1)); // words-1 is normal spacing
|
|
31
|
+
|
|
32
|
+
// Heuristic: punctuation increases TTS duration beyond word-based WPM.
|
|
33
|
+
// Commas typically introduce short pauses, sentence terminators longer ones.
|
|
34
|
+
const commaCount = (trimmed.match(/,/g) || []).length;
|
|
35
|
+
const semicolonCount = (trimmed.match(/;/g) || []).length;
|
|
36
|
+
const colonCount = (trimmed.match(/:/g) || []).length;
|
|
37
|
+
const dashCount = (trimmed.match(/\u2013|\u2014|-/g) || []).length; // – — -
|
|
38
|
+
const sentenceEndCount = (trimmed.match(/[.!?](?!\d)/g) || []).length;
|
|
39
|
+
|
|
40
|
+
const commaPauseMs = 220; // short prosody pause for ","
|
|
41
|
+
const midPauseMs = 260; // for ";", ":", dashes
|
|
42
|
+
const sentenceEndPauseMs = 420; // for ".", "!", "?"
|
|
43
|
+
const extraSpacePauseMs = 50; // per extra space beyond normal spacing
|
|
44
|
+
|
|
45
|
+
const punctuationPause =
|
|
46
|
+
commaCount * commaPauseMs +
|
|
47
|
+
(semicolonCount + colonCount + dashCount) * midPauseMs +
|
|
48
|
+
sentenceEndCount * sentenceEndPauseMs +
|
|
49
|
+
extraSpaces * extraSpacePauseMs;
|
|
50
|
+
|
|
51
|
+
const baseByWordsMs = (words / wordsPerMinute) * 60 * 1000;
|
|
52
|
+
const estimatedMs = Math.max(minimumPause, baseByWordsMs + punctuationPause);
|
|
53
|
+
|
|
54
|
+
return estimatedMs + announcementDelay;
|
|
24
55
|
}
|
|
@@ -69,6 +69,7 @@ exports[`focusManagerIOS should be defined 1`] = `
|
|
|
69
69
|
"getGroupRootById": [Function],
|
|
70
70
|
"getPreferredFocusChild": [Function],
|
|
71
71
|
"invokeHandler": [Function],
|
|
72
|
+
"isChildOf": [Function],
|
|
72
73
|
"isFocusOn": [Function],
|
|
73
74
|
"isGroupItemFocused": [Function],
|
|
74
75
|
"moveFocus": [Function],
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { NativeModules } from "react-native";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isCurrentFocusOn,
|
|
6
|
+
isChildOf as isChildOfUtils,
|
|
7
|
+
} from "../focusManagerAux/utils";
|
|
5
8
|
import { Tree } from "./treeDataStructure/Tree";
|
|
6
9
|
import { findFocusableNode } from "./treeDataStructure/Utils";
|
|
7
10
|
import { subscriber } from "../../functionUtils";
|
|
8
11
|
import { findChild } from "./utils";
|
|
9
12
|
|
|
13
|
+
import {
|
|
14
|
+
emitRegistered,
|
|
15
|
+
emitUnregistered,
|
|
16
|
+
} from "../focusManagerAux/utils/utils.ios";
|
|
17
|
+
|
|
10
18
|
const { FocusableManagerModule } = NativeModules;
|
|
11
19
|
|
|
12
20
|
/**
|
|
@@ -180,10 +188,14 @@ export const focusManager = (function () {
|
|
|
180
188
|
function register({ id, component }) {
|
|
181
189
|
const { isGroup = false } = component;
|
|
182
190
|
|
|
191
|
+
emitRegistered(id);
|
|
192
|
+
|
|
183
193
|
return isGroup ? registerGroup(id, component) : registerItem(id, component);
|
|
184
194
|
}
|
|
185
195
|
|
|
186
196
|
function unregister(id, { group = false } = {}) {
|
|
197
|
+
emitUnregistered(id);
|
|
198
|
+
|
|
187
199
|
group ? unregisterGroup(id) : unregisterItem(id);
|
|
188
200
|
}
|
|
189
201
|
|
|
@@ -400,6 +412,10 @@ export const focusManager = (function () {
|
|
|
400
412
|
return id && isCurrentFocusOn(id, currentFocusNode);
|
|
401
413
|
}
|
|
402
414
|
|
|
415
|
+
function isChildOf(childId, parentId): boolean {
|
|
416
|
+
return isChildOfUtils(focusableTree, childId, parentId);
|
|
417
|
+
}
|
|
418
|
+
|
|
403
419
|
return {
|
|
404
420
|
on,
|
|
405
421
|
invokeHandler,
|
|
@@ -422,5 +438,6 @@ export const focusManager = (function () {
|
|
|
422
438
|
isGroupItemFocused,
|
|
423
439
|
getPreferredFocusChild,
|
|
424
440
|
isFocusOn,
|
|
441
|
+
isChildOf,
|
|
425
442
|
};
|
|
426
443
|
})();
|
|
@@ -190,3 +190,21 @@ export const isCurrentFocusOn = (id, node) => {
|
|
|
190
190
|
|
|
191
191
|
return isCurrentFocusOn(id, node.parent);
|
|
192
192
|
};
|
|
193
|
+
|
|
194
|
+
export const isChildOf = (focusableTree, childId, parentId) => {
|
|
195
|
+
if (isNil(childId) || isNil(parentId)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const childNode = focusableTree.findInTree(childId);
|
|
200
|
+
|
|
201
|
+
if (isNil(childNode)) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (childNode.parent?.id === parentId) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return isChildOf(focusableTree, childNode.parent?.id, parentId);
|
|
210
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReplaySubject } from "rxjs";
|
|
2
|
+
import { filter } from "rxjs/operators";
|
|
3
|
+
import { BUTTON_PREFIX } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const";
|
|
4
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
5
|
+
|
|
6
|
+
type FocusableID = string;
|
|
7
|
+
type RegistrationEvent = {
|
|
8
|
+
id: FocusableID;
|
|
9
|
+
registered: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const isFocusableButton = (id: Option<FocusableID>): boolean =>
|
|
13
|
+
id && id.includes?.(BUTTON_PREFIX);
|
|
14
|
+
|
|
15
|
+
const registeredSubject$ = new ReplaySubject<RegistrationEvent>(1);
|
|
16
|
+
|
|
17
|
+
export const focusableButtonsRegistration$ = (focusableGroupId: string) =>
|
|
18
|
+
registeredSubject$.pipe(
|
|
19
|
+
filter(
|
|
20
|
+
(value) =>
|
|
21
|
+
value.registered && focusManager.isChildOf(value.id, focusableGroupId)
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const emitRegistered = (id: Option<FocusableID>): void => {
|
|
26
|
+
if (isFocusableButton(id)) {
|
|
27
|
+
registeredSubject$.next({ id, registered: true });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const emitUnregistered = (id: Option<FocusableID>): void => {
|
|
32
|
+
if (isFocusableButton(id)) {
|
|
33
|
+
registeredSubject$.next({ id, registered: false });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -170,7 +170,9 @@ export const getClosedCaptionState = () => {
|
|
|
170
170
|
*/
|
|
171
171
|
export class TTSManager {
|
|
172
172
|
private ttsState$ = new BehaviorSubject<boolean>(false);
|
|
173
|
+
private screenReaderEnabled$ = new BehaviorSubject<boolean>(false);
|
|
173
174
|
private static ttsManagerInstance: TTSManager;
|
|
175
|
+
private samsungListenerId: number | null = null;
|
|
174
176
|
|
|
175
177
|
private constructor() {
|
|
176
178
|
this.initialize();
|
|
@@ -185,23 +187,116 @@ export class TTSManager {
|
|
|
185
187
|
}
|
|
186
188
|
|
|
187
189
|
async initialize() {
|
|
188
|
-
if (
|
|
190
|
+
if (isVizioPlatform()) {
|
|
191
|
+
document.addEventListener(
|
|
192
|
+
"VIZIO_TTS_ENABLED",
|
|
193
|
+
() => {
|
|
194
|
+
log_debug("Vizio screen reader enabled");
|
|
195
|
+
this.screenReaderEnabled$.next(true);
|
|
196
|
+
},
|
|
197
|
+
false
|
|
198
|
+
);
|
|
189
199
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
document.addEventListener(
|
|
201
|
+
"VIZIO_TTS_DISABLED",
|
|
202
|
+
() => {
|
|
203
|
+
log_debug("Vizio screen reader disabled");
|
|
204
|
+
this.screenReaderEnabled$.next(false);
|
|
205
|
+
},
|
|
206
|
+
false
|
|
207
|
+
);
|
|
208
|
+
}
|
|
197
209
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
if (isLgPlatform() && window.webOS?.service) {
|
|
211
|
+
try {
|
|
212
|
+
// https://webostv.developer.lge.com/develop/references/settings-service
|
|
213
|
+
window.webOS.service.request("luna://com.webos.settingsservice", {
|
|
214
|
+
method: "getSystemSettings",
|
|
215
|
+
parameters: {
|
|
216
|
+
category: "option",
|
|
217
|
+
keys: ["audioGuidance"],
|
|
218
|
+
subscribe: true, // Request a subscription to changes
|
|
219
|
+
},
|
|
220
|
+
onSuccess: (response: any) => {
|
|
221
|
+
const isEnabled = response?.settings?.audioGuidance === "on";
|
|
222
|
+
|
|
223
|
+
log_debug("LG Audio Guidance status changed", {
|
|
224
|
+
isEnabled,
|
|
225
|
+
response,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this.screenReaderEnabled$.next(isEnabled);
|
|
229
|
+
},
|
|
230
|
+
onFailure: (error: any) => {
|
|
231
|
+
log_debug("webOS settings subscription failed", { error });
|
|
232
|
+
this.screenReaderEnabled$.next(false);
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
} catch (error) {
|
|
236
|
+
log_debug("webOS settings service request error", { error });
|
|
237
|
+
// Fallback to true if the service is not available
|
|
238
|
+
this.screenReaderEnabled$.next(true);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isSamsungPlatform() && typeof window.webapis !== "undefined") {
|
|
243
|
+
try {
|
|
244
|
+
if (
|
|
245
|
+
window.webapis?.tvinfo &&
|
|
246
|
+
typeof window.webapis.tvinfo.getMenuValue === "function" &&
|
|
247
|
+
typeof window.webapis.tvinfo.addCaptionChangeListener === "function"
|
|
248
|
+
) {
|
|
249
|
+
// Get initial Voice Guide status
|
|
250
|
+
const initialStatus = window.webapis.tvinfo.getMenuValue(
|
|
251
|
+
window.webapis.tvinfo.TvInfoMenuKey.VOICE_GUIDE_KEY
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const isEnabled =
|
|
255
|
+
initialStatus === window.webapis.tvinfo.TvInfoMenuValue.ON;
|
|
256
|
+
|
|
257
|
+
log_debug("Samsung Voice Guide initial status", {
|
|
258
|
+
isEnabled,
|
|
259
|
+
initialStatus,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
this.screenReaderEnabled$.next(isEnabled);
|
|
263
|
+
|
|
264
|
+
// Listen for Voice Guide status changes
|
|
265
|
+
const onChange = () => {
|
|
266
|
+
const currentStatus = window.webapis.tvinfo.getMenuValue(
|
|
267
|
+
window.webapis.tvinfo.TvInfoMenuKey.VOICE_GUIDE_KEY
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const enabled =
|
|
271
|
+
currentStatus === window.webapis.tvinfo.TvInfoMenuValue.ON;
|
|
272
|
+
|
|
273
|
+
log_debug("Samsung Voice Guide status changed", {
|
|
274
|
+
enabled,
|
|
275
|
+
currentStatus,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
this.screenReaderEnabled$.next(enabled);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
this.samsungListenerId =
|
|
282
|
+
window.webapis.tvinfo.addCaptionChangeListener(
|
|
283
|
+
window.webapis.tvinfo.TvInfoMenuKey.VOICE_GUIDE_KEY,
|
|
284
|
+
onChange
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
log_debug("Samsung Voice Guide listener registered", {
|
|
288
|
+
listenerId: this.samsungListenerId,
|
|
289
|
+
});
|
|
290
|
+
} else {
|
|
291
|
+
log_debug("Samsung TvInfo API not available");
|
|
292
|
+
this.screenReaderEnabled$.next(false);
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
log_debug("Samsung Voice Guide listener error", { error });
|
|
296
|
+
// Fallback to true if the service is not available
|
|
297
|
+
this.screenReaderEnabled$.next(true);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
205
300
|
}
|
|
206
301
|
|
|
207
302
|
getCurrentState(): boolean {
|
|
@@ -212,6 +307,10 @@ export class TTSManager {
|
|
|
212
307
|
return this.ttsState$.asObservable();
|
|
213
308
|
}
|
|
214
309
|
|
|
310
|
+
getScreenReaderEnabledAsObservable() {
|
|
311
|
+
return this.screenReaderEnabled$.asObservable();
|
|
312
|
+
}
|
|
313
|
+
|
|
215
314
|
readText(text: string) {
|
|
216
315
|
this.ttsState$.next(true);
|
|
217
316
|
|
|
@@ -229,14 +328,14 @@ export class TTSManager {
|
|
|
229
328
|
try {
|
|
230
329
|
window.webOS.service.request("luna://com.webos.service.tts", {
|
|
231
330
|
method: "speak",
|
|
232
|
-
onFailure(error: any) {
|
|
331
|
+
onFailure: (error: any) => {
|
|
233
332
|
log_debug("There was a failure setting up webOS TTS service", {
|
|
234
333
|
error,
|
|
235
334
|
});
|
|
236
335
|
|
|
237
336
|
this.ttsState$.next(false);
|
|
238
337
|
},
|
|
239
|
-
onSuccess(response: any) {
|
|
338
|
+
onSuccess: (response: any) => {
|
|
240
339
|
log_debug("webOS TTS service is configured successfully", {
|
|
241
340
|
response,
|
|
242
341
|
});
|
|
@@ -2,6 +2,27 @@ export const userPreferencesNamespace = "user_preferences";
|
|
|
2
2
|
|
|
3
3
|
export const skipActionType = "show_skip";
|
|
4
4
|
|
|
5
|
+
export class PlayerError
|
|
6
|
+
extends Error
|
|
7
|
+
implements QuickBrickPlayer.PlayerErrorI
|
|
8
|
+
{
|
|
9
|
+
description: string;
|
|
10
|
+
|
|
11
|
+
constructor(message: string, description: string) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.description = description;
|
|
14
|
+
|
|
15
|
+
Object.setPrototypeOf(this, PlayerError.prototype);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
toObject() {
|
|
19
|
+
return {
|
|
20
|
+
error: this.message,
|
|
21
|
+
message: this.description,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
5
26
|
export enum SharedPlayerCallBacksKeys {
|
|
6
27
|
OnPlayerResume = "onPlayerResume",
|
|
7
28
|
OnPlayerPause = "onPlayerPause",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { allTruthy } from "..";
|
|
2
|
+
|
|
3
|
+
describe("allTruthy", () => {
|
|
4
|
+
it("should return true when all values are true", () => {
|
|
5
|
+
expect(allTruthy([true, true, true])).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("should return false when at least one value is false", () => {
|
|
9
|
+
expect(allTruthy([true, false, true])).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should return false when all values are false", () => {
|
|
13
|
+
expect(allTruthy([false, false, false])).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should return false for an empty array", () => {
|
|
17
|
+
expect(allTruthy([])).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should handle single-element arrays correctly", () => {
|
|
21
|
+
expect(allTruthy([true])).toBe(true);
|
|
22
|
+
expect(allTruthy([false])).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { anyTruthy } from "..";
|
|
2
|
+
|
|
3
|
+
describe("anyTruthy", () => {
|
|
4
|
+
it("should return true when at least one value is true", () => {
|
|
5
|
+
expect(anyTruthy([false, true, false])).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("should return false when all values are false", () => {
|
|
9
|
+
expect(anyTruthy([false, false, false])).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should return true when all values are true", () => {
|
|
13
|
+
expect(anyTruthy([true, true, true])).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should return false for an empty array", () => {
|
|
17
|
+
expect(anyTruthy([])).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should handle single-element arrays correctly", () => {
|
|
21
|
+
expect(anyTruthy([true])).toBe(true);
|
|
22
|
+
expect(anyTruthy([false])).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
});
|
package/arrayUtils/index.ts
CHANGED
package/cellUtils/index.ts
CHANGED
|
@@ -505,3 +505,35 @@ export const getImageContainerMarginStyles = ({ value }: { value: any }) => {
|
|
|
505
505
|
marginRight: value("image_margin_right"),
|
|
506
506
|
};
|
|
507
507
|
};
|
|
508
|
+
|
|
509
|
+
export const isTextLabel = (key: string): boolean =>
|
|
510
|
+
key.includes("text_label_") && key.endsWith("_switch");
|
|
511
|
+
|
|
512
|
+
export const hasTextLabelsEnabled = (
|
|
513
|
+
configuration: Record<string, any>
|
|
514
|
+
): boolean => {
|
|
515
|
+
const textLabelsKeys = Object.keys(configuration).filter(isTextLabel);
|
|
516
|
+
|
|
517
|
+
const picked = textLabelsKeys.reduce(
|
|
518
|
+
(acc, key) => {
|
|
519
|
+
acc[key] = configuration[key];
|
|
520
|
+
|
|
521
|
+
return acc;
|
|
522
|
+
},
|
|
523
|
+
{} as Record<string, any>
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
const pickedValues = Object.values(picked);
|
|
527
|
+
|
|
528
|
+
return pickedValues.some((value) => {
|
|
529
|
+
if (typeof value === "boolean") {
|
|
530
|
+
return value === true;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (typeof value === "string") {
|
|
534
|
+
return value !== "" && value.toLowerCase() !== "false";
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return Boolean(value);
|
|
538
|
+
});
|
|
539
|
+
};
|
package/manifestUtils/keys.js
CHANGED
|
@@ -959,6 +959,27 @@ const TV_CELL_LABEL_FIELDS = [
|
|
|
959
959
|
rules: "conditional",
|
|
960
960
|
conditions: [{ key: "switch", section: "styles", value: true }],
|
|
961
961
|
},
|
|
962
|
+
{
|
|
963
|
+
type: ZAPPIFEST_FIELDS.font_selector.roku,
|
|
964
|
+
suffix: "roku font family",
|
|
965
|
+
tooltip: "",
|
|
966
|
+
rules: "conditional",
|
|
967
|
+
conditions: [{ key: "switch", section: "styles", value: true }],
|
|
968
|
+
},
|
|
969
|
+
{
|
|
970
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
971
|
+
suffix: "roku font size",
|
|
972
|
+
tooltip: "",
|
|
973
|
+
rules: "conditional",
|
|
974
|
+
conditions: [{ key: "switch", section: "styles", value: true }],
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
type: ZAPPIFEST_FIELDS.number_input,
|
|
978
|
+
suffix: "roku line height",
|
|
979
|
+
tooltip: "",
|
|
980
|
+
rules: "conditional",
|
|
981
|
+
conditions: [{ key: "switch", section: "styles", value: true }],
|
|
982
|
+
},
|
|
962
983
|
{
|
|
963
984
|
type: ZAPPIFEST_FIELDS.select,
|
|
964
985
|
suffix: "text alignment",
|
package/navigationUtils/index.ts
CHANGED
|
@@ -575,24 +575,27 @@ export function routeIsPlayerScreen(currentRoute) {
|
|
|
575
575
|
return currentRoute?.includes("/playable");
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
export const getNavBarProps =
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
578
|
+
export const getNavBarProps = (
|
|
579
|
+
currentRiver: ZappRiver,
|
|
580
|
+
pathname: string,
|
|
581
|
+
title: string
|
|
582
|
+
) => {
|
|
583
|
+
const props = getNavigationPropsV2({
|
|
584
|
+
currentRiver,
|
|
585
|
+
title,
|
|
586
|
+
category: "nav_bar",
|
|
587
|
+
});
|
|
585
588
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
589
|
+
if (props) {
|
|
590
|
+
return {
|
|
591
|
+
...props,
|
|
592
|
+
id: pathname,
|
|
593
|
+
pathname: pathname,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
593
596
|
|
|
594
|
-
|
|
595
|
-
|
|
597
|
+
return null;
|
|
598
|
+
};
|
|
596
599
|
|
|
597
600
|
export const findMenuPlugin = (
|
|
598
601
|
navigations: ZappNavigation[],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "15.0.0-alpha.
|
|
3
|
+
"version": "15.0.0-alpha.2598273579",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "15.0.0-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "15.0.0-alpha.2598273579",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -7,8 +7,11 @@ export const usePlayerTTS = () => {
|
|
|
7
7
|
const player = usePlayer();
|
|
8
8
|
const accessibilityManager = useAccessibilityManager({});
|
|
9
9
|
|
|
10
|
+
const isScreenReaderEnabled =
|
|
11
|
+
accessibilityManager.accessibilityManagerState.screenReaderEnabled;
|
|
12
|
+
|
|
10
13
|
React.useEffect(() => {
|
|
11
|
-
if (player && accessibilityManager) {
|
|
14
|
+
if (player && accessibilityManager && isScreenReaderEnabled) {
|
|
12
15
|
const playerTTS = new PlayerTTS(player, accessibilityManager);
|
|
13
16
|
const unsubscribe = playerTTS.init();
|
|
14
17
|
|
|
@@ -17,5 +20,5 @@ export const usePlayerTTS = () => {
|
|
|
17
20
|
playerTTS.destroy();
|
|
18
21
|
};
|
|
19
22
|
}
|
|
20
|
-
}, [player, accessibilityManager]);
|
|
23
|
+
}, [player, accessibilityManager, isScreenReaderEnabled]);
|
|
21
24
|
};
|
|
@@ -151,7 +151,13 @@ export const useBatchLoading = (
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
});
|
|
154
|
-
}, [
|
|
154
|
+
}, [
|
|
155
|
+
batchComponents,
|
|
156
|
+
feeds,
|
|
157
|
+
getUrl,
|
|
158
|
+
loadPipesDataDispatcher,
|
|
159
|
+
options.riverId,
|
|
160
|
+
]);
|
|
155
161
|
|
|
156
162
|
React.useEffect(() => {
|
|
157
163
|
runBatchLoading();
|
|
@@ -37,15 +37,6 @@ export const useFeedLoader = ({
|
|
|
37
37
|
mapping,
|
|
38
38
|
pipesOptions = {},
|
|
39
39
|
}: Props): FeedLoaderResponse => {
|
|
40
|
-
useEffect(() => {
|
|
41
|
-
if (!feedUrl) {
|
|
42
|
-
logger.warning({
|
|
43
|
-
message: "Required parameter feedUrl is missing",
|
|
44
|
-
data: { feedUrl },
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}, []);
|
|
48
|
-
|
|
49
40
|
const isInitialRender = useIsInitialRender();
|
|
50
41
|
|
|
51
42
|
const callableFeedUrl = useInflatedUrl({ feedUrl, mapping });
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "@applicaster/zapp-pipes-v2-client";
|
|
19
19
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
20
20
|
import { ENDPOINT_TAGS } from "../../types";
|
|
21
|
+
import { isNilOrEmpty } from "../../reactUtils/helpers";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* will match any occurrence in a string of one or more word characters
|
|
@@ -75,15 +76,19 @@ export const getInflatedDataSourceUrl: GetInflatedDataSourceUrl = ({
|
|
|
75
76
|
* https://foo.com/shows/A1234
|
|
76
77
|
*/
|
|
77
78
|
|
|
78
|
-
if (!
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
if (!isNilOrEmpty(mapping)) {
|
|
80
|
+
if (!source) {
|
|
81
|
+
if (__DEV__) {
|
|
82
|
+
// eslint-disable-next-line no-console
|
|
83
|
+
throw new Error(
|
|
84
|
+
"getInflatedDataSourceUrl: source is empty while mapping is provided"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
return source || null;
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
// Hack because in tv we expect to get key names instead of values from the fake entry
|
|
@@ -193,28 +198,17 @@ export function useInflatedUrl({
|
|
|
193
198
|
|
|
194
199
|
const url = useMemo(
|
|
195
200
|
() =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
: feedUrl,
|
|
207
|
-
[feedUrl, mapping]
|
|
201
|
+
getInflatedDataSourceUrl({
|
|
202
|
+
source: feedUrl,
|
|
203
|
+
contexts: {
|
|
204
|
+
entry: entryContext,
|
|
205
|
+
screen: screenContext,
|
|
206
|
+
search: getSearchContext(searchContext, mapping),
|
|
207
|
+
},
|
|
208
|
+
mapping,
|
|
209
|
+
}),
|
|
210
|
+
[entryContext, feedUrl, mapping, screenContext, searchContext]
|
|
208
211
|
);
|
|
209
212
|
|
|
210
|
-
if (!feedUrl) {
|
|
211
|
-
logger.warning({
|
|
212
|
-
message: "Required parameter feedUrl is missing",
|
|
213
|
-
data: { feedUrl },
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
return null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
213
|
return url;
|
|
220
214
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { mapAccum } from "../mapAccum";
|
|
2
|
+
|
|
3
|
+
describe("mapAccum", () => {
|
|
4
|
+
it("using standard ramda test", () => {
|
|
5
|
+
const digits = ["1", "2", "3", "4"];
|
|
6
|
+
const appender = (a, b) => [a + b, a + b];
|
|
7
|
+
|
|
8
|
+
const [acc, result] = mapAccum(appender, 0, digits); //= > ['01234', ['01', '012', '0123', '01234']]
|
|
9
|
+
expect(acc).toBe("01234");
|
|
10
|
+
expect(result).toEqual(["01", "012", "0123", "01234"]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("maps and accumulates over an array", () => {
|
|
14
|
+
const fn = (acc, x) => [acc + x, x * 2];
|
|
15
|
+
const [acc, result] = mapAccum(fn, 0, [1, 2, 3]);
|
|
16
|
+
|
|
17
|
+
expect(acc).toBe(6); // final accumulator (0 + 1 + 2 + 3)
|
|
18
|
+
expect(result).toEqual([2, 4, 6]); // mapped values (acc + x*2 at each step)
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns initial accumulator for empty array", () => {
|
|
22
|
+
const fn = (acc, x) => [acc + x, acc * x];
|
|
23
|
+
const [acc, result] = mapAccum(fn, 10, []);
|
|
24
|
+
|
|
25
|
+
expect(acc).toBe(10);
|
|
26
|
+
expect(result).toEqual([]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("accumulates strings correctly", () => {
|
|
30
|
+
const fn = (acc, x) => [acc + x, acc + x];
|
|
31
|
+
const [acc, result] = mapAccum(fn, "A", ["B", "C", "D"]);
|
|
32
|
+
|
|
33
|
+
expect(acc).toBe("ABCD");
|
|
34
|
+
expect(result).toEqual(["AB", "ABC", "ABCD"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("works with objects as accumulator", () => {
|
|
38
|
+
const fn = (acc, x) => {
|
|
39
|
+
const newAcc = { sum: acc.sum + x };
|
|
40
|
+
|
|
41
|
+
return [newAcc, newAcc.sum];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const [acc, result] = mapAccum(fn, { sum: 0 }, [1, 2, 3]);
|
|
45
|
+
|
|
46
|
+
expect(acc).toEqual({ sum: 6 });
|
|
47
|
+
expect(result).toEqual([1, 3, 6]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("is curried", () => {
|
|
51
|
+
const fn = (acc, x) => [acc + x, x * 2];
|
|
52
|
+
const mapWithFn = mapAccum(fn);
|
|
53
|
+
const withInit = mapWithFn(2);
|
|
54
|
+
const [acc, result] = withInit([1, 2, 3]);
|
|
55
|
+
|
|
56
|
+
expect(acc).toBe(8);
|
|
57
|
+
expect(result).toEqual([2, 4, 6]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("does not mutate the original array", () => {
|
|
61
|
+
const arr = [1, 2, 3];
|
|
62
|
+
mapAccum((acc, x) => [acc + x, acc + x], 0, arr);
|
|
63
|
+
expect(arr).toEqual([1, 2, 3]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("handles mixed types in accumulator and result", () => {
|
|
67
|
+
const fn = (acc, x) => [acc + x.length, acc + "-" + x];
|
|
68
|
+
const [acc, result] = mapAccum(fn, 0, ["a", "bb", "ccc"]);
|
|
69
|
+
|
|
70
|
+
expect(acc).toBe(6);
|
|
71
|
+
expect(result).toEqual(["0-a", "1-bb", "3-ccc"]);
|
|
72
|
+
});
|
|
73
|
+
});
|
package/utils/index.ts
CHANGED
|
@@ -16,6 +16,8 @@ export { endsWith } from "./endsWith";
|
|
|
16
16
|
|
|
17
17
|
export { take } from "./take";
|
|
18
18
|
|
|
19
|
+
export { mapAccum } from "./mapAccum";
|
|
20
|
+
|
|
19
21
|
export {
|
|
20
22
|
cloneDeep as clone,
|
|
21
23
|
flatten,
|
|
@@ -34,4 +36,9 @@ export {
|
|
|
34
36
|
last,
|
|
35
37
|
toLower,
|
|
36
38
|
isEqual as equals,
|
|
39
|
+
uniq,
|
|
40
|
+
uniqWith,
|
|
41
|
+
flowRight as compose,
|
|
42
|
+
partial,
|
|
43
|
+
reverse,
|
|
37
44
|
} from "lodash";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { curry } from "lodash";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A native reimplementation of Ramda's mapAccum.
|
|
5
|
+
*
|
|
6
|
+
* @template A, B, C
|
|
7
|
+
* @param {(acc: A, value: B) => [A, C]} fn - Function returning [newAcc, mappedValue]
|
|
8
|
+
* @param {A} acc - Initial accumulator
|
|
9
|
+
* @param {B[]} list - List to process
|
|
10
|
+
* @returns {[A, C[]]} - Tuple of [final accumulator, mapped array]
|
|
11
|
+
*/
|
|
12
|
+
export const mapAccum = curry((fn, acc, list) => {
|
|
13
|
+
const result = [];
|
|
14
|
+
let currentAcc = acc;
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < list.length; i++) {
|
|
17
|
+
const [nextAcc, mapped] = fn(currentAcc, list[i]);
|
|
18
|
+
currentAcc = nextAcc;
|
|
19
|
+
result.push(mapped);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return [currentAcc, result];
|
|
23
|
+
});
|
|
@@ -15,7 +15,7 @@ export enum ResultType {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export type CallbackResult = hookCallbackArgs & {
|
|
18
|
-
options
|
|
18
|
+
options?: {
|
|
19
19
|
resultType?: ResultType;
|
|
20
20
|
};
|
|
21
21
|
};
|
|
@@ -36,7 +36,7 @@ type General = Record<string, unknown>;
|
|
|
36
36
|
|
|
37
37
|
const LogPrefix = "useCallbackNavigationAction:";
|
|
38
38
|
|
|
39
|
-
const { log_info, log_verbose,
|
|
39
|
+
const { log_info, log_verbose, log_debug } = createLogger({
|
|
40
40
|
subsystem: "hook-navigation-callback",
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -56,23 +56,39 @@ const legacyMappingKeys = {
|
|
|
56
56
|
actionType: "logout_completion_action",
|
|
57
57
|
targetScreen: "navigate_to_logout_screen",
|
|
58
58
|
},
|
|
59
|
+
"quick-brick-storefront": {
|
|
60
|
+
actionType: "purchase_completion_action",
|
|
61
|
+
targetScreen: "navigate_to_screen_after_purchase",
|
|
62
|
+
},
|
|
63
|
+
"zapp_login_plugin_oauth_tv_2_0.login": {
|
|
64
|
+
actionType: "login_completion_action",
|
|
65
|
+
targetScreen: "navigate_to_login_screen",
|
|
66
|
+
},
|
|
67
|
+
"zapp_login_plugin_oauth_tv_2_0.logout": {
|
|
68
|
+
actionType: "logout_completion_action",
|
|
69
|
+
targetScreen: "navigate_to_logout_screen",
|
|
70
|
+
},
|
|
59
71
|
};
|
|
60
72
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const NAV_ACTIONS = Object.values(NavigationCallbackOptions) as string[];
|
|
73
|
+
const NAV_ACTIONS = (
|
|
74
|
+
Object.values(NavigationCallbackOptions) as string[]
|
|
75
|
+
).filter((value) => value !== NavigationCallbackOptions.DEFAULT);
|
|
65
76
|
|
|
66
77
|
const isNavAction = (v: unknown): v is NavigationCallbackOptions =>
|
|
67
|
-
typeof v === "string" && NAV_ACTIONS.includes(v
|
|
78
|
+
typeof v === "string" && NAV_ACTIONS.includes(v);
|
|
68
79
|
|
|
69
80
|
export const getNavigationKeys = (
|
|
70
81
|
item?: ZappUIComponent | ZappRiver,
|
|
71
|
-
resultType: ResultType = null
|
|
82
|
+
resultType: ResultType | null = null
|
|
72
83
|
): NavKeys => {
|
|
73
84
|
const general = (item?.general ?? {}) as General;
|
|
74
|
-
|
|
75
|
-
const
|
|
85
|
+
|
|
86
|
+
const pluginIdentifier = (item as any).identifier ?? item?.type ?? "";
|
|
87
|
+
|
|
88
|
+
const legacy =
|
|
89
|
+
legacyMappingKeys[`${pluginIdentifier}.${resultType}`] ??
|
|
90
|
+
legacyMappingKeys[pluginIdentifier] ??
|
|
91
|
+
{};
|
|
76
92
|
|
|
77
93
|
const actionKey = resultType
|
|
78
94
|
? `${resultType}_${CALLBACK_NAVIGATION_KEY}`
|
|
@@ -82,29 +98,27 @@ export const getNavigationKeys = (
|
|
|
82
98
|
(general as General)[actionKey] ??
|
|
83
99
|
(legacy.actionType ? (general as General)[legacy.actionType] : undefined);
|
|
84
100
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const trimmed = rawAction.trim();
|
|
89
|
-
action = isNavAction(trimmed) ? trimmed : null;
|
|
90
|
-
}
|
|
101
|
+
const action: NavigationCallbackOptions | null = isNavAction(rawAction)
|
|
102
|
+
? rawAction
|
|
103
|
+
: null;
|
|
91
104
|
|
|
92
105
|
if (!action) return null;
|
|
93
106
|
|
|
94
107
|
let targetScreenId: string | null = null;
|
|
95
108
|
|
|
96
109
|
if (action === NavigationCallbackOptions.GO_TO_SCREEN) {
|
|
110
|
+
const screenKey = resultType
|
|
111
|
+
? `${resultType}_${CALLBACK_NAVIGATION_GO_TO_SCREEN_KEY}`
|
|
112
|
+
: CALLBACK_NAVIGATION_GO_TO_SCREEN_KEY;
|
|
113
|
+
|
|
97
114
|
const screenId: string | null =
|
|
98
|
-
((general as General)[
|
|
115
|
+
((general as General)[screenKey] as string) ??
|
|
99
116
|
(legacy.targetScreen
|
|
100
117
|
? ((general as General)[legacy.targetScreen] as string)
|
|
101
118
|
: undefined);
|
|
102
119
|
|
|
103
120
|
if (screenId) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
targetScreenId =
|
|
107
|
-
trimmedTargetScreenId.length > 0 ? trimmedTargetScreenId : null;
|
|
121
|
+
targetScreenId = screenId.length > 0 ? screenId : null;
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
|
|
@@ -113,10 +127,12 @@ export const getNavigationKeys = (
|
|
|
113
127
|
|
|
114
128
|
export const useCallbackNavigationAction = (
|
|
115
129
|
item?: ZappUIComponent | ZappRiver
|
|
116
|
-
): ((
|
|
130
|
+
): ((
|
|
131
|
+
args: CallbackResult,
|
|
132
|
+
hookCallback?: hookCallback
|
|
133
|
+
) => void | undefined) => {
|
|
117
134
|
const navigation = useNavigation();
|
|
118
135
|
const rivers = useRivers();
|
|
119
|
-
const enabled = Boolean(item?.general?.[CALLBACK_NAVIGATION_KEY]);
|
|
120
136
|
const screenContext = useScreenContext();
|
|
121
137
|
|
|
122
138
|
const overrideCallbackFromComponent = useMemo(() => {
|
|
@@ -131,23 +147,36 @@ export const useCallbackNavigationAction = (
|
|
|
131
147
|
}
|
|
132
148
|
|
|
133
149
|
const callbackAction = useCallback<hookCallback>(
|
|
134
|
-
(args: CallbackResult) => {
|
|
150
|
+
(args: CallbackResult, hookCallback: hookCallback = null) => {
|
|
135
151
|
if (!args.success) {
|
|
136
|
-
|
|
152
|
+
log_debug(
|
|
153
|
+
`${LogPrefix} callback called with no success, use original callback`
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
hookCallback?.(args);
|
|
137
157
|
|
|
138
158
|
return;
|
|
139
159
|
}
|
|
140
160
|
|
|
141
161
|
if (args.cancelled) {
|
|
142
|
-
|
|
162
|
+
log_debug(
|
|
163
|
+
`${LogPrefix} callback called but cancelled, use original callback`
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
hookCallback?.(args);
|
|
167
|
+
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const data = getNavigationKeys(item, args.options?.resultType ?? null);
|
|
172
|
+
|
|
173
|
+
if (!data) {
|
|
174
|
+
hookCallback?.(args);
|
|
143
175
|
|
|
144
176
|
return;
|
|
145
177
|
}
|
|
146
178
|
|
|
147
|
-
|
|
148
|
-
action: NavigationCallbackOptions.DEFAULT,
|
|
149
|
-
targetScreenId: null,
|
|
150
|
-
};
|
|
179
|
+
hookCallback?.({ ...args, success: false, cancelled: true });
|
|
151
180
|
|
|
152
181
|
switch (data.action) {
|
|
153
182
|
case NavigationCallbackOptions.GO_BACK: {
|
|
@@ -198,5 +227,5 @@ export const useCallbackNavigationAction = (
|
|
|
198
227
|
[item, navigation, rivers]
|
|
199
228
|
);
|
|
200
229
|
|
|
201
|
-
return
|
|
230
|
+
return overrideCallbackFromComponent || callbackAction;
|
|
202
231
|
};
|
|
@@ -12,13 +12,7 @@ export const useCallbackActions = (
|
|
|
12
12
|
|
|
13
13
|
return useCallback(
|
|
14
14
|
async (data: CallbackResult) => {
|
|
15
|
-
|
|
16
|
-
hookCallback?.({ ...data, success: false, cancelled: true });
|
|
17
|
-
|
|
18
|
-
navigationAction(data);
|
|
19
|
-
} else {
|
|
20
|
-
hookCallback?.(data);
|
|
21
|
-
}
|
|
15
|
+
navigationAction(data, hookCallback);
|
|
22
16
|
},
|
|
23
17
|
[navigationAction, hookCallback]
|
|
24
18
|
);
|