@applicaster/zapp-react-native-utils 13.0.0-alpha.7120331332 → 13.0.0-alpha.7192404241
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/actionsExecutor/ActionExecutor.ts +16 -9
- package/actionsExecutor/ActionExecutorContext.tsx +59 -8
- package/alertUtils/alert.ts +148 -0
- package/alertUtils/index.ts +1 -148
- package/analyticsUtils/AnalyticPlayerListener.ts +18 -1
- package/appUtils/HooksManager/types.ts +3 -0
- package/appUtils/accessibilityManager/index.ts +49 -1
- package/appUtils/contextKeysManager/contextResolver.ts +73 -0
- package/appUtils/focusManager/index.ts +92 -64
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +23 -0
- package/appUtils/focusManager/utils/enums.ts +4 -0
- package/appUtils/focusManagerAux/utils/index.ts +4 -3
- package/appUtils/keyCodes/keys/keysByPlatform.ts +4 -0
- package/appUtils/keyInputHandler/KeyInputHandler.ts +68 -0
- package/appUtils/platform/const.ts +14 -0
- package/appUtils/platform/platformUtils.ts +17 -1
- package/appUtils/playerManager/player.ts +17 -5
- package/appUtils/playerManager/playerNative.ts +2 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +3 -3
- package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
- package/idleUtils/index.ts +1 -1
- package/idleUtils/withTimeout.ts +15 -2
- package/logger/const.ts +9 -67
- package/package.json +3 -2
- package/reactHooks/cell-click/__tests__/index.test.js +5 -0
- package/reactHooks/cell-click/index.ts +7 -1
- package/reactHooks/feed/useFeedLoader.tsx +1 -1
- package/reactHooks/player/TVSeekControlller/ISeekController.ts +32 -0
- package/reactHooks/player/TVSeekControlller/LongPressSeekController.ts +71 -0
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +258 -0
- package/reactHooks/player/TVSeekControlller/TapSeekController.ts +53 -0
- package/reactHooks/player/const.ts +0 -4
- package/reactHooks/player/index.ts +0 -6
- package/time/Timer.ts +1 -1
- package/types.ts +1 -0
- package/reactHooks/player/useAutoSeek.tsx +0 -87
- package/reactHooks/player/useSeek.tsx +0 -66
- package/reactHooks/player/useTapSeek.tsx +0 -58
- /package/reactHooks/player/__tests__/{useAutoSeek.test.tsx → useAutoSeek._test.tsx} +0 -0
- /package/reactHooks/player/__tests__/{useTapSeek.test.ts → useTapSeek._test.ts} +0 -0
|
@@ -17,9 +17,10 @@ class ActionExecutor {
|
|
|
17
17
|
|
|
18
18
|
handleAction = async (
|
|
19
19
|
action: ActionType,
|
|
20
|
-
|
|
20
|
+
context?: Record<string, any>
|
|
21
21
|
): Promise<ActionResult> => {
|
|
22
22
|
const handler = this.registeredActions[action.type];
|
|
23
|
+
const entry = context?.entry as ZappEntry;
|
|
23
24
|
|
|
24
25
|
if (!handler) {
|
|
25
26
|
log_error(
|
|
@@ -38,11 +39,11 @@ class ActionExecutor {
|
|
|
38
39
|
);
|
|
39
40
|
|
|
40
41
|
try {
|
|
41
|
-
return handler(action,
|
|
42
|
+
return handler(action, context);
|
|
42
43
|
} catch (error) {
|
|
43
44
|
log_error(
|
|
44
45
|
`handleAction: error handling action type: ${action.type}, message: ${error.message}`,
|
|
45
|
-
{ action,
|
|
46
|
+
{ action, context, error }
|
|
46
47
|
);
|
|
47
48
|
|
|
48
49
|
throw error;
|
|
@@ -51,11 +52,11 @@ class ActionExecutor {
|
|
|
51
52
|
|
|
52
53
|
handleActions = async (
|
|
53
54
|
actionTypes: ActionType[],
|
|
54
|
-
|
|
55
|
+
context?: Record<string, any>
|
|
55
56
|
): Promise<ActionResult> => {
|
|
56
57
|
for (const action of actionTypes) {
|
|
57
58
|
// TODO: Handle action result error
|
|
58
|
-
const result = await this.handleAction(action,
|
|
59
|
+
const result = await this.handleAction(action, context);
|
|
59
60
|
|
|
60
61
|
if (result === ActionResult.Cancel) {
|
|
61
62
|
log_info(`handleActions: action: ${action.type} canceled`);
|
|
@@ -67,7 +68,10 @@ class ActionExecutor {
|
|
|
67
68
|
return ActionResult.Success;
|
|
68
69
|
};
|
|
69
70
|
|
|
70
|
-
handleEntryActions = async (
|
|
71
|
+
handleEntryActions = async (
|
|
72
|
+
entry: ZappEntry,
|
|
73
|
+
context?: Record<string, any>
|
|
74
|
+
): Promise<ActionResult> => {
|
|
71
75
|
const tapActions = entry.extensions?.tap_actions?.actions;
|
|
72
76
|
|
|
73
77
|
if (!tapActions) {
|
|
@@ -77,7 +81,7 @@ class ActionExecutor {
|
|
|
77
81
|
if (!Array.isArray(tapActions)) {
|
|
78
82
|
log_error(
|
|
79
83
|
`handleEntryActions: tapActions is not an array for id: ${entry.id} title: ${entry.title}`,
|
|
80
|
-
{ entry }
|
|
84
|
+
{ context, entry }
|
|
81
85
|
);
|
|
82
86
|
|
|
83
87
|
return;
|
|
@@ -87,7 +91,7 @@ class ActionExecutor {
|
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
await this.handleActions(tapActions, entry);
|
|
94
|
+
await this.handleActions(tapActions, { ...context, entry });
|
|
91
95
|
};
|
|
92
96
|
|
|
93
97
|
unregisterAction = (actionType: string) => {
|
|
@@ -96,7 +100,10 @@ class ActionExecutor {
|
|
|
96
100
|
|
|
97
101
|
registerAction = (
|
|
98
102
|
type: string,
|
|
99
|
-
handler: (
|
|
103
|
+
handler: (
|
|
104
|
+
action: ActionType,
|
|
105
|
+
context?: Record<string, any>
|
|
106
|
+
) => Promise<ActionResult>
|
|
100
107
|
) => {
|
|
101
108
|
if (this.registeredActions[type]) {
|
|
102
109
|
log_error(`registerAction: action type: ${type} already registered`);
|
|
@@ -13,6 +13,12 @@ import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
|
|
|
13
13
|
import { createLogger } from "../logger";
|
|
14
14
|
import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
15
15
|
import { ACTIVE_LAYOUT_ID_STORAGE_KEY } from "@applicaster/quick-brick-core/App/remoteContextReloader/consts";
|
|
16
|
+
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
17
|
+
import { loadPipesData } from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
18
|
+
import {
|
|
19
|
+
EntryResolver,
|
|
20
|
+
resolveObjectValues,
|
|
21
|
+
} from "../appUtils/contextKeysManager/contextResolver";
|
|
16
22
|
|
|
17
23
|
const { log_error, log_info } = createLogger({
|
|
18
24
|
subsystem: "ActionExecutorContext",
|
|
@@ -22,15 +28,24 @@ const { log_error, log_info } = createLogger({
|
|
|
22
28
|
type ActionExecutorContextType = {
|
|
23
29
|
registerAction: (
|
|
24
30
|
type: string,
|
|
25
|
-
handler: (
|
|
31
|
+
handler: (
|
|
32
|
+
action: ActionType,
|
|
33
|
+
context?: Record<string, any>
|
|
34
|
+
) => Promise<ActionResult>
|
|
26
35
|
) => void;
|
|
27
36
|
unregisterAction: (type: string) => void;
|
|
28
|
-
handleAction: (
|
|
37
|
+
handleAction: (
|
|
38
|
+
action: ActionType,
|
|
39
|
+
context?: Record<string, any>
|
|
40
|
+
) => Promise<ActionResult>;
|
|
29
41
|
handleActions: (
|
|
30
42
|
actions: ActionType[],
|
|
31
|
-
|
|
43
|
+
context: Record<string, any>
|
|
44
|
+
) => Promise<ActionResult>;
|
|
45
|
+
handleEntryActions: (
|
|
46
|
+
entry: ZappEntry,
|
|
47
|
+
context?: Record<string, any>
|
|
32
48
|
) => Promise<ActionResult>;
|
|
33
|
-
handleEntryActions: (entry: ZappEntry) => Promise<ActionResult>;
|
|
34
49
|
};
|
|
35
50
|
type Props = {
|
|
36
51
|
children: React.ReactNode;
|
|
@@ -45,6 +60,31 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
45
60
|
return ActionResult.Success;
|
|
46
61
|
});
|
|
47
62
|
|
|
63
|
+
actionExecutor.registerAction(
|
|
64
|
+
"refreshComponent",
|
|
65
|
+
async (_action, context) => {
|
|
66
|
+
const dispatch = appStore.getDispatch();
|
|
67
|
+
|
|
68
|
+
log_info("handleAction: refreshComponent event");
|
|
69
|
+
|
|
70
|
+
const dataSource = context?.component?.data?.source;
|
|
71
|
+
|
|
72
|
+
// TODO: Later we can pass component ids to do refresh instead of current one
|
|
73
|
+
// TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
|
|
74
|
+
// TODO: handle focused item removal
|
|
75
|
+
dispatch(
|
|
76
|
+
loadPipesData(dataSource, {
|
|
77
|
+
silentRefresh: false,
|
|
78
|
+
clearCache: true,
|
|
79
|
+
callback: (_data) => {},
|
|
80
|
+
riverId: context?.screenData?.id,
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return ActionResult.Success;
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
48
88
|
actionExecutor.registerAction("switchLayout", async (action) => {
|
|
49
89
|
log_info("handleAction: switchLayout event");
|
|
50
90
|
|
|
@@ -80,13 +120,21 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
80
120
|
return confirmationPromise;
|
|
81
121
|
});
|
|
82
122
|
|
|
83
|
-
actionExecutor.registerAction("sendCloudEvent", async (action,
|
|
123
|
+
actionExecutor.registerAction("sendCloudEvent", async (action, context) => {
|
|
84
124
|
try {
|
|
85
125
|
const options = action.options;
|
|
86
126
|
|
|
127
|
+
const entry = context?.entry || {};
|
|
128
|
+
const entryResolver = new EntryResolver(entry);
|
|
129
|
+
|
|
130
|
+
const data =
|
|
131
|
+
options?.data && options.inflateData
|
|
132
|
+
? await resolveObjectValues(options.data, { entry: entryResolver })
|
|
133
|
+
: options?.data || entry;
|
|
134
|
+
|
|
87
135
|
const cloudEvent = await createCloudEvent({
|
|
88
136
|
type: options.type || "com.applicaster.selector.action.v1",
|
|
89
|
-
data
|
|
137
|
+
data,
|
|
90
138
|
subject: options.subject || entry?.id,
|
|
91
139
|
});
|
|
92
140
|
|
|
@@ -117,9 +165,12 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
117
165
|
|
|
118
166
|
actionExecutor.registerAction(
|
|
119
167
|
"localStorageToggleFlag",
|
|
120
|
-
async (
|
|
168
|
+
async (
|
|
169
|
+
action: ActionType,
|
|
170
|
+
context?: Record<string, any>
|
|
171
|
+
): Promise<ActionResult> => {
|
|
121
172
|
const keyNamespace = action.options?.key;
|
|
122
|
-
const tag = entry?.extensions?.tag;
|
|
173
|
+
const tag = context?.entry?.extensions?.tag;
|
|
123
174
|
|
|
124
175
|
if (keyNamespace && tag) {
|
|
125
176
|
const multiSelectProvider =
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Alert } from "react-native";
|
|
2
|
+
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
3
|
+
import { confirmationDialogStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";
|
|
4
|
+
|
|
5
|
+
// #region "Alert Dialog"
|
|
6
|
+
|
|
7
|
+
const globalAny: any = global;
|
|
8
|
+
|
|
9
|
+
const showWebAlertDialog = ({ message, completion }: AlertWebDialog) => {
|
|
10
|
+
globalAny?.alert?.(message);
|
|
11
|
+
completion?.();
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const showNativeAlertDialog = ({
|
|
15
|
+
title,
|
|
16
|
+
message,
|
|
17
|
+
okButtonText,
|
|
18
|
+
completion,
|
|
19
|
+
}: AlertDialogType) => {
|
|
20
|
+
Alert.alert(title, message, [
|
|
21
|
+
{
|
|
22
|
+
text: okButtonText,
|
|
23
|
+
onPress: completion,
|
|
24
|
+
},
|
|
25
|
+
]);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const showAlertDialog = ({
|
|
29
|
+
title,
|
|
30
|
+
message,
|
|
31
|
+
okButtonText,
|
|
32
|
+
completion,
|
|
33
|
+
}: AlertDialogType) => {
|
|
34
|
+
if (isWeb()) {
|
|
35
|
+
showWebAlertDialog({ message, completion });
|
|
36
|
+
} else {
|
|
37
|
+
showNativeAlertDialog({
|
|
38
|
+
title,
|
|
39
|
+
message,
|
|
40
|
+
okButtonText,
|
|
41
|
+
completion,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// #endregion "Alert Dialog"
|
|
47
|
+
|
|
48
|
+
// #region "Confirmation Dialog"
|
|
49
|
+
|
|
50
|
+
const showWebDialog = ({
|
|
51
|
+
message,
|
|
52
|
+
confirmCompletion,
|
|
53
|
+
cancelCompletion,
|
|
54
|
+
}: WebConfirmationDialog) => {
|
|
55
|
+
const isConfirmed = globalAny?.confirm?.(message);
|
|
56
|
+
|
|
57
|
+
if (isConfirmed) {
|
|
58
|
+
confirmCompletion?.();
|
|
59
|
+
} else {
|
|
60
|
+
cancelCompletion?.();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const showNativeDialog = ({
|
|
65
|
+
title,
|
|
66
|
+
message,
|
|
67
|
+
okButtonText,
|
|
68
|
+
cancelButtonText,
|
|
69
|
+
confirmCompletion,
|
|
70
|
+
cancelCompletion,
|
|
71
|
+
}: ConfirmationDialog) => {
|
|
72
|
+
Alert.alert(title, message, [
|
|
73
|
+
{
|
|
74
|
+
text: cancelButtonText,
|
|
75
|
+
onPress: cancelCompletion,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
text: okButtonText,
|
|
79
|
+
style: "destructive",
|
|
80
|
+
onPress: confirmCompletion,
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleCustomDialog = (data) => {
|
|
86
|
+
const {
|
|
87
|
+
showDialog,
|
|
88
|
+
hideDialog,
|
|
89
|
+
setConfirmAction,
|
|
90
|
+
setCancelAction,
|
|
91
|
+
setMessages,
|
|
92
|
+
} = confirmationDialogStore.getState();
|
|
93
|
+
|
|
94
|
+
setMessages(data.messages);
|
|
95
|
+
|
|
96
|
+
if (data?.cancelCompletion) {
|
|
97
|
+
setCancelAction(() => {
|
|
98
|
+
data.cancelCompletion();
|
|
99
|
+
hideDialog();
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (data?.confirmCompletion) {
|
|
104
|
+
setConfirmAction(() => {
|
|
105
|
+
data.confirmCompletion();
|
|
106
|
+
hideDialog();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
showDialog();
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const showConfirmationDialog = ({
|
|
114
|
+
title,
|
|
115
|
+
message,
|
|
116
|
+
okButtonText,
|
|
117
|
+
cancelButtonText,
|
|
118
|
+
confirmCompletion,
|
|
119
|
+
cancelCompletion,
|
|
120
|
+
}: CustomConfirmationDialog): void => {
|
|
121
|
+
if (isWeb()) {
|
|
122
|
+
if (cancelCompletion) {
|
|
123
|
+
handleCustomDialog({
|
|
124
|
+
messages: {
|
|
125
|
+
title,
|
|
126
|
+
message,
|
|
127
|
+
okButtonText,
|
|
128
|
+
cancelButtonText,
|
|
129
|
+
},
|
|
130
|
+
confirmCompletion,
|
|
131
|
+
cancelCompletion,
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
showWebDialog({ message, confirmCompletion, cancelCompletion });
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
showNativeDialog({
|
|
138
|
+
title,
|
|
139
|
+
message,
|
|
140
|
+
okButtonText,
|
|
141
|
+
cancelButtonText,
|
|
142
|
+
confirmCompletion,
|
|
143
|
+
cancelCompletion,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// #endregion "Confirmation Dialog"
|
package/alertUtils/index.ts
CHANGED
|
@@ -1,148 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
3
|
-
import { confirmationDialogStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ConfirmDialogState";
|
|
4
|
-
|
|
5
|
-
// #region "Alert Dialog"
|
|
6
|
-
|
|
7
|
-
const globalAny: any = global;
|
|
8
|
-
|
|
9
|
-
const showWebAlertDialog = ({ message, completion }: AlertWebDialog) => {
|
|
10
|
-
globalAny?.alert?.(message);
|
|
11
|
-
completion?.();
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const showNativeAlertDialog = ({
|
|
15
|
-
title,
|
|
16
|
-
message,
|
|
17
|
-
okButtonText,
|
|
18
|
-
completion,
|
|
19
|
-
}: AlertDialogType) => {
|
|
20
|
-
Alert.alert(title, message, [
|
|
21
|
-
{
|
|
22
|
-
text: okButtonText,
|
|
23
|
-
onPress: completion,
|
|
24
|
-
},
|
|
25
|
-
]);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const showAlertDialog = ({
|
|
29
|
-
title,
|
|
30
|
-
message,
|
|
31
|
-
okButtonText,
|
|
32
|
-
completion,
|
|
33
|
-
}: AlertDialogType) => {
|
|
34
|
-
if (isWeb()) {
|
|
35
|
-
showWebAlertDialog({ message, completion });
|
|
36
|
-
} else {
|
|
37
|
-
showNativeAlertDialog({
|
|
38
|
-
title,
|
|
39
|
-
message,
|
|
40
|
-
okButtonText,
|
|
41
|
-
completion,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// #endregion "Alert Dialog"
|
|
47
|
-
|
|
48
|
-
// #region "Confirmation Dialog"
|
|
49
|
-
|
|
50
|
-
const showWebDialog = ({
|
|
51
|
-
message,
|
|
52
|
-
confirmCompletion,
|
|
53
|
-
cancelCompletion,
|
|
54
|
-
}: WebConfirmationDialog) => {
|
|
55
|
-
const isConfirmed = globalAny?.confirm?.(message);
|
|
56
|
-
|
|
57
|
-
if (isConfirmed) {
|
|
58
|
-
confirmCompletion?.();
|
|
59
|
-
} else {
|
|
60
|
-
cancelCompletion?.();
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const showNativeDialog = ({
|
|
65
|
-
title,
|
|
66
|
-
message,
|
|
67
|
-
okButtonText,
|
|
68
|
-
cancelButtonText,
|
|
69
|
-
confirmCompletion,
|
|
70
|
-
cancelCompletion,
|
|
71
|
-
}: ConfirmationDialog) => {
|
|
72
|
-
Alert.alert(title, message, [
|
|
73
|
-
{
|
|
74
|
-
text: cancelButtonText,
|
|
75
|
-
onPress: cancelCompletion,
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
text: okButtonText,
|
|
79
|
-
style: "destructive",
|
|
80
|
-
onPress: confirmCompletion,
|
|
81
|
-
},
|
|
82
|
-
]);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const handleCustomDialog = (data) => {
|
|
86
|
-
const {
|
|
87
|
-
showDialog,
|
|
88
|
-
hideDialog,
|
|
89
|
-
setConfirmAction,
|
|
90
|
-
setCancelAction,
|
|
91
|
-
setMessages,
|
|
92
|
-
} = confirmationDialogStore.getState();
|
|
93
|
-
|
|
94
|
-
setMessages(data.messages);
|
|
95
|
-
|
|
96
|
-
if (data?.cancelCompletion) {
|
|
97
|
-
setCancelAction(() => {
|
|
98
|
-
data.cancelCompletion();
|
|
99
|
-
hideDialog();
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (data?.confirmCompletion) {
|
|
104
|
-
setConfirmAction(() => {
|
|
105
|
-
data.confirmCompletion();
|
|
106
|
-
hideDialog();
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
showDialog();
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
export const showConfirmationDialog = ({
|
|
114
|
-
title,
|
|
115
|
-
message,
|
|
116
|
-
okButtonText,
|
|
117
|
-
cancelButtonText,
|
|
118
|
-
confirmCompletion,
|
|
119
|
-
cancelCompletion,
|
|
120
|
-
}: CustomConfirmationDialog): void => {
|
|
121
|
-
if (isWeb()) {
|
|
122
|
-
if (cancelCompletion) {
|
|
123
|
-
handleCustomDialog({
|
|
124
|
-
messages: {
|
|
125
|
-
title,
|
|
126
|
-
message,
|
|
127
|
-
okButtonText,
|
|
128
|
-
cancelButtonText,
|
|
129
|
-
},
|
|
130
|
-
confirmCompletion,
|
|
131
|
-
cancelCompletion,
|
|
132
|
-
});
|
|
133
|
-
} else {
|
|
134
|
-
showWebDialog({ message, confirmCompletion, cancelCompletion });
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
showNativeDialog({
|
|
138
|
-
title,
|
|
139
|
-
message,
|
|
140
|
-
okButtonText,
|
|
141
|
-
cancelButtonText,
|
|
142
|
-
confirmCompletion,
|
|
143
|
-
cancelCompletion,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
// #endregion "Confirmation Dialog"
|
|
1
|
+
export { showAlertDialog, showConfirmationDialog } from "./alert";
|
|
@@ -14,7 +14,7 @@ export class AnalyticPlayerListener
|
|
|
14
14
|
static analyticsListenerID = "analytics-listener";
|
|
15
15
|
private analyticsTracker: PlayerAnalyticsTracker;
|
|
16
16
|
private player: Player;
|
|
17
|
-
|
|
17
|
+
private isSeeking: boolean = false;
|
|
18
18
|
private onVideoLoadCalled; // android only
|
|
19
19
|
|
|
20
20
|
constructor(props: Props) {
|
|
@@ -40,10 +40,27 @@ export class AnalyticPlayerListener
|
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
onPlayerPause = (event) => {
|
|
43
|
+
if (this.player?.seekController?.isSeeking()) {
|
|
44
|
+
this.isSeeking = true;
|
|
45
|
+
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
this.handleAnalyticEvent(PLAYBACK_EVENT.pause, event);
|
|
44
50
|
};
|
|
45
51
|
|
|
46
52
|
onPlayerResume = (event) => {
|
|
53
|
+
if (this.player?.seekController?.isSeeking()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (this.isSeeking) {
|
|
58
|
+
// Fallback due to player resume event been async and arriving with delays
|
|
59
|
+
this.isSeeking = false;
|
|
60
|
+
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
this.handleAnalyticEvent(PLAYBACK_EVENT.play, event);
|
|
48
65
|
};
|
|
49
66
|
|
|
@@ -4,6 +4,8 @@ import { HOOKS_EVENTS } from "./constants";
|
|
|
4
4
|
export interface HookConfiguration {}
|
|
5
5
|
|
|
6
6
|
/* existing Hook Plugn props: sample */
|
|
7
|
+
|
|
8
|
+
/*
|
|
7
9
|
interface _HookModulePlugin {
|
|
8
10
|
Component:
|
|
9
11
|
| React.FC<HookPluginProps>
|
|
@@ -23,6 +25,7 @@ interface _HookModulePlugin {
|
|
|
23
25
|
presentUI: (payload?: HookPluginProps["payload"]) => void
|
|
24
26
|
) => any;
|
|
25
27
|
}
|
|
28
|
+
*/
|
|
26
29
|
|
|
27
30
|
//* Derived from usage */
|
|
28
31
|
export interface HookModule {
|
|
@@ -6,6 +6,9 @@ import { AccessibilityRole } from "react-native";
|
|
|
6
6
|
|
|
7
7
|
export class AccessibilityManager {
|
|
8
8
|
private static _instance: AccessibilityManager | null = null;
|
|
9
|
+
private headingTimeout: NodeJS.Timeout | null = null;
|
|
10
|
+
private WORDS_PER_MINUTE = 160;
|
|
11
|
+
private MINIMUM_PAUSE = 500;
|
|
9
12
|
private state$ = new BehaviorSubject<AccessibilityState>({
|
|
10
13
|
screenReaderEnabled: false,
|
|
11
14
|
reduceMotionEnabled: false,
|
|
@@ -20,6 +23,7 @@ export class AccessibilityManager {
|
|
|
20
23
|
|
|
21
24
|
private ttsManager = TTSManager.getInstance();
|
|
22
25
|
private localizations: { [key: string]: string } = {};
|
|
26
|
+
private headingQueue: string[] = [];
|
|
23
27
|
|
|
24
28
|
private constructor() {}
|
|
25
29
|
|
|
@@ -52,6 +56,28 @@ export class AccessibilityManager {
|
|
|
52
56
|
return this.state$.asObservable();
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
/** Calculates the reading time for a given text
|
|
60
|
+
* This method is a bit of a hack because we don't have a callback, or promise from VIZIO API
|
|
61
|
+
* @param text - The text to calculate the reading time for
|
|
62
|
+
* @returns The reading time in milliseconds
|
|
63
|
+
*/
|
|
64
|
+
private calculateReadingTime(text: string): number {
|
|
65
|
+
const words = text.trim().split(/\s+/).length;
|
|
66
|
+
|
|
67
|
+
return Math.max(
|
|
68
|
+
this.MINIMUM_PAUSE,
|
|
69
|
+
(words / this.WORDS_PER_MINUTE) * 60 * 1000
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Adds a heading to the queue, headings will be read before the next text
|
|
75
|
+
* Each heading will be read once and removed from the queue
|
|
76
|
+
*/
|
|
77
|
+
public addHeading(heading: string) {
|
|
78
|
+
this.headingQueue.push(heading);
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
/**
|
|
56
82
|
* text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
|
|
57
83
|
* keyOfLocalizedText is the key to the localized text
|
|
@@ -85,7 +111,29 @@ export class AccessibilityManager {
|
|
|
85
111
|
textToRead = localizedMessage;
|
|
86
112
|
}
|
|
87
113
|
|
|
88
|
-
|
|
114
|
+
const readHeadings = () => {
|
|
115
|
+
if (this.headingQueue.length > 0) {
|
|
116
|
+
const heading = this.headingQueue.shift()!; // Get the next heading
|
|
117
|
+
|
|
118
|
+
this.ttsManager?.readText(heading); // Read the heading
|
|
119
|
+
|
|
120
|
+
if (this.headingTimeout) {
|
|
121
|
+
clearTimeout(this.headingTimeout);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const pauseTime = this.calculateReadingTime(heading); // Calculate pause time based on the heading
|
|
125
|
+
|
|
126
|
+
// Set a timeout to read the next heading
|
|
127
|
+
this.headingTimeout = setTimeout(() => {
|
|
128
|
+
readHeadings(); // Call the function recursively to read the next heading
|
|
129
|
+
}, pauseTime);
|
|
130
|
+
} else {
|
|
131
|
+
// No more headings to read, read the main text
|
|
132
|
+
this.ttsManager?.readText(textToRead);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
readHeadings(); // Start reading headings
|
|
89
137
|
|
|
90
138
|
this.announcements$.next({
|
|
91
139
|
message: textToRead,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createLogger } from "../../logger";
|
|
2
|
+
import { ContextKeysManager } from "./index";
|
|
3
|
+
import * as R from "ramda";
|
|
4
|
+
|
|
5
|
+
const { log_error } = createLogger({
|
|
6
|
+
subsystem: "contextResolver",
|
|
7
|
+
category: "General",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
interface IResolver {
|
|
11
|
+
resolve: (string) => Promise<string | number | object>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class EntryResolver implements IResolver {
|
|
15
|
+
entry: ZappEntry;
|
|
16
|
+
|
|
17
|
+
constructor(entry: ZappEntry) {
|
|
18
|
+
this.entry = entry;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async resolve(key: string) {
|
|
22
|
+
if (!key || key.length === 0) {
|
|
23
|
+
return this.entry;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return R.view(R.lensPath(key.split(".")), this.entry);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ContextResolver implements IResolver {
|
|
31
|
+
resolve = async (compositeKey: string) =>
|
|
32
|
+
ContextKeysManager.instance.getKey(compositeKey);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const resolveObjectValues = async (
|
|
36
|
+
data: Record<string, any>,
|
|
37
|
+
resolvers: Record<string, IResolver> = null
|
|
38
|
+
) => {
|
|
39
|
+
const entries = Object.entries(data);
|
|
40
|
+
|
|
41
|
+
const exResolvers = { ctx: new ContextResolver(), ...resolvers };
|
|
42
|
+
|
|
43
|
+
const resolvedEntries = await Promise.all(
|
|
44
|
+
entries.map(async ([key, value]) => {
|
|
45
|
+
const regex = /@{([^/]+)\/([^}]*)}/;
|
|
46
|
+
|
|
47
|
+
if (typeof value !== "string") {
|
|
48
|
+
return [key, value];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const match = (value as string).match(regex);
|
|
52
|
+
|
|
53
|
+
if (match) {
|
|
54
|
+
const contextResolverName = match[1];
|
|
55
|
+
const compositeKey = match[2];
|
|
56
|
+
|
|
57
|
+
const resolver = exResolvers[contextResolverName] || exResolvers.ctx;
|
|
58
|
+
const resolvedValue = await resolver.resolve(compositeKey);
|
|
59
|
+
|
|
60
|
+
return [key, resolvedValue];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
log_error("resolveObjectValues: invalid context string", {
|
|
64
|
+
key,
|
|
65
|
+
value,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return [key, null];
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return Object.fromEntries(resolvedEntries);
|
|
73
|
+
};
|