@instaread-co/react-native-instaread-sdk 1.3.4
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/CHANGELOG.md +86 -0
- package/InstareadReactNative.podspec +29 -0
- package/LICENSE +9 -0
- package/README.md +146 -0
- package/android/build.gradle +105 -0
- package/android/libs/instaread-sdk.aar +0 -0
- package/android/libs/instaread-sdk.version +1 -0
- package/android/src/main/AndroidManifest.xml +6 -0
- package/android/src/main/java/com/instaread/reactnative/InstareadNativePlayerViewManagerBase.kt +38 -0
- package/android/src/main/java/com/instaread/reactnative/InstareadPlayerHostView.kt +133 -0
- package/android/src/main/java/com/instaread/reactnative/InstareadReactNativeModule.kt +35 -0
- package/android/src/main/java/com/instaread/reactnative/InstareadReactNativePackage.kt +20 -0
- package/android/src/newarch/java/com/instaread/reactnative/InstareadNativePlayerViewManager.kt +33 -0
- package/android/src/oldarch/java/com/instaread/reactnative/InstareadNativePlayerViewManager.kt +29 -0
- package/ios/InstareadNativePlayerComponentView.h +17 -0
- package/ios/InstareadNativePlayerComponentView.mm +86 -0
- package/ios/InstareadNativePlayerLegacyView.h +22 -0
- package/ios/InstareadNativePlayerLegacyView.m +45 -0
- package/ios/InstareadNativePlayerViewManager.h +14 -0
- package/ios/InstareadNativePlayerViewManager.m +28 -0
- package/ios/InstareadPlayerHost.swift +114 -0
- package/ios/InstareadReactNativeModule.h +18 -0
- package/ios/InstareadReactNativeModule.mm +48 -0
- package/lib/module/InstareadNativePlayerNativeComponent.ts +22 -0
- package/lib/module/InstareadPlayer.js +16 -0
- package/lib/module/InstareadPlayerView.js +137 -0
- package/lib/module/NativeInstareadReactNative.js +7 -0
- package/lib/module/index.js +5 -0
- package/lib/module/package.json +1 -0
- package/lib/module/react-native-deep-imports.d.js +2 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/InstareadNativePlayerNativeComponent.d.ts +14 -0
- package/lib/typescript/src/InstareadPlayer.d.ts +11 -0
- package/lib/typescript/src/InstareadPlayerView.d.ts +49 -0
- package/lib/typescript/src/NativeInstareadReactNative.d.ts +7 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/package.json +124 -0
- package/src/InstareadNativePlayerNativeComponent.ts +22 -0
- package/src/InstareadPlayer.ts +14 -0
- package/src/InstareadPlayerView.tsx +219 -0
- package/src/NativeInstareadReactNative.ts +9 -0
- package/src/index.tsx +3 -0
- package/src/react-native-deep-imports.d.ts +21 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
2
|
+
declare const OWNED_STYLE_KEYS: readonly ["height", "minHeight", "maxHeight", "width", "minWidth", "maxWidth", "flex", "flexGrow", "flexShrink", "flexBasis", "aspectRatio"];
|
|
3
|
+
export type InstareadPlayerStyle = Omit<ViewStyle, (typeof OWNED_STYLE_KEYS)[number]>;
|
|
4
|
+
export type InstareadPlayerViewProps = {
|
|
5
|
+
/** The article's own page URL — the same one the web player is given. */
|
|
6
|
+
articleUrl: string;
|
|
7
|
+
/**
|
|
8
|
+
* Your Instaread publication slug. Leave it out to use the one passed to
|
|
9
|
+
* `InstareadPlayer.configure`.
|
|
10
|
+
*/
|
|
11
|
+
publication?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Render the dark palette. Your app decides — the SDK never reads the
|
|
14
|
+
* device's appearance itself, because an app with its own in-app theme
|
|
15
|
+
* toggle can be dark while the phone is light.
|
|
16
|
+
*/
|
|
17
|
+
dark?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Placement only — margins, alignment, padding. The player's own size is not
|
|
20
|
+
* yours to set: width, height, their min/max forms, flex and aspectRatio are
|
|
21
|
+
* ignored. It fills the container you put it in and is as tall as the player
|
|
22
|
+
* itself reports.
|
|
23
|
+
*/
|
|
24
|
+
style?: StyleProp<InstareadPlayerStyle>;
|
|
25
|
+
/**
|
|
26
|
+
* Called once this player has shown something for `articleUrl` — the
|
|
27
|
+
* player itself, or Instaread's "Audio Coming Soon" state. Fires at most
|
|
28
|
+
* once per `articleUrl`/`publication`; changing either resets it.
|
|
29
|
+
*
|
|
30
|
+
* There is no `onPlay`/`onPause`/`onEnded`: playback stays inside the
|
|
31
|
+
* player by design (see the README).
|
|
32
|
+
*/
|
|
33
|
+
onReady?: () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Called once if nothing has rendered {@link EMPTY_AFTER_MS} after
|
|
36
|
+
* mounting, or after `articleUrl`/`publication` changes — typically a
|
|
37
|
+
* wrong or not-yet-enabled `publication`, or no network. A heuristic, not
|
|
38
|
+
* a real error from the player (see the type's own note above); never
|
|
39
|
+
* fires after `onReady` has.
|
|
40
|
+
*/
|
|
41
|
+
onError?: () => void;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The Instaread player for an article. It sizes itself: zero height until there
|
|
45
|
+
* is something to show, then the loader, then the player.
|
|
46
|
+
*/
|
|
47
|
+
export declare function InstareadPlayerView({ articleUrl, publication, dark, style, onReady, onError, }: InstareadPlayerViewProps): import("react").JSX.Element | null;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=InstareadPlayerView.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type TurboModule } from 'react-native';
|
|
2
|
+
export interface Spec extends TurboModule {
|
|
3
|
+
configure(publication: string): void;
|
|
4
|
+
}
|
|
5
|
+
declare const _default: Spec | null | undefined;
|
|
6
|
+
export default _default;
|
|
7
|
+
//# sourceMappingURL=NativeInstareadReactNative.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@instaread-co/react-native-instaread-sdk",
|
|
3
|
+
"version": "1.3.4",
|
|
4
|
+
"description": "Instaread audio player for React Native, iOS and Android (wraps the native InstareadSDK)",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
10
|
+
"default": "./lib/module/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"lib",
|
|
17
|
+
"ios",
|
|
18
|
+
"android",
|
|
19
|
+
"*.podspec",
|
|
20
|
+
"!ios/build",
|
|
21
|
+
"!**/__tests__",
|
|
22
|
+
"!**/__fixtures__",
|
|
23
|
+
"!**/__mocks__",
|
|
24
|
+
"!**/.*",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"!lib/**/*.map",
|
|
27
|
+
"!android/build",
|
|
28
|
+
"!android/.gradle"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"clean": "del-cli lib dist",
|
|
32
|
+
"prepare": "bob build",
|
|
33
|
+
"typecheck": "tsc",
|
|
34
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\""
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"react-native",
|
|
38
|
+
"ios",
|
|
39
|
+
"android",
|
|
40
|
+
"instaread",
|
|
41
|
+
"audio"
|
|
42
|
+
],
|
|
43
|
+
"author": "Instaread",
|
|
44
|
+
"license": "UNLICENSED",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/instaread-co/instaread-react-native-sdk.git"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"registry": "https://registry.npmjs.org/",
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/compat": "^2.1.0",
|
|
55
|
+
"@eslint/eslintrc": "^3.3.6",
|
|
56
|
+
"@eslint/js": "^9.39.5",
|
|
57
|
+
"@react-native/babel-preset": "0.86.2",
|
|
58
|
+
"@react-native/eslint-config": "0.86.2",
|
|
59
|
+
"@types/react": "^19.2.0",
|
|
60
|
+
"del-cli": "^7.0.0",
|
|
61
|
+
"eslint": "^9.39.5",
|
|
62
|
+
"eslint-config-prettier": "^10.1.8",
|
|
63
|
+
"eslint-plugin-ft-flow": "^3.0.11",
|
|
64
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
65
|
+
"prettier": "^3.9.6",
|
|
66
|
+
"react": "19.2.3",
|
|
67
|
+
"react-native": "0.86.2",
|
|
68
|
+
"react-native-builder-bob": "^0.43.1",
|
|
69
|
+
"typescript": "^6.0.3"
|
|
70
|
+
},
|
|
71
|
+
"peerDependencies": {
|
|
72
|
+
"react": "*",
|
|
73
|
+
"react-native": ">=0.76.0"
|
|
74
|
+
},
|
|
75
|
+
"packageManager": "yarn@4.11.0",
|
|
76
|
+
"react-native-builder-bob": {
|
|
77
|
+
"source": "src",
|
|
78
|
+
"output": "lib",
|
|
79
|
+
"targets": [
|
|
80
|
+
[
|
|
81
|
+
"module",
|
|
82
|
+
{
|
|
83
|
+
"esm": true
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"typescript",
|
|
88
|
+
{
|
|
89
|
+
"project": "tsconfig.build.json"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
"codegenConfig": {
|
|
95
|
+
"name": "InstareadReactNativeSpec",
|
|
96
|
+
"type": "all",
|
|
97
|
+
"jsSrcsDir": "src",
|
|
98
|
+
"ios": {
|
|
99
|
+
"components": {
|
|
100
|
+
"InstareadNativePlayer": {
|
|
101
|
+
"className": "InstareadNativePlayerComponentView"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"android": {
|
|
106
|
+
"javaPackageName": "com.instaread.reactnative"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"prettier": {
|
|
110
|
+
"quoteProps": "consistent",
|
|
111
|
+
"singleQuote": true,
|
|
112
|
+
"tabWidth": 2,
|
|
113
|
+
"trailingComma": "es5",
|
|
114
|
+
"useTabs": false
|
|
115
|
+
},
|
|
116
|
+
"create-react-native-library": {
|
|
117
|
+
"type": "fabric-view",
|
|
118
|
+
"languages": "kotlin-objc",
|
|
119
|
+
"tools": [
|
|
120
|
+
"eslint"
|
|
121
|
+
],
|
|
122
|
+
"version": "0.63.1"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// The codegen types are imported from their original paths, not from the
|
|
2
|
+
// `react-native` root: the root only re-exports them (as `CodegenTypes`) in
|
|
3
|
+
// newer React Native, and the code generator on older versions doesn't
|
|
4
|
+
// recognise that namespace form.
|
|
5
|
+
import type { ViewProps } from 'react-native';
|
|
6
|
+
import type {
|
|
7
|
+
DirectEventHandler,
|
|
8
|
+
Double,
|
|
9
|
+
} from 'react-native/Libraries/Types/CodegenTypes';
|
|
10
|
+
// eslint-disable-next-line @react-native/no-deep-imports -- deliberate, see above
|
|
11
|
+
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
12
|
+
|
|
13
|
+
type HeightChangeEvent = Readonly<{ height: Double }>;
|
|
14
|
+
|
|
15
|
+
export interface NativeProps extends ViewProps {
|
|
16
|
+
publication?: string;
|
|
17
|
+
articleUrl?: string;
|
|
18
|
+
dark?: boolean;
|
|
19
|
+
onHeightChange?: DirectEventHandler<HeightChangeEvent>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default codegenNativeComponent<NativeProps>('InstareadNativePlayer');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import NativeInstareadReactNative from './NativeInstareadReactNative';
|
|
2
|
+
|
|
3
|
+
export const InstareadPlayer = {
|
|
4
|
+
/**
|
|
5
|
+
* Call once at app start, before anything renders.
|
|
6
|
+
*
|
|
7
|
+
* Sets the publication that `<InstareadPlayerView articleUrl=... />` uses and
|
|
8
|
+
* turns on the Instaread mini player, which floats above your app while
|
|
9
|
+
* something plays.
|
|
10
|
+
*/
|
|
11
|
+
configure(publication: string): void {
|
|
12
|
+
NativeInstareadReactNative?.configure(publication);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import {
|
|
11
|
+
Platform,
|
|
12
|
+
StyleSheet,
|
|
13
|
+
type NativeSyntheticEvent,
|
|
14
|
+
type StyleProp,
|
|
15
|
+
type ViewStyle,
|
|
16
|
+
} from 'react-native';
|
|
17
|
+
|
|
18
|
+
type NativePlayerComponent =
|
|
19
|
+
typeof import('./InstareadNativePlayerNativeComponent').default;
|
|
20
|
+
|
|
21
|
+
// In a release build the player stays silent whatever goes wrong; while
|
|
22
|
+
// developing, say why nothing shows.
|
|
23
|
+
function warnInDev(message: string) {
|
|
24
|
+
if (__DEV__) {
|
|
25
|
+
console.warn(`[InstareadSDK] ${message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// iOS and Android both ship the native component. Anywhere else (web, for
|
|
30
|
+
// instance) requiring it would register a component that doesn't exist, so the
|
|
31
|
+
// player renders nothing instead.
|
|
32
|
+
function loadNativePlayer(): NativePlayerComponent | null {
|
|
33
|
+
if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return require('./InstareadNativePlayerNativeComponent').default;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
warnInDev(`The native player could not be loaded: ${String(error)}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const NativePlayer = loadNativePlayer();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The player never puts an error in your app: if the native side is missing
|
|
48
|
+
* (say the app wasn't rebuilt after installing the SDK), it renders nothing.
|
|
49
|
+
*/
|
|
50
|
+
class FailSilent extends Component<
|
|
51
|
+
{ children: ReactNode },
|
|
52
|
+
{ failed: boolean }
|
|
53
|
+
> {
|
|
54
|
+
override state = { failed: false };
|
|
55
|
+
|
|
56
|
+
static getDerivedStateFromError() {
|
|
57
|
+
return { failed: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override componentDidCatch(error: Error) {
|
|
61
|
+
warnInDev(
|
|
62
|
+
`The player could not be shown (${error.message}). Rebuild the app after installing the SDK` +
|
|
63
|
+
(Platform.OS === 'ios' ? ', running `pod install` first.' : '.')
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override render() {
|
|
68
|
+
return this.state.failed ? null : this.props.children;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The player's size is decided on Instaread's side — the height is reported by
|
|
73
|
+
// the player itself and the width always fills whatever container it is placed
|
|
74
|
+
// in, matching the native SDKs (Compose appends fillMaxWidth(), the Android
|
|
75
|
+
// View takes its parent's width, SwiftUI takes the available width). So none of
|
|
76
|
+
// these can be set from the app; placement — margins, alignment, padding — is
|
|
77
|
+
// still yours.
|
|
78
|
+
const OWNED_STYLE_KEYS = [
|
|
79
|
+
'height',
|
|
80
|
+
'minHeight',
|
|
81
|
+
'maxHeight',
|
|
82
|
+
'width',
|
|
83
|
+
'minWidth',
|
|
84
|
+
'maxWidth',
|
|
85
|
+
'flex',
|
|
86
|
+
'flexGrow',
|
|
87
|
+
'flexShrink',
|
|
88
|
+
'flexBasis',
|
|
89
|
+
'aspectRatio',
|
|
90
|
+
] as const;
|
|
91
|
+
|
|
92
|
+
export type InstareadPlayerStyle = Omit<
|
|
93
|
+
ViewStyle,
|
|
94
|
+
(typeof OWNED_STYLE_KEYS)[number]
|
|
95
|
+
>;
|
|
96
|
+
|
|
97
|
+
function layoutOnly(style: StyleProp<InstareadPlayerStyle>): ViewStyle {
|
|
98
|
+
const flat: Record<string, unknown> = { ...StyleSheet.flatten(style) };
|
|
99
|
+
for (const key of OWNED_STYLE_KEYS) {
|
|
100
|
+
delete flat[key];
|
|
101
|
+
}
|
|
102
|
+
return flat as ViewStyle;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// How long to wait, after mounting or after articleUrl/publication changes,
|
|
106
|
+
// before treating a still-empty player as onError. Not a signal from the
|
|
107
|
+
// player itself — the native SDK never reports a distinct error, so this is
|
|
108
|
+
// a React Native-side guess, and a legitimately slow load looks the same as
|
|
109
|
+
// a real failure until it either resolves or this fires. 15s, not something
|
|
110
|
+
// shorter: the server can be slow to respond while autoscaling.
|
|
111
|
+
const EMPTY_AFTER_MS = 15000;
|
|
112
|
+
|
|
113
|
+
export type InstareadPlayerViewProps = {
|
|
114
|
+
/** The article's own page URL — the same one the web player is given. */
|
|
115
|
+
articleUrl: string;
|
|
116
|
+
/**
|
|
117
|
+
* Your Instaread publication slug. Leave it out to use the one passed to
|
|
118
|
+
* `InstareadPlayer.configure`.
|
|
119
|
+
*/
|
|
120
|
+
publication?: string;
|
|
121
|
+
/**
|
|
122
|
+
* Render the dark palette. Your app decides — the SDK never reads the
|
|
123
|
+
* device's appearance itself, because an app with its own in-app theme
|
|
124
|
+
* toggle can be dark while the phone is light.
|
|
125
|
+
*/
|
|
126
|
+
dark?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Placement only — margins, alignment, padding. The player's own size is not
|
|
129
|
+
* yours to set: width, height, their min/max forms, flex and aspectRatio are
|
|
130
|
+
* ignored. It fills the container you put it in and is as tall as the player
|
|
131
|
+
* itself reports.
|
|
132
|
+
*/
|
|
133
|
+
style?: StyleProp<InstareadPlayerStyle>;
|
|
134
|
+
/**
|
|
135
|
+
* Called once this player has shown something for `articleUrl` — the
|
|
136
|
+
* player itself, or Instaread's "Audio Coming Soon" state. Fires at most
|
|
137
|
+
* once per `articleUrl`/`publication`; changing either resets it.
|
|
138
|
+
*
|
|
139
|
+
* There is no `onPlay`/`onPause`/`onEnded`: playback stays inside the
|
|
140
|
+
* player by design (see the README).
|
|
141
|
+
*/
|
|
142
|
+
onReady?: () => void;
|
|
143
|
+
/**
|
|
144
|
+
* Called once if nothing has rendered {@link EMPTY_AFTER_MS} after
|
|
145
|
+
* mounting, or after `articleUrl`/`publication` changes — typically a
|
|
146
|
+
* wrong or not-yet-enabled `publication`, or no network. A heuristic, not
|
|
147
|
+
* a real error from the player (see the type's own note above); never
|
|
148
|
+
* fires after `onReady` has.
|
|
149
|
+
*/
|
|
150
|
+
onError?: () => void;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The Instaread player for an article. It sizes itself: zero height until there
|
|
155
|
+
* is something to show, then the loader, then the player.
|
|
156
|
+
*/
|
|
157
|
+
export function InstareadPlayerView({
|
|
158
|
+
articleUrl,
|
|
159
|
+
publication,
|
|
160
|
+
dark = false,
|
|
161
|
+
style,
|
|
162
|
+
onReady,
|
|
163
|
+
onError,
|
|
164
|
+
}: InstareadPlayerViewProps) {
|
|
165
|
+
const [height, setHeight] = useState(0);
|
|
166
|
+
const isReady = useRef(false);
|
|
167
|
+
const emptyTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
168
|
+
|
|
169
|
+
// A new article/publication is a fresh attempt: forget the last result and
|
|
170
|
+
// give this one its own onError timeout.
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
isReady.current = false;
|
|
173
|
+
setHeight(0);
|
|
174
|
+
emptyTimer.current = setTimeout(() => {
|
|
175
|
+
if (!isReady.current) {
|
|
176
|
+
onError?.();
|
|
177
|
+
}
|
|
178
|
+
}, EMPTY_AFTER_MS);
|
|
179
|
+
return () => {
|
|
180
|
+
if (emptyTimer.current) {
|
|
181
|
+
clearTimeout(emptyTimer.current);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
// Only articleUrl/publication/dark should restart the attempt — not
|
|
185
|
+
// identity changes of onReady/onError themselves.
|
|
186
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
187
|
+
}, [articleUrl, publication, dark]);
|
|
188
|
+
|
|
189
|
+
const onHeightChange = useCallback(
|
|
190
|
+
(event: NativeSyntheticEvent<Readonly<{ height: number }>>) => {
|
|
191
|
+
const next = event.nativeEvent.height;
|
|
192
|
+
setHeight(next);
|
|
193
|
+
if (next > 0 && !isReady.current) {
|
|
194
|
+
isReady.current = true;
|
|
195
|
+
if (emptyTimer.current) {
|
|
196
|
+
clearTimeout(emptyTimer.current);
|
|
197
|
+
}
|
|
198
|
+
onReady?.();
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
[onReady]
|
|
202
|
+
);
|
|
203
|
+
const layout = useMemo(() => layoutOnly(style), [style]);
|
|
204
|
+
|
|
205
|
+
if (!NativePlayer) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
return (
|
|
209
|
+
<FailSilent>
|
|
210
|
+
<NativePlayer
|
|
211
|
+
articleUrl={articleUrl}
|
|
212
|
+
publication={publication ?? ''}
|
|
213
|
+
dark={dark}
|
|
214
|
+
onHeightChange={onHeightChange}
|
|
215
|
+
style={[layout, { height }]}
|
|
216
|
+
/>
|
|
217
|
+
</FailSilent>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export interface Spec extends TurboModule {
|
|
4
|
+
configure(publication: string): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// `get`, not `getEnforcing`: on a platform without the native module (Android)
|
|
8
|
+
// `InstareadPlayer.configure` is a no-op instead of a crash in the app.
|
|
9
|
+
export default TurboModuleRegistry.get<Spec>('InstareadReactNative');
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Types for the two original-path imports used by the codegen spec
|
|
2
|
+
// (InstareadNativePlayerNativeComponent.ts). The React Native we type-check
|
|
3
|
+
// against no longer ships declarations at these paths. The code generator reads
|
|
4
|
+
// the spec's source, not these; they only keep `tsc` happy.
|
|
5
|
+
declare module 'react-native/Libraries/Types/CodegenTypes' {
|
|
6
|
+
import type { NativeSyntheticEvent } from 'react-native';
|
|
7
|
+
|
|
8
|
+
export type Double = number;
|
|
9
|
+
export type DirectEventHandler<T> = (
|
|
10
|
+
event: NativeSyntheticEvent<T>
|
|
11
|
+
) => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare module 'react-native/Libraries/Utilities/codegenNativeComponent' {
|
|
15
|
+
import type { HostComponent } from 'react-native';
|
|
16
|
+
|
|
17
|
+
export default function codegenNativeComponent<Props extends object>(
|
|
18
|
+
componentName: string,
|
|
19
|
+
options?: object
|
|
20
|
+
): HostComponent<Props>;
|
|
21
|
+
}
|