@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
package/android/src/oldarch/java/com/instaread/reactnative/InstareadNativePlayerViewManager.kt
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -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
|