@applicaster/zapp-react-native-ui-components 15.0.0-alpha.3564377339 → 15.0.0-alpha.3564837831
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/Components/Focusable/Focusable.tsx +4 -2
- package/Components/Focusable/FocusableTvOS.tsx +17 -1
- package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
- package/Components/FocusableGroup/FocusableTvOS.tsx +53 -1
- package/Components/FocusableGroup/hooks/__tests__/useIsFocusEnabled.test.ts +113 -0
- package/Components/FocusableGroup/hooks/index.ts +1 -0
- package/Components/FocusableGroup/hooks/useIsFocusEnabled.ts +68 -0
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +12 -3
- package/Components/PlayerContainer/PlayerContainer.tsx +48 -38
- package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +101 -0
- package/Components/River/TV/River.tsx +31 -14
- package/Components/River/TV/index.tsx +8 -4
- package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
- package/Components/River/TV/utils/index.ts +4 -0
- package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
- package/Contexts/AboveTabsScreenContext/index.tsx +33 -0
- package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
- package/Decorators/ConfigurationWrapper/const.ts +1 -0
- package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
- package/package.json +5 -5
- package/Components/River/TV/withTVEventHandler.tsx +0 -27
|
@@ -8,6 +8,8 @@ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withF
|
|
|
8
8
|
import { StyleSheet, ViewStyle } from "react-native";
|
|
9
9
|
import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
|
|
10
10
|
|
|
11
|
+
import { isSearchInputId } from "@applicaster/zapp-react-native-utils/searchUtils";
|
|
12
|
+
|
|
11
13
|
type Props = {
|
|
12
14
|
initialFocus?: boolean;
|
|
13
15
|
id: string;
|
|
@@ -106,7 +108,7 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
106
108
|
onMouseEnter() {
|
|
107
109
|
const { id } = this.props;
|
|
108
110
|
|
|
109
|
-
if (id
|
|
111
|
+
if (!isSearchInputId(id)) {
|
|
110
112
|
this.mouse = true;
|
|
111
113
|
this.props?.handleFocus?.({ mouse: true });
|
|
112
114
|
|
|
@@ -120,7 +122,7 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
120
122
|
onMouseLeave() {
|
|
121
123
|
const { id } = this.props;
|
|
122
124
|
|
|
123
|
-
if (id
|
|
125
|
+
if (!isSearchInputId(id)) {
|
|
124
126
|
this.mouse = false;
|
|
125
127
|
this.blur(null);
|
|
126
128
|
}
|
|
@@ -10,8 +10,12 @@ import {
|
|
|
10
10
|
forceFocusableFocus,
|
|
11
11
|
} from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
12
12
|
import { findNodeHandle, ViewStyle } from "react-native";
|
|
13
|
+
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
import {
|
|
16
|
+
emitDidFocused,
|
|
17
|
+
emitNativeRegistered,
|
|
18
|
+
} from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
|
|
15
19
|
|
|
16
20
|
type Props = {
|
|
17
21
|
id: string;
|
|
@@ -54,6 +58,7 @@ export class Focusable extends BaseFocusable<Props> {
|
|
|
54
58
|
this.nextFocusableReactTags = {};
|
|
55
59
|
this.preferredFocus = this.preferredFocus.bind(this);
|
|
56
60
|
this.measureView = this.measureView.bind(this);
|
|
61
|
+
this.onRegistered = this.onRegistered.bind(this);
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
/**
|
|
@@ -85,6 +90,9 @@ export class Focusable extends BaseFocusable<Props> {
|
|
|
85
90
|
});
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
const id: string = nativeEvent.itemID;
|
|
94
|
+
emitDidFocused(id);
|
|
95
|
+
|
|
88
96
|
onFocus(nativeEvent);
|
|
89
97
|
}
|
|
90
98
|
|
|
@@ -170,6 +178,13 @@ export class Focusable extends BaseFocusable<Props> {
|
|
|
170
178
|
});
|
|
171
179
|
}
|
|
172
180
|
|
|
181
|
+
onRegistered({ nativeEvent }) {
|
|
182
|
+
const groupId = nativeEvent?.groupId;
|
|
183
|
+
const id = nativeEvent?.itemId;
|
|
184
|
+
|
|
185
|
+
emitNativeRegistered({ id, groupId, isGroup: false });
|
|
186
|
+
}
|
|
187
|
+
|
|
173
188
|
render() {
|
|
174
189
|
const {
|
|
175
190
|
children,
|
|
@@ -204,6 +219,7 @@ export class Focusable extends BaseFocusable<Props> {
|
|
|
204
219
|
focusable={isFocusable}
|
|
205
220
|
{...this.nextFocusableReactTags}
|
|
206
221
|
{...otherProps}
|
|
222
|
+
onRegistered={this.onRegistered}
|
|
207
223
|
>
|
|
208
224
|
{typeof children === "function" ? children(focused) : children}
|
|
209
225
|
</FocusableItemNative>
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { compose } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
3
|
import { FocusableGroupNative } from "@applicaster/zapp-react-native-ui-components/Components/NativeFocusables";
|
|
3
4
|
import { BaseFocusable } from "@applicaster/zapp-react-native-ui-components/Components/BaseFocusable";
|
|
4
5
|
import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
|
|
6
|
+
import { LayoutContext } from "@applicaster/zapp-react-native-tvos-app/Context/LayoutContext";
|
|
7
|
+
import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useRoute";
|
|
8
|
+
import { isScreenPlayable } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
|
|
9
|
+
import { emitNativeRegistered } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
|
|
10
|
+
import { withAboveTabsScreenContextConsumer } from "@applicaster/zapp-react-native-ui-components/Contexts/AboveTabsScreenContext";
|
|
11
|
+
|
|
12
|
+
import { useIsFocusEnabled } from "./hooks";
|
|
5
13
|
|
|
6
14
|
const { log_verbose } = createLogger({
|
|
7
15
|
subsystem: "General",
|
|
@@ -33,9 +41,16 @@ type Props = {
|
|
|
33
41
|
screenData: { screenId: string; parentScreenId: string };
|
|
34
42
|
};
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
class FocusableGroupComponent extends BaseFocusable<Props> {
|
|
37
45
|
public readonly isGroup: boolean = true;
|
|
38
46
|
|
|
47
|
+
onRegistered = ({ nativeEvent }) => {
|
|
48
|
+
const groupId = nativeEvent?.groupId;
|
|
49
|
+
const id = nativeEvent?.itemId;
|
|
50
|
+
|
|
51
|
+
emitNativeRegistered({ id, groupId, isGroup: true });
|
|
52
|
+
};
|
|
53
|
+
|
|
39
54
|
render() {
|
|
40
55
|
const {
|
|
41
56
|
children,
|
|
@@ -68,9 +83,46 @@ export class FocusableGroup extends BaseFocusable<Props> {
|
|
|
68
83
|
onGroupBlur={onGroupBlur}
|
|
69
84
|
style={style}
|
|
70
85
|
{...otherProps}
|
|
86
|
+
onRegistered={this.onRegistered}
|
|
71
87
|
>
|
|
72
88
|
{children}
|
|
73
89
|
</FocusableGroupNative>
|
|
74
90
|
);
|
|
75
91
|
}
|
|
76
92
|
}
|
|
93
|
+
|
|
94
|
+
export const withFocusDisabledHOC = (Component) => {
|
|
95
|
+
return function WithFocusDisabledHOC(props) {
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
const { screenFocusBlocked } = React.useContext(LayoutContext.ReactContext);
|
|
98
|
+
|
|
99
|
+
const { pathname } = useRoute();
|
|
100
|
+
|
|
101
|
+
const isPlayerPresented = isScreenPlayable(pathname);
|
|
102
|
+
|
|
103
|
+
const blockScreenFocus = isPlayerPresented === false && screenFocusBlocked;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Component
|
|
107
|
+
{...props}
|
|
108
|
+
isFocusDisabled={blockScreenFocus || props.isFocusDisabled}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const withAboveTabsScreenHOC = (Component) => {
|
|
115
|
+
return function WithAboveTabsScreenHOC(props) {
|
|
116
|
+
const { aboveTabsScreen } = props;
|
|
117
|
+
|
|
118
|
+
const isFocusEnabled = useIsFocusEnabled(aboveTabsScreen);
|
|
119
|
+
|
|
120
|
+
return <Component {...props} isFocusDisabled={!isFocusEnabled} />;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const FocusableGroup = compose(
|
|
125
|
+
withAboveTabsScreenContextConsumer,
|
|
126
|
+
withAboveTabsScreenHOC,
|
|
127
|
+
withFocusDisabledHOC
|
|
128
|
+
)(FocusableGroupComponent);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react-native";
|
|
2
|
+
|
|
3
|
+
import { useIsFocusEnabled } from "../useIsFocusEnabled";
|
|
4
|
+
|
|
5
|
+
// ----------------- MOCKS -----------------
|
|
6
|
+
jest.mock(
|
|
7
|
+
"@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios",
|
|
8
|
+
() => ({
|
|
9
|
+
focusManager: {
|
|
10
|
+
isFocusOnMenu: jest.fn(),
|
|
11
|
+
isFocusOnTabsScreen: jest.fn(),
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
jest.mock(
|
|
17
|
+
"@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios",
|
|
18
|
+
() => {
|
|
19
|
+
const { Subject } = require("rxjs");
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
willFocused$: new Subject<void>(),
|
|
23
|
+
didFocused$: new Subject<void>(),
|
|
24
|
+
TabsScreenScreenSelectorContainerRegistry: {
|
|
25
|
+
observable$: new Subject<string>(),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
32
|
+
import {
|
|
33
|
+
willFocused$,
|
|
34
|
+
didFocused$,
|
|
35
|
+
TabsScreenScreenSelectorContainerRegistry,
|
|
36
|
+
} from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
|
|
37
|
+
|
|
38
|
+
// ----------------- TESTS -----------------
|
|
39
|
+
describe("useIsFocusEnabled", () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
jest.clearAllMocks();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("returns true by default", () => {
|
|
45
|
+
const { result } = renderHook(() => useIsFocusEnabled(true));
|
|
46
|
+
|
|
47
|
+
expect(result.current).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("disables focus when focus moves to menu", () => {
|
|
51
|
+
(focusManager.isFocusOnMenu as jest.Mock).mockReturnValue(true);
|
|
52
|
+
|
|
53
|
+
const { result } = renderHook(() => useIsFocusEnabled(true));
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
willFocused$.next();
|
|
57
|
+
didFocused$.next();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(result.current).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("re-enables focus when focus lands on tabs screen", () => {
|
|
64
|
+
(focusManager.isFocusOnMenu as jest.Mock).mockReturnValue(true);
|
|
65
|
+
(focusManager.isFocusOnTabsScreen as jest.Mock).mockReturnValue(true);
|
|
66
|
+
|
|
67
|
+
const { result } = renderHook(() => useIsFocusEnabled(true));
|
|
68
|
+
|
|
69
|
+
// Disable focus first
|
|
70
|
+
act(() => {
|
|
71
|
+
willFocused$.next();
|
|
72
|
+
didFocused$.next();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
expect(result.current).toBe(false);
|
|
76
|
+
|
|
77
|
+
// Enable focus again
|
|
78
|
+
act(() => {
|
|
79
|
+
didFocused$.next();
|
|
80
|
+
TabsScreenScreenSelectorContainerRegistry.observable$.next("tabs-id");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(result.current).toBe(true);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("does nothing when not above tabs screen", () => {
|
|
87
|
+
const { result } = renderHook(() => useIsFocusEnabled(false));
|
|
88
|
+
|
|
89
|
+
act(() => {
|
|
90
|
+
willFocused$.next();
|
|
91
|
+
didFocused$.next();
|
|
92
|
+
TabsScreenScreenSelectorContainerRegistry.observable$.next("id");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(result.current).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("cleans up subscriptions on unmount", () => {
|
|
99
|
+
(focusManager.isFocusOnMenu as jest.Mock).mockReturnValue(true);
|
|
100
|
+
|
|
101
|
+
const { unmount, result } = renderHook(() => useIsFocusEnabled(true));
|
|
102
|
+
|
|
103
|
+
unmount();
|
|
104
|
+
|
|
105
|
+
act(() => {
|
|
106
|
+
willFocused$.next();
|
|
107
|
+
didFocused$.next();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// State should not change after unmount
|
|
111
|
+
expect(result.current).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useIsFocusEnabled } from "./useIsFocusEnabled";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { switchMap, first, filter, repeat } from "rxjs/operators";
|
|
3
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
willFocused$,
|
|
7
|
+
didFocused$,
|
|
8
|
+
TabsScreenScreenSelectorContainerRegistry,
|
|
9
|
+
} from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
|
|
10
|
+
|
|
11
|
+
export const useIsFocusEnabled = (isAboveTabsScreen: boolean): boolean => {
|
|
12
|
+
const [isFocusEnabled, setIsFocusedEnabled] = React.useState(true);
|
|
13
|
+
|
|
14
|
+
const enableFocus = React.useCallback(() => {
|
|
15
|
+
setIsFocusedEnabled(true);
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
const disableFocus = React.useCallback(() => {
|
|
19
|
+
setIsFocusedEnabled(false);
|
|
20
|
+
}, []);
|
|
21
|
+
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
const subscription = didFocused$
|
|
24
|
+
.pipe(
|
|
25
|
+
filter(() => isAboveTabsScreen && !isFocusEnabled),
|
|
26
|
+
|
|
27
|
+
switchMap(() => TabsScreenScreenSelectorContainerRegistry.observable$),
|
|
28
|
+
filter((id) => id && focusManager.isFocusOnTabsScreen(id)),
|
|
29
|
+
|
|
30
|
+
// run only once, then re-subscribe on didFocused$ again
|
|
31
|
+
first(),
|
|
32
|
+
repeat()
|
|
33
|
+
)
|
|
34
|
+
.subscribe(() => {
|
|
35
|
+
enableFocus();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
subscription.unsubscribe();
|
|
40
|
+
};
|
|
41
|
+
}, [enableFocus, isFocusEnabled, isAboveTabsScreen]);
|
|
42
|
+
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
const subscription = willFocused$
|
|
45
|
+
.pipe(
|
|
46
|
+
filter(() => isAboveTabsScreen && isFocusEnabled),
|
|
47
|
+
|
|
48
|
+
// start waiting onFocus event
|
|
49
|
+
switchMap(() => didFocused$),
|
|
50
|
+
|
|
51
|
+
// focus is landed on top-menu
|
|
52
|
+
filter(() => focusManager.isFocusOnMenu()),
|
|
53
|
+
|
|
54
|
+
// run only once, then re-subscribe on willFocused$ again
|
|
55
|
+
first(),
|
|
56
|
+
repeat()
|
|
57
|
+
)
|
|
58
|
+
.subscribe(() => {
|
|
59
|
+
disableFocus();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
subscription.unsubscribe();
|
|
64
|
+
};
|
|
65
|
+
}, [disableFocus, isFocusEnabled, isAboveTabsScreen]);
|
|
66
|
+
|
|
67
|
+
return isFocusEnabled;
|
|
68
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { all, equals,
|
|
1
|
+
import { all, equals, isEmpty, path, pluck, prop, values } from "ramda";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo } from "react";
|
|
4
4
|
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
|
|
25
25
|
import { produce } from "immer";
|
|
26
26
|
import { useLoadPipesDataDispatch } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
27
|
+
|
|
27
28
|
// types reference
|
|
28
29
|
|
|
29
30
|
declare interface CurationEntry {
|
|
@@ -36,6 +37,7 @@ type Feeds = Record<string, ZappPipesData>;
|
|
|
36
37
|
type LayoutPresets = PresetsMapping["presets_mappings"];
|
|
37
38
|
|
|
38
39
|
const TABS_SCREEN_TYPE = "tabs_screen";
|
|
40
|
+
const QB_TABS_SCREEN_TYPE = "quick-brick-tabs";
|
|
39
41
|
const SMART_COMPONENT_TYPE = "quick-brick-smart-component";
|
|
40
42
|
const SOURCE_PATH = ["data", "source"];
|
|
41
43
|
const MAPPING_PATH = ["data", "mapping"];
|
|
@@ -54,7 +56,10 @@ export const getTransformedPreset = (
|
|
|
54
56
|
const presetComponent = layoutPresets?.[preset?.preset_name];
|
|
55
57
|
|
|
56
58
|
if (!presetComponent) {
|
|
57
|
-
logger.log_error(
|
|
59
|
+
logger.log_error(
|
|
60
|
+
`Preset "${preset?.preset_name}" missing or wrong data format`,
|
|
61
|
+
{ entry: preset }
|
|
62
|
+
);
|
|
58
63
|
|
|
59
64
|
return;
|
|
60
65
|
}
|
|
@@ -135,7 +140,11 @@ export const useCurationAPI = (
|
|
|
135
140
|
const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
|
|
136
141
|
const [screenContext] = ZappPipesScreenContext.useZappPipesContext();
|
|
137
142
|
|
|
138
|
-
const
|
|
143
|
+
const screenContextType = screenContext?.type;
|
|
144
|
+
|
|
145
|
+
const isNestedScreen =
|
|
146
|
+
screenContextType === TABS_SCREEN_TYPE ||
|
|
147
|
+
screenContextType === QB_TABS_SCREEN_TYPE;
|
|
139
148
|
|
|
140
149
|
const [entryContext] = ZappPipesEntryContext.useZappPipesContext(
|
|
141
150
|
pathname,
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
PlayerNativeSendCommand,
|
|
62
62
|
} from "@applicaster/zapp-react-native-utils/appUtils/playerManager/playerNativeCommand";
|
|
63
63
|
import { useAppData } from "@applicaster/zapp-react-native-redux";
|
|
64
|
+
import { useRestrictMobilePlayback } from "./useRestrictMobilePlayback";
|
|
64
65
|
|
|
65
66
|
type Props = {
|
|
66
67
|
Player: React.ComponentType<any>;
|
|
@@ -264,7 +265,21 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
264
265
|
|
|
265
266
|
showNavBar(true);
|
|
266
267
|
navigator.goBack();
|
|
267
|
-
}, [isModal,
|
|
268
|
+
}, [isModal, state.playerId, showNavBar, navigator]);
|
|
269
|
+
|
|
270
|
+
const pluginConfiguration = React.useMemo(() => {
|
|
271
|
+
return (
|
|
272
|
+
playerManager.getPluginConfiguration() ||
|
|
273
|
+
R.prop("__plugin_configuration", Player)
|
|
274
|
+
);
|
|
275
|
+
}, [playerManager.isRegistered()]);
|
|
276
|
+
|
|
277
|
+
const { isRestricted } = useRestrictMobilePlayback({
|
|
278
|
+
player,
|
|
279
|
+
entry: item,
|
|
280
|
+
pluginConfiguration,
|
|
281
|
+
close,
|
|
282
|
+
});
|
|
268
283
|
|
|
269
284
|
const playEntry = (entry) => navigator.replaceTop(entry, { mode });
|
|
270
285
|
|
|
@@ -456,13 +471,6 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
456
471
|
}
|
|
457
472
|
}, []);
|
|
458
473
|
|
|
459
|
-
const pluginConfiguration = React.useMemo(() => {
|
|
460
|
-
return (
|
|
461
|
-
playerManager.getPluginConfiguration() ||
|
|
462
|
-
R.prop("__plugin_configuration", Player)
|
|
463
|
-
);
|
|
464
|
-
}, [playerManager.isRegistered()]);
|
|
465
|
-
|
|
466
474
|
const disableMiniPlayer = React.useMemo(() => {
|
|
467
475
|
return pluginConfiguration?.disable_mini_player_when_inline;
|
|
468
476
|
}, [pluginConfiguration]);
|
|
@@ -662,36 +670,38 @@ const PlayerContainerComponent = (props: Props) => {
|
|
|
662
670
|
<PlayerFocusableWrapperView
|
|
663
671
|
nextFocusDown={context.bottomFocusableId}
|
|
664
672
|
>
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
setNextVideoPreloadThresholdPercentage
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
673
|
+
{isRestricted ? null : (
|
|
674
|
+
<Player
|
|
675
|
+
source={{
|
|
676
|
+
uri,
|
|
677
|
+
entry: item,
|
|
678
|
+
}}
|
|
679
|
+
focused={isInlineTV ? true : undefined}
|
|
680
|
+
autoplay={true}
|
|
681
|
+
controls={false}
|
|
682
|
+
disableCastAction={disableCastAction}
|
|
683
|
+
docked={navigator.isVideoModalDocked()}
|
|
684
|
+
entry={item}
|
|
685
|
+
fullscreen={mode === VideoModalMode.FULLSCREEN}
|
|
686
|
+
inline={inline}
|
|
687
|
+
isModal={isModal}
|
|
688
|
+
isTabletPortrait={isTabletPortrait}
|
|
689
|
+
muted={false}
|
|
690
|
+
playableItem={item}
|
|
691
|
+
playerEvent={playerEvent}
|
|
692
|
+
playerId={state.playerId}
|
|
693
|
+
pluginConfiguration={pluginConfiguration}
|
|
694
|
+
ref={playerRef}
|
|
695
|
+
toggleFullscreen={toggleFullscreen}
|
|
696
|
+
style={videoStyle}
|
|
697
|
+
playNextData={playNextData}
|
|
698
|
+
setNextVideoPreloadThresholdPercentage={
|
|
699
|
+
setNextVideoPreloadThresholdPercentage
|
|
700
|
+
}
|
|
701
|
+
>
|
|
702
|
+
{renderApplePlayer(applePlayerProps)}
|
|
703
|
+
</Player>
|
|
704
|
+
)}
|
|
695
705
|
</PlayerFocusableWrapperView>
|
|
696
706
|
|
|
697
707
|
{state.error ? <ErrorDisplay error={state.error} /> : null}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Player } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/player";
|
|
2
|
+
import NetInfo from "@react-native-community/netinfo";
|
|
3
|
+
|
|
4
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { showAlertDialog } from "@applicaster/zapp-react-native-utils/alertUtils";
|
|
6
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
7
|
+
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
8
|
+
import { log_info } from "./logger";
|
|
9
|
+
|
|
10
|
+
type RestrictMobilePlaybackProps = {
|
|
11
|
+
player?: Player;
|
|
12
|
+
entry?: ZappEntry;
|
|
13
|
+
pluginConfiguration?: Record<string, string>;
|
|
14
|
+
close: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const useRestrictMobilePlayback = ({
|
|
18
|
+
player,
|
|
19
|
+
entry,
|
|
20
|
+
pluginConfiguration,
|
|
21
|
+
close,
|
|
22
|
+
}: RestrictMobilePlaybackProps): { isRestricted: boolean } => {
|
|
23
|
+
const dialogVisibleRef = useRef<boolean>(false);
|
|
24
|
+
const theme = useTheme();
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
return () => {
|
|
28
|
+
if (isTV()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
dialogVisibleRef.current = false;
|
|
33
|
+
};
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const isConnectionRestricted = useMemo(() => {
|
|
37
|
+
if (isTV()) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return player && entry?.extensions?.connection_restricted;
|
|
42
|
+
}, [player, entry]);
|
|
43
|
+
|
|
44
|
+
const [isRestricted, setIsRestricted] = useState<boolean>(
|
|
45
|
+
isConnectionRestricted
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!isConnectionRestricted) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const stopPlayer = () => {
|
|
54
|
+
log_info(
|
|
55
|
+
"Stopping player due to mobile restriction, connection_restricted: true"
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
player?.close();
|
|
59
|
+
|
|
60
|
+
dialogVisibleRef.current = true;
|
|
61
|
+
|
|
62
|
+
showAlertDialog({
|
|
63
|
+
title:
|
|
64
|
+
pluginConfiguration?.mobile_connection_restricted_alert_title ||
|
|
65
|
+
"Restricted Connection Type",
|
|
66
|
+
message:
|
|
67
|
+
pluginConfiguration?.mobile_connection_restricted_alert_message ||
|
|
68
|
+
"This content can only be viewed over a Wi-Fi or LAN network.",
|
|
69
|
+
okButtonText: theme.ok_button || "OK",
|
|
70
|
+
completion: () => {
|
|
71
|
+
dialogVisibleRef.current = false;
|
|
72
|
+
|
|
73
|
+
close();
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return NetInfo.addEventListener((state) => {
|
|
79
|
+
if (state.type === "cellular") {
|
|
80
|
+
setIsRestricted(true);
|
|
81
|
+
|
|
82
|
+
if (dialogVisibleRef.current) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
stopPlayer();
|
|
87
|
+
} else {
|
|
88
|
+
setIsRestricted(false);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}, [
|
|
92
|
+
close,
|
|
93
|
+
entry?.extensions?.connection_restricted,
|
|
94
|
+
pluginConfiguration,
|
|
95
|
+
player,
|
|
96
|
+
theme.ok_button,
|
|
97
|
+
isConnectionRestricted,
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
return { isRestricted };
|
|
101
|
+
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Text } from "react-native";
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
import { mergeRight } from "@applicaster/zapp-react-native-utils/utils";
|
|
6
7
|
|
|
7
8
|
import { GeneralContentScreen } from "../../GeneralContentScreen";
|
|
8
9
|
import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
|
|
@@ -13,6 +14,8 @@ import {
|
|
|
13
14
|
} from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
|
|
14
15
|
import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
|
|
15
16
|
|
|
17
|
+
import { toStringOrEmpty } from "./utils";
|
|
18
|
+
|
|
16
19
|
type Props = {
|
|
17
20
|
screenId: string;
|
|
18
21
|
screenData: ZappRiver | ZappEntry;
|
|
@@ -24,6 +27,7 @@ type Props = {
|
|
|
24
27
|
isInsideContainer?: boolean;
|
|
25
28
|
extraAnchorPointYOffset: number;
|
|
26
29
|
river?: ZappRiver | ZappEntry;
|
|
30
|
+
groupId: string;
|
|
27
31
|
};
|
|
28
32
|
|
|
29
33
|
export const River = (props: Props) => {
|
|
@@ -35,6 +39,7 @@ export const River = (props: Props) => {
|
|
|
35
39
|
componentsMapExtraProps,
|
|
36
40
|
isInsideContainer,
|
|
37
41
|
extraAnchorPointYOffset,
|
|
42
|
+
groupId,
|
|
38
43
|
} = props;
|
|
39
44
|
|
|
40
45
|
const { title: screenTitle, summary: screenSummary } = useNavbarState();
|
|
@@ -51,28 +56,41 @@ export const River = (props: Props) => {
|
|
|
51
56
|
[screenId]
|
|
52
57
|
);
|
|
53
58
|
|
|
54
|
-
const
|
|
55
|
-
|
|
59
|
+
const screenResolverData = React.useMemo(() => {
|
|
60
|
+
const extraData = mergeRight(extraProps, screenResolverExtraProps);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
extraData,
|
|
64
|
+
screenData: mergeRight(river, { groupId: extraData?.groupId }),
|
|
65
|
+
componentsMapExtraProps: mergeRight(componentsMapExtraProps, { groupId }),
|
|
66
|
+
};
|
|
67
|
+
}, [
|
|
68
|
+
extraProps,
|
|
69
|
+
screenResolverExtraProps,
|
|
70
|
+
river,
|
|
71
|
+
componentsMapExtraProps,
|
|
72
|
+
groupId,
|
|
73
|
+
]);
|
|
56
74
|
|
|
57
75
|
React.useEffect(() => {
|
|
58
76
|
if (!isInsideContainer) {
|
|
59
|
-
setScreenTitle(
|
|
60
|
-
setScreenSummary(
|
|
77
|
+
setScreenTitle(toStringOrEmpty(screenData?.title));
|
|
78
|
+
setScreenSummary(toStringOrEmpty(screenData?.summary));
|
|
61
79
|
}
|
|
62
80
|
}, [screenData.id]);
|
|
63
81
|
|
|
64
82
|
React.useEffect(() => {
|
|
65
83
|
if (feedData && !isInsideContainer) {
|
|
66
84
|
if (feedData.title && feedData.title !== screenTitle) {
|
|
67
|
-
setScreenTitle(
|
|
85
|
+
setScreenTitle(toStringOrEmpty(feedData.title));
|
|
68
86
|
}
|
|
69
87
|
|
|
70
88
|
if (feedData.summary && feedData.summary !== screenSummary) {
|
|
71
|
-
setScreenSummary(
|
|
89
|
+
setScreenSummary(toStringOrEmpty(feedData.summary));
|
|
72
90
|
}
|
|
73
91
|
} else {
|
|
74
|
-
setScreenTitle(
|
|
75
|
-
setScreenSummary(
|
|
92
|
+
setScreenTitle(toStringOrEmpty(screenData?.title));
|
|
93
|
+
setScreenSummary(toStringOrEmpty(screenData?.summary));
|
|
76
94
|
}
|
|
77
95
|
}, [feedData, screenData, screenTitle, screenSummary]);
|
|
78
96
|
|
|
@@ -86,15 +104,13 @@ export const River = (props: Props) => {
|
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
if (river.type !== "general_content") {
|
|
89
|
-
const extraData = { ...R.mergeRight(extraProps, screenResolverExtraProps) };
|
|
90
|
-
|
|
91
107
|
return (
|
|
92
108
|
<ScreenResolver
|
|
93
109
|
screenType={river.type}
|
|
94
110
|
screenId={screenId}
|
|
95
|
-
screenData={
|
|
96
|
-
componentsMapExtraProps={componentsMapExtraProps}
|
|
97
|
-
{...extraData}
|
|
111
|
+
screenData={screenResolverData.screenData}
|
|
112
|
+
componentsMapExtraProps={screenResolverData.componentsMapExtraProps}
|
|
113
|
+
{...screenResolverData.extraData}
|
|
98
114
|
/>
|
|
99
115
|
);
|
|
100
116
|
}
|
|
@@ -106,6 +122,7 @@ export const River = (props: Props) => {
|
|
|
106
122
|
isScreenWrappedInContainer={isInsideContainer}
|
|
107
123
|
extraAnchorPointYOffset={extraAnchorPointYOffset}
|
|
108
124
|
componentsMapExtraProps={componentsMapExtraProps}
|
|
125
|
+
groupId={groupId}
|
|
109
126
|
/>
|
|
110
127
|
);
|
|
111
128
|
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { compose } from "
|
|
1
|
+
import { compose, identity } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
|
+
import { isTvOSPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
3
|
+
|
|
2
4
|
import { River as RiverComponent } from "./River";
|
|
3
|
-
import { withTvEventHandler } from "./withTVEventHandler";
|
|
4
5
|
import { withComponentsMapOffsetContext } from "../../../Contexts/ComponentsMapOffsetContext";
|
|
5
6
|
import { withRiverDataLoader } from "./withRiverDataLoader";
|
|
7
|
+
import { withFocusableGroupForContent } from "./withFocusableGroupForContent";
|
|
8
|
+
|
|
9
|
+
const isTVOS = isTvOSPlatform();
|
|
6
10
|
|
|
7
11
|
export const River = compose(
|
|
8
|
-
withTvEventHandler,
|
|
9
12
|
withComponentsMapOffsetContext,
|
|
10
|
-
withRiverDataLoader
|
|
13
|
+
withRiverDataLoader,
|
|
14
|
+
isTVOS ? withFocusableGroupForContent : identity
|
|
11
15
|
)(RiverComponent);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { toStringOrEmpty } from "..";
|
|
2
|
+
|
|
3
|
+
describe("toStringOrEmpty", () => {
|
|
4
|
+
test("returns empty string for undefined", () => {
|
|
5
|
+
expect(toStringOrEmpty(undefined)).toBe("");
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test("returns empty string for null", () => {
|
|
9
|
+
expect(toStringOrEmpty(null)).toBe("");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("converts number to string", () => {
|
|
13
|
+
expect(toStringOrEmpty(0)).toBe("0");
|
|
14
|
+
expect(toStringOrEmpty(123)).toBe("123");
|
|
15
|
+
expect(toStringOrEmpty(-42)).toBe("-42");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns string as is", () => {
|
|
19
|
+
expect(toStringOrEmpty("hello")).toBe("hello");
|
|
20
|
+
expect(toStringOrEmpty("")).toBe("");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("works with numeric strings", () => {
|
|
24
|
+
expect(toStringOrEmpty("123")).toBe("123");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("does not throw on falsy values like 0", () => {
|
|
28
|
+
expect(toStringOrEmpty(0)).toBe("0");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
|
|
4
|
+
import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
|
|
5
|
+
import { riverFocusManager } from "@applicaster/zapp-react-native-utils/appUtils/RiverFocusManager";
|
|
6
|
+
|
|
7
|
+
import { topMenuLayoutChange$ } from "@applicaster/zapp-react-native-tvos-app/Layout/topMenu";
|
|
8
|
+
|
|
9
|
+
const styles = StyleSheet.create({
|
|
10
|
+
flexOne: {
|
|
11
|
+
flex: 1,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const withFocusableGroupForContent = (Component) => {
|
|
16
|
+
return function WithFocusableGroupForContent(props) {
|
|
17
|
+
const { screenId, isInsideContainer } = props;
|
|
18
|
+
|
|
19
|
+
const [topMenuHeight, setTopMenuHeight] = React.useState(0);
|
|
20
|
+
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
const subscription = topMenuLayoutChange$.subscribe((layout) => {
|
|
23
|
+
setTopMenuHeight(layout.height);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
subscription.unsubscribe();
|
|
28
|
+
};
|
|
29
|
+
}, []);
|
|
30
|
+
|
|
31
|
+
const focusableId = React.useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
riverFocusManager.screenFocusableGroupId({
|
|
34
|
+
screenId,
|
|
35
|
+
isInsideContainer,
|
|
36
|
+
}),
|
|
37
|
+
[screenId, isInsideContainer]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (isInsideContainer) {
|
|
41
|
+
return <Component {...props} />;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<FocusableGroup
|
|
46
|
+
key={focusableId}
|
|
47
|
+
id={focusableId}
|
|
48
|
+
// The top menu is rendered in its own FocusableGroup, anchored at the top of the screen.
|
|
49
|
+
// When the "content" FocusableGroup starts at y = 0 as well, the two groups visually overlap.
|
|
50
|
+
// On TvOS platform this overlap can confuse the focus engine, because the focusable bounds of
|
|
51
|
+
// the top-menu group and the content group intersect, leading to erratic navigation between
|
|
52
|
+
// the menu and the content (e.g. unexpected jumps or focus getting "stuck").
|
|
53
|
+
//
|
|
54
|
+
// To avoid this, we shift the entire content FocusableGroup down by the dynamic top menu
|
|
55
|
+
// height (marginTop: topMenuHeight). This separates the focus regions of the two groups in
|
|
56
|
+
// focus space, so they no longer intersect.
|
|
57
|
+
//
|
|
58
|
+
// The inner <View> below then applies the inverse margin (marginTop: -topMenuHeight) so that
|
|
59
|
+
// the actual visual position of the content on screen does not change; only the focusable
|
|
60
|
+
// bounds of the outer group are offset.
|
|
61
|
+
style={[styles.flexOne, { marginTop: topMenuHeight }]}
|
|
62
|
+
// this group does not have parent
|
|
63
|
+
groupId={undefined}
|
|
64
|
+
>
|
|
65
|
+
<View style={[styles.flexOne, { marginTop: -1 * topMenuHeight }]}>
|
|
66
|
+
<Component {...props} groupId={focusableId} />
|
|
67
|
+
</View>
|
|
68
|
+
</FocusableGroup>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
3
|
+
|
|
4
|
+
export const AboveTabsScreenContext = React.createContext({
|
|
5
|
+
aboveTabsScreen: false,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const withAboveTabsScreenContext = (Component) =>
|
|
9
|
+
function AbovetabsScreenContextProviderWrapper(props) {
|
|
10
|
+
const aboveTabsScreen = toBooleanWithDefaultFalse(
|
|
11
|
+
props?.item?.above_tabs_screen
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const contextValue = useMemo(
|
|
15
|
+
() => ({
|
|
16
|
+
aboveTabsScreen,
|
|
17
|
+
}),
|
|
18
|
+
[aboveTabsScreen]
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<AboveTabsScreenContext.Provider value={contextValue}>
|
|
23
|
+
<Component {...props} />
|
|
24
|
+
</AboveTabsScreenContext.Provider>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const withAboveTabsScreenContextConsumer = (Component) =>
|
|
29
|
+
function AboveTabsScreenContextConsumerWrapper(props) {
|
|
30
|
+
const { aboveTabsScreen } = React.useContext(AboveTabsScreenContext);
|
|
31
|
+
|
|
32
|
+
return <Component {...props} aboveTabsScreen={aboveTabsScreen} />;
|
|
33
|
+
};
|
|
@@ -88,6 +88,7 @@ exports[`withConfigurationProvider correctly passes all the configuration keys c
|
|
|
88
88
|
tab_cell_padding_right={10}
|
|
89
89
|
tab_cell_padding_top={14}
|
|
90
90
|
tablet_theme={false}
|
|
91
|
+
tabs_screen_background_color="transparent"
|
|
91
92
|
target={false}
|
|
92
93
|
target_screen_switch={false}
|
|
93
94
|
text_label_active_font_color="rgba(239, 239, 239, 0.5)"
|
|
@@ -208,4 +208,5 @@ export const keysMap: Record<string, Function> = {
|
|
|
208
208
|
tab_bar_item_margin_right: castOrDefaultValue(Number, 0),
|
|
209
209
|
tab_bar_item_margin_bottom: castOrDefaultValue(Number, 0),
|
|
210
210
|
tab_bar_item_margin_left: castOrDefaultValue(Number, 0),
|
|
211
|
+
tabs_screen_background_color: castOrDefaultValue(R.identity, "transparent"),
|
|
211
212
|
};
|
|
@@ -3,7 +3,7 @@ import { renderWithProviders } from "@applicaster/zapp-react-native-utils/testUt
|
|
|
3
3
|
|
|
4
4
|
import * as zappPipesRedux from "@applicaster/zapp-react-native-redux/ZappPipes";
|
|
5
5
|
import configureStore from "redux-mock-store";
|
|
6
|
-
import thunk from "redux-thunk";
|
|
6
|
+
import { thunk } from "redux-thunk";
|
|
7
7
|
|
|
8
8
|
import { zappPipesDataConnector } from "../index";
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-ui-components",
|
|
3
|
-
"version": "15.0.0-alpha.
|
|
3
|
+
"version": "15.0.0-alpha.3564837831",
|
|
4
4
|
"description": "Applicaster Zapp React Native ui components for the Quick Brick App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "15.0.0-alpha.
|
|
32
|
-
"@applicaster/zapp-react-native-bridge": "15.0.0-alpha.
|
|
33
|
-
"@applicaster/zapp-react-native-redux": "15.0.0-alpha.
|
|
34
|
-
"@applicaster/zapp-react-native-utils": "15.0.0-alpha.
|
|
31
|
+
"@applicaster/applicaster-types": "15.0.0-alpha.3564837831",
|
|
32
|
+
"@applicaster/zapp-react-native-bridge": "15.0.0-alpha.3564837831",
|
|
33
|
+
"@applicaster/zapp-react-native-redux": "15.0.0-alpha.3564837831",
|
|
34
|
+
"@applicaster/zapp-react-native-utils": "15.0.0-alpha.3564837831",
|
|
35
35
|
"fast-json-stable-stringify": "^2.1.0",
|
|
36
36
|
"promise": "^8.3.0",
|
|
37
37
|
"url": "^0.11.0",
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/* eslint max-len: off */
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { TVEventHandlerComponent } from "@applicaster/zapp-react-native-tvos-ui-components/Components/TVEventHandlerComponent";
|
|
5
|
-
import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
6
|
-
|
|
7
|
-
export const withTvEventHandler = (Component) => {
|
|
8
|
-
return function WithTVEventHandler(props) {
|
|
9
|
-
const navigator = useNavigation();
|
|
10
|
-
|
|
11
|
-
const remoteHandler = (event) => {
|
|
12
|
-
const { eventType } = event;
|
|
13
|
-
|
|
14
|
-
const canGoBack = navigator.canGoBack();
|
|
15
|
-
|
|
16
|
-
if (eventType === "menu" && canGoBack) {
|
|
17
|
-
navigator.goBack();
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<TVEventHandlerComponent tvEventHandler={remoteHandler}>
|
|
23
|
-
<Component {...props} />
|
|
24
|
-
</TVEventHandlerComponent>
|
|
25
|
-
);
|
|
26
|
-
};
|
|
27
|
-
};
|