@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.
Files changed (43) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/InstareadReactNative.podspec +29 -0
  3. package/LICENSE +9 -0
  4. package/README.md +146 -0
  5. package/android/build.gradle +105 -0
  6. package/android/libs/instaread-sdk.aar +0 -0
  7. package/android/libs/instaread-sdk.version +1 -0
  8. package/android/src/main/AndroidManifest.xml +6 -0
  9. package/android/src/main/java/com/instaread/reactnative/InstareadNativePlayerViewManagerBase.kt +38 -0
  10. package/android/src/main/java/com/instaread/reactnative/InstareadPlayerHostView.kt +133 -0
  11. package/android/src/main/java/com/instaread/reactnative/InstareadReactNativeModule.kt +35 -0
  12. package/android/src/main/java/com/instaread/reactnative/InstareadReactNativePackage.kt +20 -0
  13. package/android/src/newarch/java/com/instaread/reactnative/InstareadNativePlayerViewManager.kt +33 -0
  14. package/android/src/oldarch/java/com/instaread/reactnative/InstareadNativePlayerViewManager.kt +29 -0
  15. package/ios/InstareadNativePlayerComponentView.h +17 -0
  16. package/ios/InstareadNativePlayerComponentView.mm +86 -0
  17. package/ios/InstareadNativePlayerLegacyView.h +22 -0
  18. package/ios/InstareadNativePlayerLegacyView.m +45 -0
  19. package/ios/InstareadNativePlayerViewManager.h +14 -0
  20. package/ios/InstareadNativePlayerViewManager.m +28 -0
  21. package/ios/InstareadPlayerHost.swift +114 -0
  22. package/ios/InstareadReactNativeModule.h +18 -0
  23. package/ios/InstareadReactNativeModule.mm +48 -0
  24. package/lib/module/InstareadNativePlayerNativeComponent.ts +22 -0
  25. package/lib/module/InstareadPlayer.js +16 -0
  26. package/lib/module/InstareadPlayerView.js +137 -0
  27. package/lib/module/NativeInstareadReactNative.js +7 -0
  28. package/lib/module/index.js +5 -0
  29. package/lib/module/package.json +1 -0
  30. package/lib/module/react-native-deep-imports.d.js +2 -0
  31. package/lib/typescript/package.json +1 -0
  32. package/lib/typescript/src/InstareadNativePlayerNativeComponent.d.ts +14 -0
  33. package/lib/typescript/src/InstareadPlayer.d.ts +11 -0
  34. package/lib/typescript/src/InstareadPlayerView.d.ts +49 -0
  35. package/lib/typescript/src/NativeInstareadReactNative.d.ts +7 -0
  36. package/lib/typescript/src/index.d.ts +4 -0
  37. package/package.json +124 -0
  38. package/src/InstareadNativePlayerNativeComponent.ts +22 -0
  39. package/src/InstareadPlayer.ts +14 -0
  40. package/src/InstareadPlayerView.tsx +219 -0
  41. package/src/NativeInstareadReactNative.ts +9 -0
  42. package/src/index.tsx +3 -0
  43. package/src/react-native-deep-imports.d.ts +21 -0
@@ -0,0 +1,29 @@
1
+ package com.instaread.reactnative
2
+
3
+ import com.facebook.react.module.annotations.ReactModule
4
+ import com.facebook.react.uimanager.annotations.ReactProp
5
+
6
+ /**
7
+ * Old architecture (Paper). No codegen delegate exists there, so the same two
8
+ * props are declared with `@ReactProp` instead; everything else is in the
9
+ * shared base. Kept so apps that have not moved to the new architecture — the
10
+ * reason this package supports both — still get the player.
11
+ */
12
+ @ReactModule(name = InstareadNativePlayerViewManagerBase.NAME)
13
+ class InstareadNativePlayerViewManager : InstareadNativePlayerViewManagerBase() {
14
+
15
+ @ReactProp(name = "publication")
16
+ fun setPublication(view: InstareadPlayerHostView, value: String?) {
17
+ view.setPublicationProp(value)
18
+ }
19
+
20
+ @ReactProp(name = "articleUrl")
21
+ fun setArticleUrl(view: InstareadPlayerHostView, value: String?) {
22
+ view.setArticleUrlProp(value)
23
+ }
24
+
25
+ @ReactProp(name = "dark")
26
+ fun setDark(view: InstareadPlayerHostView, value: Boolean) {
27
+ view.setDarkProp(value)
28
+ }
29
+ }
@@ -0,0 +1,17 @@
1
+ // New Architecture (Fabric) only. The old architecture uses
2
+ // InstareadNativePlayerViewManager instead.
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+
5
+ #import <React/RCTViewComponentView.h>
6
+ #import <UIKit/UIKit.h>
7
+
8
+ NS_ASSUME_NONNULL_BEGIN
9
+
10
+ /// Fabric component that hosts the native InstareadSDK player. Internal: apps
11
+ /// use the `InstareadPlayerView` React component, which sizes it.
12
+ @interface InstareadNativePlayerComponentView : RCTViewComponentView
13
+ @end
14
+
15
+ NS_ASSUME_NONNULL_END
16
+
17
+ #endif
@@ -0,0 +1,86 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ // New Architecture (Fabric) only. The old architecture uses
4
+ // InstareadNativePlayerViewManager instead.
5
+ #ifdef RCT_NEW_ARCH_ENABLED
6
+
7
+ #import "InstareadNativePlayerComponentView.h"
8
+
9
+ #import <React/RCTConversions.h>
10
+
11
+ #import <react/renderer/components/InstareadReactNativeSpec/ComponentDescriptors.h>
12
+ #import <react/renderer/components/InstareadReactNativeSpec/EventEmitters.h>
13
+ #import <react/renderer/components/InstareadReactNativeSpec/Props.h>
14
+ #import <react/renderer/components/InstareadReactNativeSpec/RCTComponentViewHelpers.h>
15
+
16
+ #import "RCTFabricComponentsPlugins.h"
17
+
18
+ #if __has_include(<InstareadReactNative/InstareadReactNative-Swift.h>)
19
+ #import <InstareadReactNative/InstareadReactNative-Swift.h>
20
+ #else
21
+ #import "InstareadReactNative-Swift.h"
22
+ #endif
23
+
24
+ using namespace facebook::react;
25
+
26
+ @implementation InstareadNativePlayerComponentView {
27
+ InstareadPlayerHost *_host;
28
+ }
29
+
30
+ + (ComponentDescriptorProvider)componentDescriptorProvider
31
+ {
32
+ return concreteComponentDescriptorProvider<InstareadNativePlayerComponentDescriptor>();
33
+ }
34
+
35
+ - (instancetype)initWithFrame:(CGRect)frame
36
+ {
37
+ if (self = [super initWithFrame:frame]) {
38
+ static const auto defaultProps = std::make_shared<const InstareadNativePlayerProps>();
39
+ _props = defaultProps;
40
+
41
+ _host = [[InstareadPlayerHost alloc] init];
42
+ __weak __typeof(self) weakSelf = self;
43
+ _host.onHeightChange = ^(CGFloat height) {
44
+ [weakSelf emitHeight:height];
45
+ };
46
+ self.contentView = _host;
47
+ }
48
+ return self;
49
+ }
50
+
51
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
52
+ {
53
+ const auto &oldViewProps = *std::static_pointer_cast<InstareadNativePlayerProps const>(_props);
54
+ const auto &newViewProps = *std::static_pointer_cast<InstareadNativePlayerProps const>(props);
55
+
56
+ if (oldViewProps.publication != newViewProps.publication ||
57
+ oldViewProps.articleUrl != newViewProps.articleUrl ||
58
+ oldViewProps.dark != newViewProps.dark) {
59
+ [_host updateWithPublication:RCTNSStringFromString(newViewProps.publication)
60
+ articleUrl:RCTNSStringFromString(newViewProps.articleUrl)
61
+ dark:newViewProps.dark];
62
+ }
63
+
64
+ [super updateProps:props oldProps:oldProps];
65
+ }
66
+
67
+ - (void)prepareForRecycle
68
+ {
69
+ [super prepareForRecycle];
70
+ [_host reset];
71
+ static const auto defaultProps = std::make_shared<const InstareadNativePlayerProps>();
72
+ _props = defaultProps;
73
+ }
74
+
75
+ - (void)emitHeight:(CGFloat)height
76
+ {
77
+ if (!_eventEmitter) {
78
+ return;
79
+ }
80
+ std::static_pointer_cast<const InstareadNativePlayerEventEmitter>(_eventEmitter)
81
+ ->onHeightChange({.height = static_cast<double>(height)});
82
+ }
83
+
84
+ @end
85
+
86
+ #endif
@@ -0,0 +1,22 @@
1
+ // Old architecture (Paper) only. The New Architecture uses
2
+ // InstareadNativePlayerComponentView instead.
3
+ #ifndef RCT_NEW_ARCH_ENABLED
4
+
5
+ #import <React/RCTComponent.h>
6
+ #import <UIKit/UIKit.h>
7
+
8
+ NS_ASSUME_NONNULL_BEGIN
9
+
10
+ /// The native InstareadSDK player as a view a React Native view manager can create.
11
+ @interface InstareadNativePlayerLegacyView : UIView
12
+
13
+ @property (nonatomic, copy, nullable) NSString *publication;
14
+ @property (nonatomic, copy, nullable) NSString *articleUrl;
15
+ @property (nonatomic, assign) BOOL dark;
16
+ @property (nonatomic, copy, nullable) RCTDirectEventBlock onHeightChange;
17
+
18
+ @end
19
+
20
+ NS_ASSUME_NONNULL_END
21
+
22
+ #endif
@@ -0,0 +1,45 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ // Old architecture (Paper) only. The New Architecture uses
4
+ // InstareadNativePlayerComponentView instead.
5
+ #ifndef RCT_NEW_ARCH_ENABLED
6
+
7
+ #import "InstareadNativePlayerLegacyView.h"
8
+
9
+ #if __has_include(<InstareadReactNative/InstareadReactNative-Swift.h>)
10
+ #import <InstareadReactNative/InstareadReactNative-Swift.h>
11
+ #else
12
+ #import "InstareadReactNative-Swift.h"
13
+ #endif
14
+
15
+ @implementation InstareadNativePlayerLegacyView {
16
+ InstareadPlayerHost *_host;
17
+ }
18
+
19
+ - (instancetype)initWithFrame:(CGRect)frame
20
+ {
21
+ if (self = [super initWithFrame:frame]) {
22
+ _host = [[InstareadPlayerHost alloc] initWithFrame:self.bounds];
23
+ _host.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
24
+ __weak __typeof(self) weakSelf = self;
25
+ _host.onHeightChange = ^(CGFloat height) {
26
+ __strong __typeof(weakSelf) strongSelf = weakSelf;
27
+ if (strongSelf.onHeightChange) {
28
+ strongSelf.onHeightChange(@{@"height": @(height)});
29
+ }
30
+ };
31
+ [self addSubview:_host];
32
+ }
33
+ return self;
34
+ }
35
+
36
+ // React Native calls this once after each batch of prop changes, so changing
37
+ // both props at once builds the player once.
38
+ - (void)didSetProps:(NSArray<NSString *> *)changedProps
39
+ {
40
+ [_host updateWithPublication:self.publication ?: @"" articleUrl:self.articleUrl ?: @"" dark:self.dark];
41
+ }
42
+
43
+ @end
44
+
45
+ #endif
@@ -0,0 +1,14 @@
1
+ // Old architecture (Paper) only. The New Architecture registers
2
+ // InstareadNativePlayerComponentView through codegen instead.
3
+ #ifndef RCT_NEW_ARCH_ENABLED
4
+
5
+ #import <React/RCTViewManager.h>
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ @interface InstareadNativePlayerViewManager : RCTViewManager
10
+ @end
11
+
12
+ NS_ASSUME_NONNULL_END
13
+
14
+ #endif
@@ -0,0 +1,28 @@
1
+ #import <Foundation/Foundation.h>
2
+
3
+ // Old architecture (Paper) only. The New Architecture registers
4
+ // InstareadNativePlayerComponentView through codegen instead.
5
+ #ifndef RCT_NEW_ARCH_ENABLED
6
+
7
+ #import "InstareadNativePlayerViewManager.h"
8
+
9
+ #import "InstareadNativePlayerLegacyView.h"
10
+
11
+ @implementation InstareadNativePlayerViewManager
12
+
13
+ // Same name as the codegen component, so the JS side is identical on both architectures.
14
+ RCT_EXPORT_MODULE(InstareadNativePlayer)
15
+
16
+ - (UIView *)view
17
+ {
18
+ return [[InstareadNativePlayerLegacyView alloc] init];
19
+ }
20
+
21
+ RCT_EXPORT_VIEW_PROPERTY(publication, NSString)
22
+ RCT_EXPORT_VIEW_PROPERTY(articleUrl, NSString)
23
+ RCT_EXPORT_VIEW_PROPERTY(dark, BOOL)
24
+ RCT_EXPORT_VIEW_PROPERTY(onHeightChange, RCTDirectEventBlock)
25
+
26
+ @end
27
+
28
+ #endif
@@ -0,0 +1,114 @@
1
+ //
2
+ // InstareadPlayerHost.swift
3
+ // InstareadReactNative
4
+ //
5
+ // The only place the React Native wrapper touches the native SDK. It uses the
6
+ // SDK's public API exactly as a UIKit app would — `InstareadPlayerView` and
7
+ // `InstareadPlayer.configure` — and adds nothing to it. The SDK's classes are
8
+ // Swift-only, so the Objective-C++ Fabric component talks to these two @objc
9
+ // types instead.
10
+ //
11
+
12
+ import InstareadSDK
13
+ import UIKit
14
+
15
+ /// Hosts one `InstareadPlayerView` and reports how tall it wants to be.
16
+ ///
17
+ /// `InstareadPlayerView` sizes itself through `intrinsicContentSize` only, and a
18
+ /// Fabric view is laid out by frame from JS, so this reads the intrinsic height
19
+ /// after each layout pass and hands it to `onHeightChange`; JS turns it into the
20
+ /// component's height.
21
+ @objc(InstareadPlayerHost)
22
+ public final class InstareadPlayerHost: UIView {
23
+ /// Set by the view that owns this host. If a height was already measured
24
+ /// before the handler arrived, it is delivered right away instead of lost.
25
+ @objc public var onHeightChange: ((CGFloat) -> Void)? {
26
+ didSet {
27
+ if reportedHeight > 0 {
28
+ onHeightChange?(reportedHeight)
29
+ }
30
+ }
31
+ }
32
+
33
+ private var player: InstareadPlayerView?
34
+ private var publication = ""
35
+ private var articleUrl = ""
36
+ private var dark = false
37
+ private var reportedHeight: CGFloat = 0
38
+
39
+ @objc(updateWithPublication:articleUrl:dark:)
40
+ public func update(publication: String, articleUrl: String, dark: Bool) {
41
+ guard publication != self.publication || articleUrl != self.articleUrl || dark != self.dark else { return }
42
+ self.publication = publication
43
+ self.articleUrl = articleUrl
44
+ self.dark = dark
45
+ rebuildPlayer()
46
+ }
47
+
48
+ /// Drops the player, e.g. when Fabric recycles this view for another article.
49
+ @objc public func reset() {
50
+ update(publication: "", articleUrl: "", dark: false)
51
+ }
52
+
53
+ private func rebuildPlayer() {
54
+ player?.removeFromSuperview()
55
+ player = nil
56
+
57
+ // Nothing to show without an article; an empty publication falls back to
58
+ // the one passed to InstareadPlayer.configure.
59
+ if !articleUrl.isEmpty {
60
+ let player = publication.isEmpty
61
+ ? InstareadPlayerView(articleUrl: articleUrl, dark: dark)
62
+ : InstareadPlayerView(publication: publication, articleUrl: articleUrl, dark: dark)
63
+ player.translatesAutoresizingMaskIntoConstraints = false
64
+ addSubview(player)
65
+ // No bottom constraint: the player's height is its own intrinsic one,
66
+ // whatever frame JS has given this view so far.
67
+ NSLayoutConstraint.activate([
68
+ player.topAnchor.constraint(equalTo: topAnchor),
69
+ player.leadingAnchor.constraint(equalTo: leadingAnchor),
70
+ player.trailingAnchor.constraint(equalTo: trailingAnchor),
71
+ ])
72
+ self.player = player
73
+ }
74
+ setNeedsLayout()
75
+ reportHeightIfNeeded()
76
+ }
77
+
78
+ public override func layoutSubviews() {
79
+ super.layoutSubviews()
80
+ reportHeightIfNeeded()
81
+ }
82
+
83
+ private func reportHeightIfNeeded() {
84
+ let height = ceil(player?.intrinsicContentSize.height ?? 0)
85
+ guard abs(height - reportedHeight) > 0.5 else { return }
86
+ reportedHeight = height
87
+ onHeightChange?(height)
88
+ }
89
+ }
90
+
91
+ /// `InstareadPlayer.configure(publication:)` for the TurboModule.
92
+ @objc(InstareadBridge)
93
+ public final class InstareadBridge: NSObject {
94
+ @objc(configureWithPublication:)
95
+ @MainActor
96
+ public static func configure(publication: String) {
97
+ #if DEBUG
98
+ warnIfBackgroundAudioIsMissing()
99
+ #endif
100
+ InstareadPlayer.configure(publication: publication)
101
+ }
102
+
103
+ #if DEBUG
104
+ /// The most common integration mistake: without `audio` in UIBackgroundModes,
105
+ /// playback stops when the screen locks. Development builds only; a release
106
+ /// build prints nothing.
107
+ private static func warnIfBackgroundAudioIsMissing() {
108
+ let modes = Bundle.main.object(forInfoDictionaryKey: "UIBackgroundModes") as? [String] ?? []
109
+ if !modes.contains("audio") {
110
+ NSLog("InstareadSDK: this app's Info.plist has no \"audio\" entry in UIBackgroundModes, so playback stops when the screen locks and the lock screen stays empty. Add it and rebuild.")
111
+ }
112
+ }
113
+ #endif
114
+ }
@@ -0,0 +1,18 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ #ifdef RCT_NEW_ARCH_ENABLED
4
+ #import <InstareadReactNativeSpec/InstareadReactNativeSpec.h>
5
+ #endif
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ // The same module on both architectures: a TurboModule on the New Architecture,
10
+ // a regular native module on the old one.
11
+ #ifdef RCT_NEW_ARCH_ENABLED
12
+ @interface InstareadReactNativeModule : NSObject <NativeInstareadReactNativeSpec>
13
+ #else
14
+ @interface InstareadReactNativeModule : NSObject <RCTBridgeModule>
15
+ #endif
16
+ @end
17
+
18
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,48 @@
1
+ #import "InstareadReactNativeModule.h"
2
+
3
+ #if __has_include(<InstareadReactNative/InstareadReactNative-Swift.h>)
4
+ #import <InstareadReactNative/InstareadReactNative-Swift.h>
5
+ #else
6
+ #import "InstareadReactNative-Swift.h"
7
+ #endif
8
+
9
+ @implementation InstareadReactNativeModule
10
+
11
+ RCT_EXPORT_MODULE(InstareadReactNative)
12
+
13
+ + (BOOL)requiresMainQueueSetup
14
+ {
15
+ return NO;
16
+ }
17
+
18
+ // InstareadPlayer.configure builds a UIWindow, so it has to run on main.
19
+ static void InstareadConfigure(NSString *publication)
20
+ {
21
+ dispatch_async(dispatch_get_main_queue(), ^{
22
+ [InstareadBridge configureWithPublication:publication];
23
+ });
24
+ }
25
+
26
+ #ifdef RCT_NEW_ARCH_ENABLED
27
+
28
+ - (void)configure:(NSString *)publication
29
+ {
30
+ InstareadConfigure(publication);
31
+ }
32
+
33
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
34
+ (const facebook::react::ObjCTurboModule::InitParams &)params
35
+ {
36
+ return std::make_shared<facebook::react::NativeInstareadReactNativeSpecJSI>(params);
37
+ }
38
+
39
+ #else
40
+
41
+ RCT_EXPORT_METHOD(configure : (NSString *)publication)
42
+ {
43
+ InstareadConfigure(publication);
44
+ }
45
+
46
+ #endif
47
+
48
+ @end
@@ -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,16 @@
1
+ "use strict";
2
+
3
+ import NativeInstareadReactNative from "./NativeInstareadReactNative.js";
4
+ export const InstareadPlayer = {
5
+ /**
6
+ * Call once at app start, before anything renders.
7
+ *
8
+ * Sets the publication that `<InstareadPlayerView articleUrl=... />` uses and
9
+ * turns on the Instaread mini player, which floats above your app while
10
+ * something plays.
11
+ */
12
+ configure(publication) {
13
+ NativeInstareadReactNative?.configure(publication);
14
+ }
15
+ };
16
+ //# sourceMappingURL=InstareadPlayer.js.map
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+
3
+ import { Component, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { Platform, StyleSheet } from 'react-native';
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ // In a release build the player stays silent whatever goes wrong; while
7
+ // developing, say why nothing shows.
8
+ function warnInDev(message) {
9
+ if (__DEV__) {
10
+ console.warn(`[InstareadSDK] ${message}`);
11
+ }
12
+ }
13
+
14
+ // iOS and Android both ship the native component. Anywhere else (web, for
15
+ // instance) requiring it would register a component that doesn't exist, so the
16
+ // player renders nothing instead.
17
+ function loadNativePlayer() {
18
+ if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
19
+ return null;
20
+ }
21
+ try {
22
+ return require('./InstareadNativePlayerNativeComponent').default;
23
+ } catch (error) {
24
+ warnInDev(`The native player could not be loaded: ${String(error)}`);
25
+ return null;
26
+ }
27
+ }
28
+ const NativePlayer = loadNativePlayer();
29
+
30
+ /**
31
+ * The player never puts an error in your app: if the native side is missing
32
+ * (say the app wasn't rebuilt after installing the SDK), it renders nothing.
33
+ */
34
+ class FailSilent extends Component {
35
+ state = {
36
+ failed: false
37
+ };
38
+ static getDerivedStateFromError() {
39
+ return {
40
+ failed: true
41
+ };
42
+ }
43
+ componentDidCatch(error) {
44
+ warnInDev(`The player could not be shown (${error.message}). Rebuild the app after installing the SDK` + (Platform.OS === 'ios' ? ', running `pod install` first.' : '.'));
45
+ }
46
+ render() {
47
+ return this.state.failed ? null : this.props.children;
48
+ }
49
+ }
50
+
51
+ // The player's size is decided on Instaread's side — the height is reported by
52
+ // the player itself and the width always fills whatever container it is placed
53
+ // in, matching the native SDKs (Compose appends fillMaxWidth(), the Android
54
+ // View takes its parent's width, SwiftUI takes the available width). So none of
55
+ // these can be set from the app; placement — margins, alignment, padding — is
56
+ // still yours.
57
+ const OWNED_STYLE_KEYS = ['height', 'minHeight', 'maxHeight', 'width', 'minWidth', 'maxWidth', 'flex', 'flexGrow', 'flexShrink', 'flexBasis', 'aspectRatio'];
58
+ function layoutOnly(style) {
59
+ const flat = {
60
+ ...StyleSheet.flatten(style)
61
+ };
62
+ for (const key of OWNED_STYLE_KEYS) {
63
+ delete flat[key];
64
+ }
65
+ return flat;
66
+ }
67
+
68
+ // How long to wait, after mounting or after articleUrl/publication changes,
69
+ // before treating a still-empty player as onError. Not a signal from the
70
+ // player itself — the native SDK never reports a distinct error, so this is
71
+ // a React Native-side guess, and a legitimately slow load looks the same as
72
+ // a real failure until it either resolves or this fires. 15s, not something
73
+ // shorter: the server can be slow to respond while autoscaling.
74
+ const EMPTY_AFTER_MS = 15000;
75
+ /**
76
+ * The Instaread player for an article. It sizes itself: zero height until there
77
+ * is something to show, then the loader, then the player.
78
+ */
79
+ export function InstareadPlayerView({
80
+ articleUrl,
81
+ publication,
82
+ dark = false,
83
+ style,
84
+ onReady,
85
+ onError
86
+ }) {
87
+ const [height, setHeight] = useState(0);
88
+ const isReady = useRef(false);
89
+ const emptyTimer = useRef(null);
90
+
91
+ // A new article/publication is a fresh attempt: forget the last result and
92
+ // give this one its own onError timeout.
93
+ useEffect(() => {
94
+ isReady.current = false;
95
+ setHeight(0);
96
+ emptyTimer.current = setTimeout(() => {
97
+ if (!isReady.current) {
98
+ onError?.();
99
+ }
100
+ }, EMPTY_AFTER_MS);
101
+ return () => {
102
+ if (emptyTimer.current) {
103
+ clearTimeout(emptyTimer.current);
104
+ }
105
+ };
106
+ // Only articleUrl/publication/dark should restart the attempt — not
107
+ // identity changes of onReady/onError themselves.
108
+ // eslint-disable-next-line react-hooks/exhaustive-deps
109
+ }, [articleUrl, publication, dark]);
110
+ const onHeightChange = useCallback(event => {
111
+ const next = event.nativeEvent.height;
112
+ setHeight(next);
113
+ if (next > 0 && !isReady.current) {
114
+ isReady.current = true;
115
+ if (emptyTimer.current) {
116
+ clearTimeout(emptyTimer.current);
117
+ }
118
+ onReady?.();
119
+ }
120
+ }, [onReady]);
121
+ const layout = useMemo(() => layoutOnly(style), [style]);
122
+ if (!NativePlayer) {
123
+ return null;
124
+ }
125
+ return /*#__PURE__*/_jsx(FailSilent, {
126
+ children: /*#__PURE__*/_jsx(NativePlayer, {
127
+ articleUrl: articleUrl,
128
+ publication: publication ?? '',
129
+ dark: dark,
130
+ onHeightChange: onHeightChange,
131
+ style: [layout, {
132
+ height
133
+ }]
134
+ })
135
+ });
136
+ }
137
+ //# sourceMappingURL=InstareadPlayerView.js.map
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ // `get`, not `getEnforcing`: on a platform without the native module (Android)
5
+ // `InstareadPlayer.configure` is a no-op instead of a crash in the app.
6
+ export default TurboModuleRegistry.get('InstareadReactNative');
7
+ //# sourceMappingURL=NativeInstareadReactNative.js.map
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ export { InstareadPlayer } from "./InstareadPlayer.js";
4
+ export { InstareadPlayerView } from "./InstareadPlayerView.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=react-native-deep-imports.d.js.map
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,14 @@
1
+ import type { ViewProps } from 'react-native';
2
+ import type { DirectEventHandler, Double } from 'react-native/Libraries/Types/CodegenTypes';
3
+ type HeightChangeEvent = Readonly<{
4
+ height: Double;
5
+ }>;
6
+ export interface NativeProps extends ViewProps {
7
+ publication?: string;
8
+ articleUrl?: string;
9
+ dark?: boolean;
10
+ onHeightChange?: DirectEventHandler<HeightChangeEvent>;
11
+ }
12
+ declare const _default: import("react-native").HostComponent<NativeProps>;
13
+ export default _default;
14
+ //# sourceMappingURL=InstareadNativePlayerNativeComponent.d.ts.map
@@ -0,0 +1,11 @@
1
+ export declare const InstareadPlayer: {
2
+ /**
3
+ * Call once at app start, before anything renders.
4
+ *
5
+ * Sets the publication that `<InstareadPlayerView articleUrl=... />` uses and
6
+ * turns on the Instaread mini player, which floats above your app while
7
+ * something plays.
8
+ */
9
+ configure(publication: string): void;
10
+ };
11
+ //# sourceMappingURL=InstareadPlayer.d.ts.map