@applicaster/zapp-react-native-utils 12.0.0-alpha.4195481103 → 12.0.0-alpha.4220424021
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 +141 -0
- package/analyticsUtils/PlayerAnalyticsManager.ts +90 -0
- package/analyticsUtils/{analyticsTracker.ts → playerAnalyticsTracker.ts} +19 -10
- package/appUtils/accessibilityManager/hooks.ts +16 -7
- package/appUtils/accessibilityManager/index.ts +42 -26
- package/appUtils/platform/platformUtils.ts +1 -97
- package/appUtils/playerManager/index.ts +9 -27
- package/appUtils/playerManager/player.ts +1 -0
- package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +57 -0
- package/appUtils/playerManager/playerNative.ts +1 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +2 -1
- package/appUtils/playerManager/usePlayerState.tsx +19 -34
- package/cellUtils/index.ts +38 -5
- package/functionUtils/index.ts +38 -0
- package/index.d.ts +15 -0
- package/navigationUtils/index.ts +48 -0
- package/numberUtils/__tests__/toNumber.test.ts +2 -2
- package/package.json +2 -2
- package/playerUtils/imaScriptDom.ts +59 -0
- package/reactHooks/debugging/__tests__/index.test.js +81 -11
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -0
- package/reactHooks/resolvers/useCellResolver.ts +5 -1
- package/reactUtils/index.ts +1 -2
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { PlayerAnalyticsTracker } from "./playerAnalyticsTracker";
|
|
2
|
+
import { Player } from "../appUtils/playerManager/player";
|
|
3
|
+
import { AD_EVENT, GENERAL_EVENT, PLAYBACK_EVENT, QOS_EVENT } from "./events";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
analyticsTracker: any;
|
|
7
|
+
player: any;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/* eslint brace-style: 0 */ // --> OFF
|
|
11
|
+
export class AnalyticPlayerListener
|
|
12
|
+
implements QuickBrickPlayer.SharedPlayerCallBacks
|
|
13
|
+
{
|
|
14
|
+
static analyticsListenerID = "analytics-listener";
|
|
15
|
+
private analyticsTracker: PlayerAnalyticsTracker;
|
|
16
|
+
private player: Player;
|
|
17
|
+
|
|
18
|
+
private onVideoLoadCalled; // android only
|
|
19
|
+
|
|
20
|
+
constructor(props: Props) {
|
|
21
|
+
this.analyticsTracker = props.analyticsTracker;
|
|
22
|
+
this.player = props.player;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
handleAnalyticEvent = (event, data = {}) => {
|
|
26
|
+
if (this.analyticsTracker?.handleAnalyticEvent) {
|
|
27
|
+
this.analyticsTracker?.handleAnalyticEvent?.(event, {
|
|
28
|
+
...data,
|
|
29
|
+
player_type: this.player.playerPluginId,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
onVideoEnd = () => {
|
|
35
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.complete);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
onError = (err: Error) => {
|
|
39
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.error, err); // TODO: Check error format
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
onPlayerPause = (event) => {
|
|
43
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.pause, event);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
onPlayerResume = (event) => {
|
|
47
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.play, event);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
onPlayerSeekComplete = (event) => {
|
|
51
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.seek_complete, event);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
onPlayerSeekStart = (event) => {
|
|
55
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.seek_start, event);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
onBufferStart = (event) => {
|
|
59
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.buffer_start, event);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
onBufferComplete = (event) => {
|
|
63
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.buffer_complete, event);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Not implemented
|
|
67
|
+
onPlayerStop = (event) => {
|
|
68
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.stop, event);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// TODO: Implement method on native
|
|
72
|
+
onVideoBandwidthUpdate = (event) => {
|
|
73
|
+
this.handleAnalyticEvent(QOS_EVENT.bitrate_changed, event);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
onAdBegin = (event) => {
|
|
77
|
+
this.handleAnalyticEvent(AD_EVENT.ad_start, event);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
onAdBreakBegin = (event) => {
|
|
81
|
+
this.handleAnalyticEvent(AD_EVENT.ad_break_start, event);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
onAdBreakEnd = (event) => {
|
|
85
|
+
this.handleAnalyticEvent(AD_EVENT.ad_break_completed, event);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
onAdEnd = (event) => {
|
|
89
|
+
this.handleAnalyticEvent(AD_EVENT.ad_complete, event);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
onAdError = (event) => {
|
|
93
|
+
this.handleAnalyticEvent(AD_EVENT.ad_error, event);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
onAdRequest = (event) => {
|
|
97
|
+
this.handleAnalyticEvent(AD_EVENT.ad_request, event);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
onAdClicked = (event) => {
|
|
101
|
+
this.handleAnalyticEvent(AD_EVENT.ad_clicked, event);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
onAdTapped = (event) => {
|
|
105
|
+
this.handleAnalyticEvent(AD_EVENT.ad_tapped, event);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
onVideoLoad = () => {
|
|
109
|
+
// if (!this.state.isAd && !this.onVideoLoadCalled) {
|
|
110
|
+
if (!this.player.isAd() && !this.onVideoLoadCalled) {
|
|
111
|
+
this.onVideoLoadCalled = true;
|
|
112
|
+
|
|
113
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.load_video, {
|
|
114
|
+
player_type: this.player.playerPluginId,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
onPlayerSessionEnd = () => {
|
|
120
|
+
// TODO: Implement method on native android, ios exists
|
|
121
|
+
this.handleAnalyticEvent(GENERAL_EVENT.session_end);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
onPlayerSessionStart = () => {
|
|
125
|
+
// TODO: Implement method on native android, ios exists
|
|
126
|
+
|
|
127
|
+
this.handleAnalyticEvent(GENERAL_EVENT.session_start);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
onPlayerNext = ({ nativeEvent }) => {
|
|
131
|
+
// TODO: Implement method on native
|
|
132
|
+
|
|
133
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.next, nativeEvent);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
onPlayerPrevious = ({ nativeEvent }) => {
|
|
137
|
+
// TODO: Implement method on native
|
|
138
|
+
|
|
139
|
+
this.handleAnalyticEvent(PLAYBACK_EVENT.previous, nativeEvent);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlayerAnalyticsTracker,
|
|
3
|
+
PlayerAnalyticsTrackerI,
|
|
4
|
+
} from "./playerAnalyticsTracker";
|
|
5
|
+
import {
|
|
6
|
+
PlayerLifecycleListener,
|
|
7
|
+
PlayerManager,
|
|
8
|
+
} from "../appUtils/playerManager";
|
|
9
|
+
import { Player } from "../appUtils/playerManager/player";
|
|
10
|
+
import { AnalyticPlayerListener } from "./AnalyticPlayerListener";
|
|
11
|
+
import { PlayerRole } from "../appUtils/playerManager/conts";
|
|
12
|
+
|
|
13
|
+
enum PlayerAnalyticsTrackerType {
|
|
14
|
+
PlayerAnalyticsTracker = "PlayerAnalyticsTracker",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class PlayerAnalyticsTrackerFactory {
|
|
18
|
+
createTracker(type: PlayerAnalyticsTrackerType) {
|
|
19
|
+
switch (type) {
|
|
20
|
+
case PlayerAnalyticsTrackerType.PlayerAnalyticsTracker:
|
|
21
|
+
return new PlayerAnalyticsTracker();
|
|
22
|
+
default:
|
|
23
|
+
return new PlayerAnalyticsTracker();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class PlayerAnalyticsManager implements PlayerLifecycleListener {
|
|
29
|
+
static instance: PlayerAnalyticsManager;
|
|
30
|
+
private trackers: Record<string, PlayerAnalyticsTrackerI> = {};
|
|
31
|
+
private trackerFactory: PlayerAnalyticsTrackerFactory;
|
|
32
|
+
|
|
33
|
+
public getTracker(
|
|
34
|
+
id: string,
|
|
35
|
+
type: PlayerAnalyticsTrackerType = PlayerAnalyticsTrackerType.PlayerAnalyticsTracker
|
|
36
|
+
): PlayerAnalyticsTrackerI {
|
|
37
|
+
if (!this.trackers[id]) {
|
|
38
|
+
this.trackers[id] = this.trackerFactory.createTracker(type);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return this.trackers[id];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
constructor(trackerFactory: PlayerAnalyticsTrackerFactory) {
|
|
45
|
+
this.trackerFactory = trackerFactory;
|
|
46
|
+
this.startListenPlayerManager();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static getInstance(
|
|
50
|
+
trackerFactory: PlayerAnalyticsTrackerFactory = new PlayerAnalyticsTrackerFactory()
|
|
51
|
+
): PlayerAnalyticsManager {
|
|
52
|
+
if (!PlayerAnalyticsManager.instance) {
|
|
53
|
+
PlayerAnalyticsManager.instance = new PlayerAnalyticsManager(
|
|
54
|
+
trackerFactory
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return PlayerAnalyticsManager.instance;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private startListenPlayerManager() {
|
|
62
|
+
PlayerManager.getInstance().addLifecycleListener(this);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// PlayerLifecycleListener
|
|
66
|
+
|
|
67
|
+
onRegistered = (player: Player) => {
|
|
68
|
+
if (player.playerRole !== PlayerRole.Primary) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const tracker = this.getTracker(player.playerId);
|
|
73
|
+
tracker.setPlayer(player);
|
|
74
|
+
|
|
75
|
+
const analyticsListener = new AnalyticPlayerListener({
|
|
76
|
+
analyticsTracker: tracker,
|
|
77
|
+
player,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
player.addListener({
|
|
81
|
+
id: AnalyticPlayerListener.analyticsListenerID,
|
|
82
|
+
listener: analyticsListener,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
onUnRegistered = (player: Player) => {
|
|
87
|
+
player.removeListener(AnalyticPlayerListener.analyticsListenerID);
|
|
88
|
+
delete this.trackers[player.playerId];
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -5,16 +5,25 @@ import { postAnalyticEvent } from "./manager";
|
|
|
5
5
|
import { isLive } from "../playerUtils";
|
|
6
6
|
import { extensionsEvents } from "./AnalyticsEvents/helper";
|
|
7
7
|
import { AD_EVENT, EVENT_TYPES, PLAYER_DISPLAY_STATES } from "./events";
|
|
8
|
+
import { Player } from "../appUtils/playerManager/player";
|
|
8
9
|
|
|
9
|
-
export
|
|
10
|
+
export interface PlayerAnalyticsTrackerI {
|
|
11
|
+
handleAnalyticEvent(
|
|
12
|
+
event: QuickBrickPlayer.Events,
|
|
13
|
+
eventData?: QuickBrickPlayer.EventData
|
|
14
|
+
): void;
|
|
15
|
+
setPlayer(player: Player): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class PlayerAnalyticsTracker implements PlayerAnalyticsTrackerI {
|
|
10
19
|
adTime: number | string;
|
|
11
20
|
mediaTime: number | string;
|
|
12
|
-
entry: ZappEntry;
|
|
13
21
|
eventType: string;
|
|
14
22
|
playerState: QuickBrickPlayer.NativePlayerState;
|
|
23
|
+
private player: Player;
|
|
15
24
|
|
|
16
|
-
|
|
17
|
-
this.
|
|
25
|
+
setPlayer(player: Player) {
|
|
26
|
+
this.player = player;
|
|
18
27
|
}
|
|
19
28
|
|
|
20
29
|
isAdBreak(event: string): boolean {
|
|
@@ -30,6 +39,10 @@ export class AnalyticsTracker {
|
|
|
30
39
|
return !!isAdBreak;
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
get entry(): ZappEntry {
|
|
43
|
+
return this.player?.getEntry();
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
getDuration(
|
|
34
47
|
event: QuickBrickPlayer.Events,
|
|
35
48
|
eventData: QuickBrickPlayer.EventData
|
|
@@ -215,13 +228,9 @@ export class AnalyticsTracker {
|
|
|
215
228
|
|
|
216
229
|
handleAnalyticEvent(
|
|
217
230
|
event: QuickBrickPlayer.Events,
|
|
218
|
-
eventData: QuickBrickPlayer.EventData = {}
|
|
219
|
-
entry: ZappEntry,
|
|
220
|
-
playerState = null
|
|
231
|
+
eventData: QuickBrickPlayer.EventData = {}
|
|
221
232
|
) {
|
|
222
|
-
this.
|
|
223
|
-
this.playerState = playerState;
|
|
224
|
-
|
|
233
|
+
this.playerState = this.player?.getState();
|
|
225
234
|
postAnalyticEvent(event, this.prepareAnalyticPayload(event, eventData));
|
|
226
235
|
}
|
|
227
236
|
}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { useEffect, useMemo } from "react";
|
|
2
|
-
import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
|
|
3
2
|
import { AccessibilityManager } from "./index";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
/**
|
|
5
|
+
* This hook is used to instantiate the accessibility manager and update localizations used by the class.
|
|
6
|
+
* If the plugin config is not provided or it does not contain any localizations, the accessibility manager will not be updated.
|
|
7
|
+
* We use the plugin configuration because it offers a flattened structure where we can easily access the accessibility labels and hints
|
|
8
|
+
* i.e. pluginConfiguration = {
|
|
9
|
+
* accessibility_close_label: "Close",
|
|
10
|
+
* accessibility_close_hint: "Press here to close",
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
export const useAccessibilityManager = (
|
|
14
|
+
pluginConfiguration: Record<string, any> = {}
|
|
15
|
+
) => {
|
|
9
16
|
const accessibilityManager = useMemo(() => {
|
|
10
17
|
return AccessibilityManager.getInstance();
|
|
11
18
|
}, []);
|
|
12
19
|
|
|
13
20
|
useEffect(() => {
|
|
14
|
-
|
|
15
|
-
|
|
21
|
+
if (pluginConfiguration) {
|
|
22
|
+
accessibilityManager.updateLocalizations(pluginConfiguration);
|
|
23
|
+
}
|
|
24
|
+
}, [pluginConfiguration, accessibilityManager]);
|
|
16
25
|
|
|
17
26
|
useEffect(() => {
|
|
18
27
|
const subscription = accessibilityManager.getStateAsObservable().subscribe({
|
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import { BehaviorSubject } from "rxjs";
|
|
2
2
|
import { accessibilityManagerLogger as logger } from "./logger";
|
|
3
3
|
import { TTSManager } from "../platform/platformUtils";
|
|
4
|
-
import { getLocalizations } from "../../localizationUtils";
|
|
5
4
|
import { BUTTON_ACCESSIBILITY_KEYS } from "./const";
|
|
6
|
-
|
|
7
|
-
export interface AccessibilityState {
|
|
8
|
-
screenReaderEnabled: boolean;
|
|
9
|
-
reduceMotionEnabled: boolean;
|
|
10
|
-
boldTextEnabled: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface AccessibilityProps {
|
|
14
|
-
accessibilityLabel?: string;
|
|
15
|
-
accessibilityHint?: string;
|
|
16
|
-
"aria-label"?: string;
|
|
17
|
-
"aria-description"?: string;
|
|
18
|
-
}
|
|
5
|
+
import { AccessibilityRole } from "react-native";
|
|
19
6
|
|
|
20
7
|
export class AccessibilityManager {
|
|
21
8
|
private static _instance: AccessibilityManager | null = null;
|
|
@@ -32,13 +19,9 @@ export class AccessibilityManager {
|
|
|
32
19
|
} | null>(null);
|
|
33
20
|
|
|
34
21
|
private ttsManager = TTSManager.getInstance();
|
|
35
|
-
private localizations: { [key: string]: string };
|
|
22
|
+
private localizations: { [key: string]: string } = {};
|
|
36
23
|
|
|
37
|
-
private constructor(
|
|
38
|
-
if (localizations) {
|
|
39
|
-
this.localizations = getLocalizations({ localizations });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
24
|
+
private constructor() {}
|
|
42
25
|
|
|
43
26
|
public static getInstance(): AccessibilityManager {
|
|
44
27
|
if (!AccessibilityManager._instance) {
|
|
@@ -48,8 +31,17 @@ export class AccessibilityManager {
|
|
|
48
31
|
return AccessibilityManager._instance;
|
|
49
32
|
}
|
|
50
33
|
|
|
34
|
+
/**
|
|
35
|
+
* The method now accepts any object with localizations using a flattened structure
|
|
36
|
+
*
|
|
37
|
+
* i.e. { accessibility_close_label: "Close", accessibility_close_hint: "Press here to close" }
|
|
38
|
+
*
|
|
39
|
+
* No longer accepts:
|
|
40
|
+
*
|
|
41
|
+
* i.e. localizations: [{ en: { accessibility_close_label: "Close", accessibility_close_hint: "Press here to close" } }]
|
|
42
|
+
*/
|
|
51
43
|
public updateLocalizations(localizations: { [key: string]: string }) {
|
|
52
|
-
this.localizations =
|
|
44
|
+
this.localizations = localizations;
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
public getState(): AccessibilityState {
|
|
@@ -71,7 +63,7 @@ export class AccessibilityManager {
|
|
|
71
63
|
text: string;
|
|
72
64
|
keyOfLocalizedText?: string;
|
|
73
65
|
}) {
|
|
74
|
-
|
|
66
|
+
let textToRead = text;
|
|
75
67
|
|
|
76
68
|
if (keyOfLocalizedText) {
|
|
77
69
|
if (!this.localizations) {
|
|
@@ -90,16 +82,25 @@ export class AccessibilityManager {
|
|
|
90
82
|
return;
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
|
|
85
|
+
textToRead = localizedMessage;
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
if (this.ttsManager) {
|
|
97
|
-
|
|
89
|
+
logger.debug({
|
|
90
|
+
message: `Reading text: ${textToRead}`,
|
|
91
|
+
data: {
|
|
92
|
+
textToRead,
|
|
93
|
+
text,
|
|
94
|
+
keyOfLocalizedText,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
this.ttsManager.readText(textToRead);
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
this.announcements$.next({
|
|
101
|
-
message:
|
|
102
|
-
localizedMessage: keyOfLocalizedText ?
|
|
102
|
+
message: textToRead,
|
|
103
|
+
localizedMessage: keyOfLocalizedText ? textToRead : undefined,
|
|
103
104
|
timestamp: Date.now(),
|
|
104
105
|
});
|
|
105
106
|
}
|
|
@@ -113,6 +114,8 @@ export class AccessibilityManager {
|
|
|
113
114
|
accessibilityHint: `Press button to perform action on ${buttonName}`,
|
|
114
115
|
"aria-label": buttonName,
|
|
115
116
|
"aria-description": `Press button to perform action on ${buttonName}`,
|
|
117
|
+
accessibilityRole: "button" as AccessibilityRole,
|
|
118
|
+
"aria-role": "button",
|
|
116
119
|
};
|
|
117
120
|
}
|
|
118
121
|
|
|
@@ -130,6 +133,19 @@ export class AccessibilityManager {
|
|
|
130
133
|
accessibilityHint: hint,
|
|
131
134
|
"aria-label": label,
|
|
132
135
|
"aria-description": hint,
|
|
136
|
+
accessibilityRole: "button" as AccessibilityRole,
|
|
137
|
+
"aria-role": "button",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public getInputAccessibilityProps(inputName: string): AccessibilityProps {
|
|
142
|
+
return {
|
|
143
|
+
accessibilityLabel: inputName,
|
|
144
|
+
accessibilityHint: `Enter text into ${inputName}`,
|
|
145
|
+
"aria-label": inputName,
|
|
146
|
+
"aria-description": `Enter text into ${inputName}`,
|
|
147
|
+
accessibilityRole: "textbox" as AccessibilityRole,
|
|
148
|
+
"aria-role": "textbox",
|
|
133
149
|
};
|
|
134
150
|
}
|
|
135
151
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { BehaviorSubject } from "rxjs";
|
|
2
2
|
|
|
3
3
|
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
|
|
4
|
-
import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
|
|
5
|
-
import { PlayerWebController } from "@applicaster/zapp-react-native-default-player/src/playerFactory/players/playerWebController";
|
|
6
4
|
|
|
7
5
|
import { PLATFORM_KEYS, PLATFORMS } from "./const";
|
|
8
6
|
import { createLogger, utilsLogger } from "../../logger";
|
|
@@ -21,7 +19,7 @@ declare global {
|
|
|
21
19
|
}
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
const { log_debug
|
|
22
|
+
const { log_debug } = createLogger({
|
|
25
23
|
category: "General",
|
|
26
24
|
subsystem: "PlatformUtils",
|
|
27
25
|
parent: utilsLogger,
|
|
@@ -131,100 +129,6 @@ export const getClosedCaptionState = () => {
|
|
|
131
129
|
return ccManager.getStateAsObservable();
|
|
132
130
|
};
|
|
133
131
|
|
|
134
|
-
export class TextTrackManager {
|
|
135
|
-
private controller?: PlayerWebController;
|
|
136
|
-
private static instance: TextTrackManager;
|
|
137
|
-
|
|
138
|
-
private constructor() {}
|
|
139
|
-
|
|
140
|
-
static getInstance(): TextTrackManager {
|
|
141
|
-
if (!TextTrackManager.instance) {
|
|
142
|
-
TextTrackManager.instance = new TextTrackManager();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return TextTrackManager.instance;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
initialize(controller: PlayerWebController) {
|
|
149
|
-
if (!controller) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
"Controller is required for TextTrackManager initialization"
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
this.controller = controller;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async setTextTrackBasedOnCaptions(closedCaptionsState: boolean) {
|
|
159
|
-
if (!this.controller) {
|
|
160
|
-
log_error("Controller is not initialized");
|
|
161
|
-
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (closedCaptionsState) {
|
|
166
|
-
const textTracks = this.controller.getTracksState().textTracks;
|
|
167
|
-
const preferredTextTrack = await this.getPreferredTextTrack(textTracks);
|
|
168
|
-
|
|
169
|
-
if (preferredTextTrack) {
|
|
170
|
-
this.controller.selectTrack(preferredTextTrack);
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
this.controller.selectTrack(null);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async getPreferredTextTrack(
|
|
178
|
-
textTracks: QuickBrickPlayer.TextTrack[]
|
|
179
|
-
): Promise<QuickBrickPlayer.TextTrack | undefined> {
|
|
180
|
-
if (!this.controller) return;
|
|
181
|
-
|
|
182
|
-
const userPreferredTextLanguageLocalStorage = await localStorage.getItem(
|
|
183
|
-
"preferred_text_language",
|
|
184
|
-
"user_preferences"
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
let userPreferredTextLanguage;
|
|
188
|
-
|
|
189
|
-
if (userPreferredTextLanguageLocalStorage) {
|
|
190
|
-
userPreferredTextLanguage = textTracks.find(
|
|
191
|
-
(track) => track.language === userPreferredTextLanguageLocalStorage
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (userPreferredTextLanguage?.id) {
|
|
196
|
-
return userPreferredTextLanguage;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const enableAutoTextTrackSelection =
|
|
200
|
-
this.controller.getPluginConfiguration()
|
|
201
|
-
?.enable_auto_text_track_selection ||
|
|
202
|
-
this.controller.getTracksState().textTracks.length > 0;
|
|
203
|
-
|
|
204
|
-
if (enableAutoTextTrackSelection) {
|
|
205
|
-
const languageCode = await sessionStorage.getItem("languageCode");
|
|
206
|
-
|
|
207
|
-
if (languageCode) {
|
|
208
|
-
const chosenTextTrackBySystemLanguage = textTracks.find(
|
|
209
|
-
(track) => track.language === languageCode
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
if (chosenTextTrackBySystemLanguage?.id) {
|
|
213
|
-
return chosenTextTrackBySystemLanguage;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return textTracks?.[0];
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
isTextTrackEnabled(): boolean {
|
|
222
|
-
if (!this.controller) return false;
|
|
223
|
-
|
|
224
|
-
return this.controller.getTracksState().textTrackId !== null;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
132
|
/**
|
|
229
133
|
* Manages device based closed captioning state
|
|
230
134
|
* Returns an observable that emits the current state and updates whenever it changes
|
|
@@ -3,7 +3,6 @@ import * as R from "ramda";
|
|
|
3
3
|
import { subscriber } from "../../functionUtils";
|
|
4
4
|
import { utilsLogger, createLogger } from "../../logger";
|
|
5
5
|
import { Player } from "./player";
|
|
6
|
-
import { AnalyticsTracker } from "../../analyticsUtils/analyticsTracker";
|
|
7
6
|
import {
|
|
8
7
|
hasTizenAPIs,
|
|
9
8
|
subscribeToDeviceNetworkChanges,
|
|
@@ -37,7 +36,10 @@ export interface PlayerLifecycleListener {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
type PlayersMap = {
|
|
40
|
-
[id: string]: {
|
|
39
|
+
[id: string]: {
|
|
40
|
+
player: Player;
|
|
41
|
+
isInViewport: boolean;
|
|
42
|
+
};
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
export class PlayerManager {
|
|
@@ -46,7 +48,6 @@ export class PlayerManager {
|
|
|
46
48
|
private playersMap: PlayersMap = {};
|
|
47
49
|
private manager = subscriber();
|
|
48
50
|
private castingReceiver: Player;
|
|
49
|
-
private analyticsTracker: AnalyticsTracker;
|
|
50
51
|
private lifecycleListeners: PlayerLifecycleListener[] = [];
|
|
51
52
|
|
|
52
53
|
public static getInstance() {
|
|
@@ -57,7 +58,7 @@ export class PlayerManager {
|
|
|
57
58
|
return PlayerManager.instance;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
public getActivePlayerId = ():
|
|
61
|
+
public getActivePlayerId = (): string | null => {
|
|
61
62
|
return this.activePlayerId;
|
|
62
63
|
};
|
|
63
64
|
|
|
@@ -102,7 +103,10 @@ export class PlayerManager {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
private addPlayer = (id: string, player: Player) => {
|
|
105
|
-
this.playersMap[id] = {
|
|
106
|
+
this.playersMap[id] = {
|
|
107
|
+
player: player,
|
|
108
|
+
isInViewport: false,
|
|
109
|
+
};
|
|
106
110
|
};
|
|
107
111
|
|
|
108
112
|
public isPlayerRegistered = (id: string) => !!this.playersMap[id];
|
|
@@ -251,28 +255,6 @@ export class PlayerManager {
|
|
|
251
255
|
this.castingReceiver = null;
|
|
252
256
|
}
|
|
253
257
|
|
|
254
|
-
/**
|
|
255
|
-
* Handles analytic events, generates a new analytics tracker and transmits events
|
|
256
|
-
* Passes entry data, and playerState to the tracker for additional context
|
|
257
|
-
* The end result is an event formatted and passed to AnalyticsBridge.postEvent
|
|
258
|
-
* When we get the synthetic event we need to pull the data in the e object
|
|
259
|
-
* e contains the return value from native
|
|
260
|
-
*/
|
|
261
|
-
public handleAnalyticEvent(event, data = {}, entry: ZappEntry) {
|
|
262
|
-
const playerState = this.getActivePlayer()?.getState();
|
|
263
|
-
|
|
264
|
-
if (!this.analyticsTracker?.handleAnalyticEvent) {
|
|
265
|
-
this.analyticsTracker = new AnalyticsTracker(entry);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
this.analyticsTracker?.handleAnalyticEvent?.(
|
|
269
|
-
event,
|
|
270
|
-
data,
|
|
271
|
-
entry,
|
|
272
|
-
playerState
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
258
|
/**
|
|
277
259
|
* Method retrieves the instantiated player instance controller
|
|
278
260
|
* Instance controller exposes methods like getContentDuration, getContentPosision
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { Player } from "../player";
|
|
3
|
+
import { usePlayer } from "../usePlayer";
|
|
4
|
+
import { useThrottle } from "../../../functionUtils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param listenerId - A unique identifier for the listener.
|
|
9
|
+
* @param playerId - (Optional) The ID of the player to observe.
|
|
10
|
+
* @returns The current playback time in seconds.
|
|
11
|
+
*/
|
|
12
|
+
export const usePlayerCurrentTime = (
|
|
13
|
+
listenerId: string,
|
|
14
|
+
playerId?: string
|
|
15
|
+
): number | null => {
|
|
16
|
+
const [currentTime, setCurrentTime] = useState<number | null>(null);
|
|
17
|
+
const player: Player = usePlayer(playerId);
|
|
18
|
+
|
|
19
|
+
const updateCurrentTime = useCallback(() => {
|
|
20
|
+
if (player) {
|
|
21
|
+
const newCurrentTime = player.getContentPosition();
|
|
22
|
+
|
|
23
|
+
setCurrentTime((prevTime) =>
|
|
24
|
+
prevTime !== newCurrentTime ? newCurrentTime : prevTime
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}, [player]);
|
|
28
|
+
|
|
29
|
+
const { throttledFunction: throttledUpdate, cancel } = useThrottle(
|
|
30
|
+
updateCurrentTime,
|
|
31
|
+
1000
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (player) {
|
|
36
|
+
throttledUpdate();
|
|
37
|
+
|
|
38
|
+
const listeners = {
|
|
39
|
+
onVideoProgress: throttledUpdate,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const id = `${listenerId}_player_current_time`;
|
|
43
|
+
|
|
44
|
+
player.addListener({
|
|
45
|
+
id: id,
|
|
46
|
+
listener: listeners,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
player.removeListener(id);
|
|
51
|
+
cancel();
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}, [player, listenerId, throttledUpdate, cancel]);
|
|
55
|
+
|
|
56
|
+
return currentTime;
|
|
57
|
+
};
|
|
@@ -61,7 +61,7 @@ export class PlayerNative extends Player {
|
|
|
61
61
|
currentPlayerComponent = (): ReactComponent<PlayerProps> =>
|
|
62
62
|
this.playerComponent?.current;
|
|
63
63
|
|
|
64
|
-
getState = () => this.
|
|
64
|
+
getState = () => this.playerState;
|
|
65
65
|
|
|
66
66
|
// TODO: Try to add module interface
|
|
67
67
|
invokeNativeFunction = (funcName: string, ...params: any[]): boolean => {
|
|
@@ -50,7 +50,7 @@ export const usePlayerControllerSetup = ({
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
setPlayerController(newPlayerController);
|
|
53
|
-
}, [playerId, entry?.id]);
|
|
53
|
+
}, [playerId, entry?.id, ref.current]);
|
|
54
54
|
|
|
55
55
|
React.useEffect(() => {
|
|
56
56
|
if (!playerController) {
|
|
@@ -72,6 +72,7 @@ export const usePlayerControllerSetup = ({
|
|
|
72
72
|
|
|
73
73
|
return () => {
|
|
74
74
|
playerManager.unregisterPlayer(playerId);
|
|
75
|
+
playerController.destroy?.();
|
|
75
76
|
};
|
|
76
77
|
}, [playerId, playerController]);
|
|
77
78
|
|
|
@@ -16,24 +16,28 @@ export const usePlayerState = (
|
|
|
16
16
|
listenerId: string,
|
|
17
17
|
playerId?: string
|
|
18
18
|
): PlayerState => {
|
|
19
|
-
const [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const [state, setState] = React.useState<PlayerState>({
|
|
20
|
+
currentTime: null,
|
|
21
|
+
duration: null,
|
|
22
|
+
seekableDuration: null,
|
|
23
|
+
isLive: null,
|
|
24
|
+
isPaused: null,
|
|
25
|
+
isBuffering: false,
|
|
26
|
+
isReadyToPlay: false,
|
|
27
|
+
});
|
|
26
28
|
|
|
27
29
|
const player: Player = usePlayer(playerId);
|
|
28
30
|
|
|
29
31
|
const onPlayerChangeState = React.useCallback(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
setState({
|
|
33
|
+
currentTime: player.getContentPosition(),
|
|
34
|
+
duration: player.getDuration(),
|
|
35
|
+
seekableDuration: player.getSeekableDuration(),
|
|
36
|
+
isLive: player.isLive(),
|
|
37
|
+
isPaused: player.isPaused(),
|
|
38
|
+
isBuffering: player.isBuffering(),
|
|
39
|
+
isReadyToPlay: player.isReadyToPlay(),
|
|
40
|
+
});
|
|
37
41
|
}, [player]);
|
|
38
42
|
|
|
39
43
|
React.useEffect(() => {
|
|
@@ -55,24 +59,5 @@ export const usePlayerState = (
|
|
|
55
59
|
}
|
|
56
60
|
}, [player]);
|
|
57
61
|
|
|
58
|
-
return
|
|
59
|
-
() => ({
|
|
60
|
-
currentTime,
|
|
61
|
-
duration,
|
|
62
|
-
seekableDuration,
|
|
63
|
-
isLive,
|
|
64
|
-
isPaused,
|
|
65
|
-
isBuffering,
|
|
66
|
-
isReadyToPlay,
|
|
67
|
-
}),
|
|
68
|
-
[
|
|
69
|
-
currentTime,
|
|
70
|
-
duration,
|
|
71
|
-
seekableDuration,
|
|
72
|
-
isLive,
|
|
73
|
-
isPaused,
|
|
74
|
-
isBuffering,
|
|
75
|
-
isReadyToPlay,
|
|
76
|
-
]
|
|
77
|
-
);
|
|
62
|
+
return state;
|
|
78
63
|
};
|
package/cellUtils/index.ts
CHANGED
|
@@ -31,13 +31,12 @@ export const getKeyForLabel = (dataKey, customKey) => {
|
|
|
31
31
|
* This method will return true if the argument passed to it is either empty or nil
|
|
32
32
|
* The method prevents zero from being evaluated as falsey in an || condition
|
|
33
33
|
* i.e. - NaN, null, {}, "", [], should all return true
|
|
34
|
-
* @param {*} argument - argument passed to it could be any type
|
|
35
34
|
* @returns {Boolean} returns true if empty or nil
|
|
35
|
+
* @param value
|
|
36
36
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
37
|
+
|
|
38
|
+
export const isEmptyOrNil = (value: any): boolean =>
|
|
39
|
+
Number.isNaN(value) || value == null || R.isEmpty(value);
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
42
|
* This method is used to find the appropriate label for any given text field.
|
|
@@ -320,6 +319,7 @@ export const ifElseFocusedSelected = (
|
|
|
320
319
|
* @param {Object} stylesObj styles object with keys from zapp { button_icon_width: "10" }
|
|
321
320
|
* @returns {*} value, returns any type that can be used as styles
|
|
322
321
|
*/
|
|
322
|
+
|
|
323
323
|
export const getValue = (stylesObj) => (key) => {
|
|
324
324
|
return R.propOr(null, key, stylesObj);
|
|
325
325
|
};
|
|
@@ -468,3 +468,36 @@ export const getColorFromData = ({
|
|
|
468
468
|
|
|
469
469
|
return valueFromLayout;
|
|
470
470
|
};
|
|
471
|
+
|
|
472
|
+
export const getImageStyles = ({
|
|
473
|
+
image,
|
|
474
|
+
value,
|
|
475
|
+
}: {
|
|
476
|
+
image: any;
|
|
477
|
+
value: any;
|
|
478
|
+
}) => {
|
|
479
|
+
return {
|
|
480
|
+
width: image.imageContentWidth,
|
|
481
|
+
height: image.imageContentHeight,
|
|
482
|
+
aspectRatio: image.aspectRatio,
|
|
483
|
+
borderRadius: toNumberWithDefaultZero(value("image_corner_radius")),
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
export const getImageContainerPaddingStyles = ({ value }: { value: any }) => {
|
|
488
|
+
return {
|
|
489
|
+
paddingTop: value("image_padding_top"),
|
|
490
|
+
paddingBottom: value("image_padding_bottom"),
|
|
491
|
+
paddingLeft: value("image_padding_left"),
|
|
492
|
+
paddingRight: value("image_padding_right"),
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
export const getImageContainerMarginStyles = ({ value }: { value: any }) => {
|
|
497
|
+
return {
|
|
498
|
+
marginTop: value("image_margin_top"),
|
|
499
|
+
marginBottom: value("image_margin_bottom"),
|
|
500
|
+
marginLeft: value("image_margin_left"),
|
|
501
|
+
marginRight: value("image_margin_right"),
|
|
502
|
+
};
|
|
503
|
+
};
|
package/functionUtils/index.ts
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param callback - The function to throttle.
|
|
7
|
+
* @param delay - The delay in milliseconds.
|
|
8
|
+
* @returns A throttled version of the callback.
|
|
9
|
+
*/
|
|
10
|
+
export const useThrottle = (callback: () => void, delay: number) => {
|
|
11
|
+
const lastCallRef = useRef<number>(0);
|
|
12
|
+
const timeoutRef = useRef<any | null>(null);
|
|
13
|
+
|
|
14
|
+
const throttledFunction = () => {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
|
|
17
|
+
if (now - lastCallRef.current >= delay) {
|
|
18
|
+
callback();
|
|
19
|
+
lastCallRef.current = now;
|
|
20
|
+
} else if (!timeoutRef.current) {
|
|
21
|
+
const remainingTime = delay - (now - lastCallRef.current);
|
|
22
|
+
|
|
23
|
+
timeoutRef.current = setTimeout(() => {
|
|
24
|
+
callback();
|
|
25
|
+
lastCallRef.current = Date.now();
|
|
26
|
+
timeoutRef.current = null;
|
|
27
|
+
}, remainingTime);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const cancel = () => {
|
|
32
|
+
if (timeoutRef.current) {
|
|
33
|
+
clearTimeout(timeoutRef.current);
|
|
34
|
+
timeoutRef.current = null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return { throttledFunction, cancel };
|
|
39
|
+
};
|
|
2
40
|
|
|
3
41
|
/**
|
|
4
42
|
* standard debounce function.
|
package/index.d.ts
CHANGED
|
@@ -143,3 +143,18 @@ declare type BuilderData = {
|
|
|
143
143
|
river?: ZappRiver;
|
|
144
144
|
method?: string;
|
|
145
145
|
};
|
|
146
|
+
|
|
147
|
+
declare type AccessibilityState = {
|
|
148
|
+
screenReaderEnabled: boolean;
|
|
149
|
+
reduceMotionEnabled: boolean;
|
|
150
|
+
boldTextEnabled: boolean;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
declare type AccessibilityProps = {
|
|
154
|
+
accessibilityLabel?: string;
|
|
155
|
+
accessibilityHint?: string;
|
|
156
|
+
"aria-label"?: string;
|
|
157
|
+
"aria-description"?: string;
|
|
158
|
+
accessibilityRole?: string;
|
|
159
|
+
"aria-role"?: string;
|
|
160
|
+
};
|
package/navigationUtils/index.ts
CHANGED
|
@@ -592,3 +592,51 @@ export function isMenuDisabled(
|
|
|
592
592
|
export function routeIsPlayerScreen(currentRoute) {
|
|
593
593
|
return currentRoute?.includes("/playable");
|
|
594
594
|
}
|
|
595
|
+
|
|
596
|
+
export const getNavBarProps =
|
|
597
|
+
(currentRiver: ZappRiver, pathname: string, title: string) => () => {
|
|
598
|
+
const props = getNavigationPropsV2({
|
|
599
|
+
currentRiver,
|
|
600
|
+
title,
|
|
601
|
+
category: "nav_bar",
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
if (props) {
|
|
605
|
+
return {
|
|
606
|
+
...props,
|
|
607
|
+
id: pathname,
|
|
608
|
+
pathname: pathname,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return null;
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
export const findMenuPlugin = (
|
|
616
|
+
navigations: ZappNavigation[],
|
|
617
|
+
plugins
|
|
618
|
+
): QuickBrickPlugin => {
|
|
619
|
+
return resolveNavigationPlugin({
|
|
620
|
+
category: "menu",
|
|
621
|
+
navigations,
|
|
622
|
+
plugins,
|
|
623
|
+
}) as QuickBrickPlugin;
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
export const shouldNavBarDisplayMenu = (currentRiver: ZappRiver, plugins) =>
|
|
627
|
+
findMenuPlugin(currentRiver.navigations, plugins)?.identifier ===
|
|
628
|
+
"quick_brick_side_menu" ||
|
|
629
|
+
currentRiver?.navigations.findIndex(
|
|
630
|
+
(nav) => nav.category === "nav_bar" && nav.general?.override_menu_target
|
|
631
|
+
) > -1;
|
|
632
|
+
|
|
633
|
+
export const getScreenId = (
|
|
634
|
+
screenData: LegacyNavigationScreenData,
|
|
635
|
+
fallbackRiver: ZappRiver
|
|
636
|
+
) => {
|
|
637
|
+
return screenData?.targetScreen
|
|
638
|
+
? screenData?.targetScreen?.id ?? fallbackRiver.id
|
|
639
|
+
: "screen_id" in screenData
|
|
640
|
+
? screenData.screen_id
|
|
641
|
+
: fallbackRiver.id;
|
|
642
|
+
};
|
|
@@ -2,7 +2,7 @@ import { toNumber } from "..";
|
|
|
2
2
|
|
|
3
3
|
describe("toNumber", () => {
|
|
4
4
|
it("return number if input is number", () => {
|
|
5
|
-
const inputs = [-1, 0, 1, 100];
|
|
5
|
+
const inputs = [-1, 0, 1, 100, 0.2];
|
|
6
6
|
expect.assertions(inputs.length);
|
|
7
7
|
|
|
8
8
|
inputs.forEach((input) => {
|
|
@@ -12,7 +12,7 @@ describe("toNumber", () => {
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
it("return number if input is string as number", () => {
|
|
15
|
-
const inputs = ["-1", "0", "1", "100"];
|
|
15
|
+
const inputs = ["-1", "0", "1", "100", "0.2"];
|
|
16
16
|
expect.assertions(inputs.length);
|
|
17
17
|
|
|
18
18
|
inputs.forEach((input) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "12.0.0-alpha.
|
|
3
|
+
"version": "12.0.0-alpha.4220424021",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "12.0.0-alpha.
|
|
31
|
+
"@applicaster/applicaster-types": "12.0.0-alpha.4220424021",
|
|
32
32
|
"buffer": "^5.2.1",
|
|
33
33
|
"camelize": "^1.0.0",
|
|
34
34
|
"create-react-context": "^0.2.3",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createLogger } from "../logger";
|
|
2
|
+
|
|
3
|
+
export const logger = createLogger({
|
|
4
|
+
category: "ImaDomUtils",
|
|
5
|
+
subsystem: "Player/ImaDomUtils",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const { log_debug, log_error, log_verbose } = logger;
|
|
9
|
+
|
|
10
|
+
const removeElement = (node) => {
|
|
11
|
+
if (node) {
|
|
12
|
+
const parentNode = node.parentNode;
|
|
13
|
+
|
|
14
|
+
if (parentNode) {
|
|
15
|
+
parentNode.removeChild(node);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// TODO: This should probably be loaded to root of project before player init
|
|
21
|
+
export const loadIMAScript = async () => {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
// If ima script is already loaded, resolve
|
|
24
|
+
const imaScript = document.getElementById("ima-script");
|
|
25
|
+
|
|
26
|
+
if (imaScript) {
|
|
27
|
+
log_verbose("loadIMAScript: IMA script already loaded");
|
|
28
|
+
|
|
29
|
+
return resolve(true);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const script = document.createElement("script");
|
|
33
|
+
|
|
34
|
+
if (__DEV__) {
|
|
35
|
+
script.src = "https://imasdk.googleapis.com/js/sdkloader/ima3_debug.js";
|
|
36
|
+
} else {
|
|
37
|
+
script.src = "https://imasdk.googleapis.com/js/sdkloader/ima3.js";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
script.async = true;
|
|
41
|
+
script.id = "ima-script";
|
|
42
|
+
|
|
43
|
+
script.onload = () => {
|
|
44
|
+
removeElement(script);
|
|
45
|
+
log_debug("loadIMAScript: Loaded ima script", script.src);
|
|
46
|
+
resolve(true);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
script.onerror = (_e) => {
|
|
50
|
+
removeElement(script);
|
|
51
|
+
log_error("loadIMAScript: Failed to load ima script", script.src);
|
|
52
|
+
resolve(false);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (document.head) {
|
|
56
|
+
document.head.appendChild(script);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
};
|
|
@@ -4,38 +4,77 @@ import {
|
|
|
4
4
|
timers,
|
|
5
5
|
} from "@applicaster/zapp-react-native-utils/reactHooks/debugging";
|
|
6
6
|
|
|
7
|
-
jest.useFakeTimers();
|
|
8
|
-
|
|
9
7
|
describe("Debug utils", () => {
|
|
8
|
+
let performanceNowMock;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.useFakeTimers();
|
|
12
|
+
// Clear the timers object
|
|
13
|
+
Object.keys(timers).forEach((key) => delete timers[key]);
|
|
14
|
+
|
|
15
|
+
// Mock performance.now()
|
|
16
|
+
// eslint-disable-next-line no-undef
|
|
17
|
+
performanceNowMock = jest.spyOn(performance, "now");
|
|
18
|
+
performanceNowMock.mockReturnValue(0); // Initial value
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
jest.useRealTimers();
|
|
23
|
+
jest.restoreAllMocks();
|
|
24
|
+
performanceNowMock.mockRestore();
|
|
25
|
+
});
|
|
26
|
+
|
|
10
27
|
describe("Unit Test: time", () => {
|
|
11
28
|
it("should create new timer if timer with a label does not exist", () => {
|
|
29
|
+
performanceNowMock.mockReturnValue(1000);
|
|
12
30
|
time("timer1");
|
|
13
31
|
expect(timers["timer1"]).toBeDefined();
|
|
32
|
+
expect(timers["timer1"].startTime).toBe(1000);
|
|
14
33
|
});
|
|
15
34
|
|
|
16
35
|
it("should update start time of timer if timer with a label exists", () => {
|
|
36
|
+
// First call to time()
|
|
37
|
+
performanceNowMock.mockReturnValue(1000);
|
|
38
|
+
time("timer1");
|
|
17
39
|
const previousStartTime = timers["timer1"].startTime;
|
|
18
|
-
|
|
40
|
+
|
|
41
|
+
// Second call to time()
|
|
42
|
+
performanceNowMock.mockReturnValue(2000);
|
|
19
43
|
time("timer1");
|
|
20
44
|
const currentStartTime = timers["timer1"].startTime;
|
|
21
|
-
|
|
45
|
+
|
|
46
|
+
expect(previousStartTime).toBe(1000);
|
|
47
|
+
expect(currentStartTime).toBe(2000);
|
|
22
48
|
});
|
|
23
49
|
});
|
|
24
50
|
|
|
25
51
|
describe("Unit Test: timeEnd", () => {
|
|
26
|
-
beforeEach(() =>
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
54
|
+
});
|
|
27
55
|
|
|
28
56
|
it("should log elapsed time", () => {
|
|
29
57
|
const logSpy = jest.spyOn(console, "log");
|
|
30
58
|
|
|
59
|
+
performanceNowMock.mockReturnValue(1000);
|
|
60
|
+
time("timer1");
|
|
61
|
+
|
|
62
|
+
performanceNowMock.mockReturnValue(2000);
|
|
31
63
|
timeEnd("timer1");
|
|
32
64
|
|
|
33
|
-
expect(logSpy).
|
|
65
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
66
|
+
"timer1: 1000.000ms 2",
|
|
67
|
+
expect.any(Object)
|
|
68
|
+
);
|
|
34
69
|
});
|
|
35
70
|
|
|
36
71
|
it("should not log elapsed time if accumulative: true in settings", () => {
|
|
37
72
|
const logSpy = jest.spyOn(console, "log");
|
|
38
73
|
|
|
74
|
+
performanceNowMock.mockReturnValue(1000);
|
|
75
|
+
time("timer1");
|
|
76
|
+
|
|
77
|
+
performanceNowMock.mockReturnValue(2000);
|
|
39
78
|
timeEnd("timer1", { accumulative: true });
|
|
40
79
|
|
|
41
80
|
expect(logSpy).not.toHaveBeenCalled();
|
|
@@ -43,21 +82,52 @@ describe("Debug utils", () => {
|
|
|
43
82
|
});
|
|
44
83
|
|
|
45
84
|
describe("Integration Test: time and timeEnd", () => {
|
|
46
|
-
beforeEach(() =>
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
87
|
+
});
|
|
47
88
|
|
|
48
89
|
it("should log elapsed time on timeEnd call", () => {
|
|
49
90
|
const logSpy = jest.spyOn(console, "log");
|
|
91
|
+
|
|
92
|
+
performanceNowMock.mockReturnValue(1000);
|
|
50
93
|
time("timer1");
|
|
94
|
+
|
|
95
|
+
performanceNowMock.mockReturnValue(2000);
|
|
51
96
|
timeEnd("timer1");
|
|
52
|
-
|
|
97
|
+
|
|
98
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
99
|
+
"timer1: 1000.000ms 2",
|
|
100
|
+
expect.objectContaining({
|
|
101
|
+
label: "timer1",
|
|
102
|
+
settings: undefined,
|
|
103
|
+
})
|
|
104
|
+
);
|
|
53
105
|
});
|
|
54
106
|
|
|
55
|
-
it("should log total and average time
|
|
107
|
+
it("should log total and average time when accumulative timing is complete", () => {
|
|
56
108
|
const logSpy = jest.spyOn(console, "log");
|
|
109
|
+
|
|
110
|
+
// First timing
|
|
111
|
+
performanceNowMock.mockReturnValue(1000);
|
|
57
112
|
time("timer2");
|
|
113
|
+
|
|
114
|
+
performanceNowMock.mockReturnValue(2000);
|
|
58
115
|
timeEnd("timer2", { accumulative: true });
|
|
59
|
-
|
|
60
|
-
|
|
116
|
+
|
|
117
|
+
// Second timing
|
|
118
|
+
performanceNowMock.mockReturnValue(3000);
|
|
119
|
+
time("timer2");
|
|
120
|
+
|
|
121
|
+
performanceNowMock.mockReturnValue(5000);
|
|
122
|
+
timeEnd("timer2"); // This will log because timer is accumulative (callCount > 1)
|
|
123
|
+
|
|
124
|
+
expect(logSpy).toHaveBeenCalledWith(
|
|
125
|
+
"timer2: 2000.000ms 2",
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
label: "timer2",
|
|
128
|
+
settings: undefined,
|
|
129
|
+
})
|
|
130
|
+
);
|
|
61
131
|
});
|
|
62
132
|
});
|
|
63
133
|
});
|
|
@@ -48,7 +48,11 @@ export function useCellResolver({
|
|
|
48
48
|
...cellResolverOptions
|
|
49
49
|
}: Props) {
|
|
50
50
|
let _component = Object.assign({}, component);
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
const { width, height } = useDimensions("screen", {
|
|
53
|
+
updateForInactiveScreens: false,
|
|
54
|
+
});
|
|
55
|
+
|
|
52
56
|
const { plugins, cellStyles } = usePickFromState(["plugins", "cellStyles"]);
|
|
53
57
|
const isRTL = useIsRTL();
|
|
54
58
|
|
package/reactUtils/index.ts
CHANGED
|
@@ -152,8 +152,7 @@ export const hasTizen = typeof window?.tizen !== "undefined";
|
|
|
152
152
|
*/
|
|
153
153
|
export const hasWebOS = typeof window?.webOS !== "undefined";
|
|
154
154
|
|
|
155
|
-
export const getUserAgent = () =>
|
|
156
|
-
window?.navigator?.userAgent || global?.navigator?.userAgent;
|
|
155
|
+
export const getUserAgent = () => window?.navigator?.userAgent;
|
|
157
156
|
|
|
158
157
|
export const isLgPlatform = () => {
|
|
159
158
|
const userAgent = getUserAgent();
|