@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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.3.4
|
|
4
|
+
|
|
5
|
+
- Bundles InstareadSDK 1.4.4 (iOS) and 1.3.3 (Android). Both bring the same fix: the mini player
|
|
6
|
+
now follows `mobile_is_floating_player_enabled` being switched on or off once the app is
|
|
7
|
+
reopened, instead of keeping whatever it read when the process first started.
|
|
8
|
+
- Android also reserves a few dp below the height the player page reports, so a bottom border or
|
|
9
|
+
shadow is not clipped on the compact design.
|
|
10
|
+
- No change to this package's own API.
|
|
11
|
+
|
|
12
|
+
## 1.3.3
|
|
13
|
+
|
|
14
|
+
- Bundles InstareadSDK 1.4.3 (iOS) and 1.3.2 (Android). Both are documentation-only releases of
|
|
15
|
+
the native SDKs — the players themselves are byte-for-byte what 1.4.2 and 1.3.1 shipped, so
|
|
16
|
+
behaviour is unchanged.
|
|
17
|
+
- The README's checksum step is now a command that verifies on its own,
|
|
18
|
+
`shasum -a 256 -c react-native-instaread-sdk-<version>.sha256`, instead of asking you to compare
|
|
19
|
+
two hashes by eye.
|
|
20
|
+
- No change to this package's own API.
|
|
21
|
+
|
|
22
|
+
## 1.3.2
|
|
23
|
+
|
|
24
|
+
- Bundles InstareadSDK 1.3.1 (Android), which fixes a band of blank space under the player on
|
|
25
|
+
Android — or a clipped player — depending on which design the publication uses. 1.3.1 of this
|
|
26
|
+
package shipped the Android SDK from before that fix.
|
|
27
|
+
- iOS is unaffected and stays on InstareadSDK 1.4.2. No change to this package's own API.
|
|
28
|
+
|
|
29
|
+
## 1.3.1
|
|
30
|
+
|
|
31
|
+
- Bundles InstareadSDK 1.4.2 (iOS), up from 1.4.0 — which brings 1.4.1's packaging work with it.
|
|
32
|
+
Nothing changed in the native player itself; both of those iOS releases were packaging only.
|
|
33
|
+
- Android stays on InstareadSDK 1.3.0.
|
|
34
|
+
- No change to this package's own API: `<InstareadPlayerView />` takes the same props and behaves
|
|
35
|
+
identically.
|
|
36
|
+
|
|
37
|
+
## 1.3.0
|
|
38
|
+
|
|
39
|
+
- **Dark mode.** `<InstareadPlayerView dark />` renders the dark player, on both platforms and
|
|
40
|
+
both Android architectures. Your app decides — the SDK never reads the device's appearance,
|
|
41
|
+
because an app with its own in-app theme toggle can be dark while the phone is light. The mini
|
|
42
|
+
player, its playlist sheet and its Now Playing panel follow the article player's palette rather
|
|
43
|
+
than carrying their own.
|
|
44
|
+
- Bundles InstareadSDK 1.4.0 (iOS) and 1.3.0 (Android).
|
|
45
|
+
|
|
46
|
+
## 1.2.0
|
|
47
|
+
|
|
48
|
+
- **Android.** The package now ships both platforms: the Android half hosts the native
|
|
49
|
+
`InstareadPlayerView` from `co.instaread:sdk` (bundled as an `.aar`) exactly as the iOS half
|
|
50
|
+
hosts `InstareadSDK.xcframework`. Nothing about the player is reimplemented. The JS API is
|
|
51
|
+
unchanged — `InstareadPlayer.configure(...)` and `<InstareadPlayerView articleUrl=... />` work
|
|
52
|
+
the same on both.
|
|
53
|
+
- Supports both Android architectures: a Fabric `ViewManager` with the generated delegate on the
|
|
54
|
+
new architecture, `@ReactProp` on the old one. Verified running on RN 0.79.7 (old) and 0.87.1
|
|
55
|
+
(new).
|
|
56
|
+
- The player's height is reported natively and applied in JS, as on iOS. Reported in dp, so it is
|
|
57
|
+
correct on every screen density.
|
|
58
|
+
- `style` now also ignores `width`/`minWidth`/`maxWidth`, alongside height and flex: the player
|
|
59
|
+
fills the container it is placed in on every platform, and its size is not the app's to set.
|
|
60
|
+
- Bundles InstareadSDK 1.3.3 (iOS) and 1.2.3 (Android) — see `instareadNativeSdk` in
|
|
61
|
+
`package.json`.
|
|
62
|
+
|
|
63
|
+
## 1.1.0
|
|
64
|
+
|
|
65
|
+
- Bundles InstareadSDK 1.3.3, which adds Apple's privacy manifest (`PrivacyInfo.xcprivacy`) —
|
|
66
|
+
see `instareadNativeSdk` in `package.json`.
|
|
67
|
+
- `InstareadPlayerView` gains `onReady` and `onError`, its only two signals (see the README). No
|
|
68
|
+
`onPlay`/`onPause`/`onEnded`/analytics hook — deliberately not exposed.
|
|
69
|
+
- Supports the old architecture (Paper) as well as the New Architecture, so apps on older React
|
|
70
|
+
Native don't need to upgrade to integrate. Same JS API on both.
|
|
71
|
+
- Widens support down to React Native 0.76 (from 0.84): built and run on 0.76.9, 0.79.7, 0.81.6,
|
|
72
|
+
0.84.1, 0.86.2 and 0.87.1, Xcode 26, both architectures where the version has one.
|
|
73
|
+
- Codegen spec now imports from React Native's original module paths instead of its newer
|
|
74
|
+
re-exports, for compatibility across that wider version range.
|
|
75
|
+
- `peerDependencies.react-native` lowered to `>=0.76.0`; `react` un-pinned.
|
|
76
|
+
- `InstareadPlayerView`'s `style` prop no longer accepts height/flex sizing — the player's height
|
|
77
|
+
was never actually settable through it, so this only makes the type honest.
|
|
78
|
+
- In development builds, warns to the console instead of failing silently when the native module
|
|
79
|
+
is missing or `UIBackgroundModes` lacks `audio`.
|
|
80
|
+
|
|
81
|
+
## 1.0.0
|
|
82
|
+
|
|
83
|
+
- First release. iOS only: on Android the view renders nothing and `configure` does nothing.
|
|
84
|
+
- `InstareadPlayer.configure(publication)` and a self-sizing `<InstareadPlayerView articleUrl />`.
|
|
85
|
+
- Bundles InstareadSDK 1.3.2 (see `instareadNativeSdk` in `package.json`).
|
|
86
|
+
- React Native 0.84+ (built and run on 0.84.1, 0.86.2, 0.87.1), Xcode 26.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
# The pod is deliberately not called "InstareadSdk": that differs from the native
|
|
6
|
+
# framework "InstareadSDK" only by case, and on a case-insensitive filesystem the
|
|
7
|
+
# two would share build products and module-cache entries.
|
|
8
|
+
Pod::Spec.new do |s|
|
|
9
|
+
s.name = "InstareadReactNative"
|
|
10
|
+
s.version = package["version"]
|
|
11
|
+
s.summary = package["description"]
|
|
12
|
+
s.homepage = "https://instaread.co"
|
|
13
|
+
s.license = { :type => "Proprietary", :file => "LICENSE" }
|
|
14
|
+
s.authors = package["author"]
|
|
15
|
+
|
|
16
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
17
|
+
# Delivered as a zip and linked from the customer's node_modules; never fetched.
|
|
18
|
+
s.source = { :path => "." }
|
|
19
|
+
|
|
20
|
+
# Not `ios/**`: the XCFramework below carries its own headers.
|
|
21
|
+
s.source_files = "ios/*.{h,m,mm,swift}"
|
|
22
|
+
s.private_header_files = "ios/*.h"
|
|
23
|
+
s.swift_version = "5.0"
|
|
24
|
+
|
|
25
|
+
# The native SDK, unchanged. Put it here with scripts/sync-ios-sdk.sh.
|
|
26
|
+
s.vendored_frameworks = "ios/Frameworks/InstareadSDK.xcframework"
|
|
27
|
+
|
|
28
|
+
install_modules_dependencies(s)
|
|
29
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Copyright (c) 2026 Instaread. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software is proprietary to Instaread. It is provided to Instaread
|
|
4
|
+
customers for use in their own applications, under the terms of their agreement
|
|
5
|
+
with Instaread. Without such an agreement no right to use, copy, modify or
|
|
6
|
+
distribute it is granted.
|
|
7
|
+
|
|
8
|
+
The package includes a compiled copy of InstareadSDK, which is covered by the
|
|
9
|
+
same terms.
|
package/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# react-native-instaread-sdk
|
|
2
|
+
|
|
3
|
+
Audio player for article screens in React Native, on iOS and Android. It hosts the native
|
|
4
|
+
InstareadSDK, which renders Instaread's real web player inside a WebView — so it looks and
|
|
5
|
+
behaves exactly like the web player — publishes it to the lock screen (and the notification
|
|
6
|
+
shade on Android), and shows a floating mini player above your app while something plays.
|
|
7
|
+
|
|
8
|
+
- **Same player as the web.** Nothing is re-implemented in JavaScript, and there is no
|
|
9
|
+
`react-native-webview` dependency.
|
|
10
|
+
- **Read-only.** It plays audio that already exists for an article. It never triggers audio
|
|
11
|
+
generation.
|
|
12
|
+
- **Server-driven.** Player design, colours and whether the mini player is on come from the
|
|
13
|
+
server per publication, so most changes need no app release.
|
|
14
|
+
- **Both platforms, one API.** The same JavaScript runs on iOS and Android, and on both Android
|
|
15
|
+
architectures (New and old/Paper).
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
| | |
|
|
20
|
+
|---|---|
|
|
21
|
+
| React Native | 0.76 or newer, either architecture (New or old/Paper). Built and run on 0.76.9, 0.79.7, 0.81.6, 0.84.1, 0.86.2 and 0.87.1 — old architecture on 0.76–0.81 (the versions that still have one), New Architecture on all six |
|
|
22
|
+
| iOS | 13.0+. Built with Xcode 26 |
|
|
23
|
+
| Android | API 24 (7.0)+ |
|
|
24
|
+
| Your `Info.plist` (iOS) | `UIBackgroundModes` containing `audio` — see below |
|
|
25
|
+
| Android permissions | `FOREGROUND_SERVICE` and `FOREGROUND_SERVICE_MEDIA_PLAYBACK` are merged in for you. `POST_NOTIFICATIONS` on Android 13+ is yours to request — without it audio still plays, but the notification and lock-screen card do not appear |
|
|
26
|
+
| Network | HTTPS to `player.instaread.co` and `player-api.instaread.co`. No ATS exceptions needed |
|
|
27
|
+
| Permissions prompts | None from the SDK. No entitlements |
|
|
28
|
+
|
|
29
|
+
**`UIBackgroundModes` → `audio` is yours to add.** It belongs to your app bundle; the SDK cannot
|
|
30
|
+
grant it. Without it, playback stops and the lock screen goes empty as soon as the screen locks.
|
|
31
|
+
|
|
32
|
+
```xml
|
|
33
|
+
<key>UIBackgroundModes</key>
|
|
34
|
+
<array>
|
|
35
|
+
<string>audio</string>
|
|
36
|
+
</array>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
You receive `react-native-instaread-sdk-<version>.zip`. Nothing to publish or download from a
|
|
42
|
+
registry; the native SDK is inside.
|
|
43
|
+
|
|
44
|
+
1. Unzip it into your project, e.g. `vendor/`. Keep the folder inside your project.
|
|
45
|
+
2. Add it as a dependency:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
npm install ./vendor/react-native-instaread-sdk
|
|
49
|
+
# yarn: yarn add file:./vendor/react-native-instaread-sdk
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
3. Install pods:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
cd ios && pod install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
4. iOS: add `audio` to `UIBackgroundModes` (above), then rebuild.
|
|
59
|
+
5. Android: nothing else. Rebuild the app.
|
|
60
|
+
|
|
61
|
+
To verify the download, run `shasum -a 256 -c react-native-instaread-sdk-<version>.sha256` next to the zip. It prints `OK`.
|
|
62
|
+
|
|
63
|
+
## Use
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { InstareadPlayer, InstareadPlayerView } from 'react-native-instaread-sdk';
|
|
67
|
+
|
|
68
|
+
// Once, at app start — before anything renders.
|
|
69
|
+
InstareadPlayer.configure('yourslug');
|
|
70
|
+
|
|
71
|
+
// Wherever the player goes on an article screen.
|
|
72
|
+
<InstareadPlayerView articleUrl={article.canonicalUrl} />
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
That is the whole integration. There is no client to create and nothing to pass around: every
|
|
76
|
+
`InstareadPlayerView` and the mini player share the SDK's own state, so pressing Play on one
|
|
77
|
+
article's player shows up in the mini player anywhere else in your app.
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
| | Notes |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `InstareadPlayer.configure(publication)` | Once, at startup. Sets the publication `InstareadPlayerView` uses and shows the mini player in its own window above your app (above your tab bar if you have one). |
|
|
84
|
+
| `<InstareadPlayerView articleUrl publication? style? onReady? onError? />` | `articleUrl` is the article's own page URL, the same one the web player is given. `publication` overrides the configured slug for this one player. `style` is for layout only (margins, alignment) — the height is the player's own. |
|
|
85
|
+
|
|
86
|
+
`InstareadPlayerView` sizes itself: zero height until there is something to show, then the
|
|
87
|
+
loader, then the player. It never scrolls on its own, so place it inside your article's
|
|
88
|
+
`ScrollView`.
|
|
89
|
+
|
|
90
|
+
**`onReady` and `onError`** are the only two signals the player gives your code:
|
|
91
|
+
|
|
92
|
+
- `onReady()` fires once the player has shown something for this article — the player itself, or
|
|
93
|
+
Instaread's "Audio Coming Soon" state. Changing `articleUrl` or `publication` resets it.
|
|
94
|
+
- `onError()` fires once if nothing has rendered about 15 seconds after mounting (or after
|
|
95
|
+
`articleUrl`/`publication` changes) — usually a wrong or not-yet-enabled `publication`, or no
|
|
96
|
+
network. It's a guess made on the React Native side, not a report from the player itself: the
|
|
97
|
+
native SDK never distinguishes a real failure from an unusually slow load, so `onError` can fire
|
|
98
|
+
on a slow connection that would have come through fine a moment later. It never fires once
|
|
99
|
+
`onReady` has.
|
|
100
|
+
|
|
101
|
+
There is deliberately no `onPlay`/`onPause`/`onEnded`/analytics hook: what happens inside the
|
|
102
|
+
player, and the mini player's own behaviour, are not exposed — see *How it behaves* below.
|
|
103
|
+
|
|
104
|
+
## How it behaves
|
|
105
|
+
|
|
106
|
+
- **`publication`** is your Instaread slug. It must match exactly, and Instaread must have set it
|
|
107
|
+
up for the app SDK.
|
|
108
|
+
- **An article whose audio isn't ready** shows Instaread's "Audio Coming Soon" state. The SDK
|
|
109
|
+
never starts audio processing.
|
|
110
|
+
- **The mini player** only appears for publications where it is switched on (see below). Leaving
|
|
111
|
+
an article screen while it plays keeps the audio going, controlled from the mini player.
|
|
112
|
+
|
|
113
|
+
## Server-side settings
|
|
114
|
+
|
|
115
|
+
Set per publication by Instaread, not in the app — which is why they change without a release:
|
|
116
|
+
|
|
117
|
+
| Key | Effect |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `mobile_playerType` | Inline design: `acoustic` (default), `compact` or `rectangular` |
|
|
120
|
+
| `mobile_is_floating_player_enabled` | Whether the mini player exists at all. **Off by default — the usual reason a mini player never appears** |
|
|
121
|
+
| `mobile_mini_player_bottom_height` | Extra points the mini bar sits above the bottom. `0` by default. Use it when your bottom UI isn't a native `UITabBar` (React Navigation's bottom tabs, for instance, are not one — ask Instaread to set this) |
|
|
122
|
+
|
|
123
|
+
## Privacy
|
|
124
|
+
|
|
125
|
+
The native SDK ships Apple's privacy manifest (`PrivacyInfo.xcprivacy`) inside the framework.
|
|
126
|
+
It collects an anonymous device ID (a random UUID kept in the Keychain, not the advertising ID)
|
|
127
|
+
and playback interactions, for analytics and app functionality, not linked to identity and not
|
|
128
|
+
used for tracking. Your own App Store privacy label is still yours to fill in.
|
|
129
|
+
|
|
130
|
+
## Troubleshooting
|
|
131
|
+
|
|
132
|
+
| Symptom | Likely cause |
|
|
133
|
+
|---|---|
|
|
134
|
+
| Nothing shows | Wrong `publication` slug, or not enabled for the app SDK. Check that `configure` ran before the first `InstareadPlayerView` |
|
|
135
|
+
| Player works, no mini player | `mobile_is_floating_player_enabled` is off for the publication |
|
|
136
|
+
| Audio stops when the screen locks (iOS) | `UIBackgroundModes` → `audio` missing from your `Info.plist` |
|
|
137
|
+
| No notification or lock-screen card (Android 13+) | Your app has not requested `POST_NOTIFICATIONS` |
|
|
138
|
+
| Mini player covers your bottom tab bar | It is not a native `UITabBar`; ask Instaread to set `mobile_mini_player_bottom_height` |
|
|
139
|
+
| `pod install` can't find `InstareadReactNative` | The folder was unzipped outside your project, or `npm install` wasn't re-run after moving it |
|
|
140
|
+
|
|
141
|
+
In a Debug build the native SDK prints reasons prefixed `InstareadSDK:` — to the Xcode console on
|
|
142
|
+
iOS, to logcat on Android. A release build prints nothing.
|
|
143
|
+
|
|
144
|
+
## License
|
|
145
|
+
|
|
146
|
+
Proprietary. See `LICENSE`.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// The Android half of the wrapper. It hosts the native InstareadSDK for
|
|
2
|
+
// Android exactly as the iOS half hosts InstareadSDK.xcframework: the SDK is
|
|
3
|
+
// bundled, used through its public API, and nothing about the player is
|
|
4
|
+
// reimplemented here.
|
|
5
|
+
|
|
6
|
+
buildscript {
|
|
7
|
+
repositories {
|
|
8
|
+
google()
|
|
9
|
+
mavenCentral()
|
|
10
|
+
}
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
apply plugin: "com.android.library"
|
|
18
|
+
apply plugin: "org.jetbrains.kotlin.android"
|
|
19
|
+
apply plugin: "com.facebook.react"
|
|
20
|
+
|
|
21
|
+
def reactNativeArchitectures() {
|
|
22
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
23
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def isNewArchitectureEnabled() {
|
|
27
|
+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
android {
|
|
31
|
+
namespace "com.instaread.reactnative"
|
|
32
|
+
|
|
33
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 35)
|
|
34
|
+
|
|
35
|
+
defaultConfig {
|
|
36
|
+
// The native SDK's own floor; raising it here would raise it for every
|
|
37
|
+
// app that installs this package.
|
|
38
|
+
minSdkVersion safeExtGet("minSdkVersion", 24)
|
|
39
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 35)
|
|
40
|
+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
buildFeatures {
|
|
44
|
+
buildConfig true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
sourceSets {
|
|
48
|
+
main {
|
|
49
|
+
// The generated ViewManagerDelegate/interface differ between the two
|
|
50
|
+
// architectures, so each gets its own thin ViewManager subclass and the
|
|
51
|
+
// shared code stays in src/main.
|
|
52
|
+
if (isNewArchitectureEnabled()) {
|
|
53
|
+
java.srcDirs += ["src/newarch"]
|
|
54
|
+
} else {
|
|
55
|
+
java.srcDirs += ["src/oldarch"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
compileOptions {
|
|
61
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
62
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
lintOptions {
|
|
66
|
+
disable "GradleCompatible"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
kotlin {
|
|
71
|
+
jvmToolchain(17)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
repositories {
|
|
75
|
+
mavenCentral()
|
|
76
|
+
google()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
dependencies {
|
|
80
|
+
implementation "com.facebook.react:react-android"
|
|
81
|
+
|
|
82
|
+
// The native SDK, vendored by scripts/sync-android-sdk.sh. A plain file
|
|
83
|
+
// dependency rather than the Maven repository the customer zip ships:
|
|
84
|
+
// a library module that declares its own repositories{} breaks any app whose
|
|
85
|
+
// settings.gradle uses FAIL_ON_PROJECT_REPOS, and this wrapper must not care
|
|
86
|
+
// how a partner's Gradle is set up.
|
|
87
|
+
implementation files("$projectDir/libs/instaread-sdk.aar")
|
|
88
|
+
|
|
89
|
+
// An .aar carries no dependency metadata, so the SDK's own dependencies are
|
|
90
|
+
// declared here instead. sync-android-sdk.sh reads them out of the SDK's POM
|
|
91
|
+
// and fails if this list has drifted — do not edit these by hand.
|
|
92
|
+
implementation "androidx.media:media:1.7.0"
|
|
93
|
+
implementation "androidx.core:core-ktx:1.13.1"
|
|
94
|
+
implementation "androidx.startup:startup-runtime:1.2.0"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
react {
|
|
98
|
+
jsRootDir = file("../src/")
|
|
99
|
+
libraryName = "InstareadReactNative"
|
|
100
|
+
codegenJavaPackageName = "com.instaread.reactnative"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def safeExtGet(prop, fallback) {
|
|
104
|
+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
105
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.3.3
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<!-- Empty on purpose. Everything the player needs — the FOREGROUND_SERVICE
|
|
3
|
+
permissions, the playback service and the androidx.startup initializer
|
|
4
|
+
— is declared by the native SDK's own manifest and merged in from the
|
|
5
|
+
.aar, exactly as it is for a plain Android app. -->
|
|
6
|
+
</manifest>
|
package/android/src/main/java/com/instaread/reactnative/InstareadNativePlayerViewManagerBase.kt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package com.instaread.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
4
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Everything the two architectures share. The only difference between them is
|
|
8
|
+
* how props reach the view — the new architecture goes through a generated
|
|
9
|
+
* delegate, the old one through `@ReactProp` — so each has a thin subclass in
|
|
10
|
+
* src/newarch and src/oldarch and the real work stays here.
|
|
11
|
+
*/
|
|
12
|
+
abstract class InstareadNativePlayerViewManagerBase : SimpleViewManager<InstareadPlayerHostView>() {
|
|
13
|
+
|
|
14
|
+
override fun getName() = NAME
|
|
15
|
+
|
|
16
|
+
public override fun createViewInstance(context: ThemedReactContext) =
|
|
17
|
+
InstareadPlayerHostView(context)
|
|
18
|
+
|
|
19
|
+
/** Runs once per update after every prop has been set — see the view's own
|
|
20
|
+
* note on why the player is built here rather than in a setter. */
|
|
21
|
+
override fun onAfterUpdateTransaction(view: InstareadPlayerHostView) {
|
|
22
|
+
super.onAfterUpdateTransaction(view)
|
|
23
|
+
view.commitProps()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Makes `onHeightChange` reach the JS prop of the same name. */
|
|
27
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
|
|
28
|
+
mutableMapOf(
|
|
29
|
+
InstareadPlayerHostView.HEIGHT_CHANGE_EVENT to
|
|
30
|
+
mapOf("registrationName" to InstareadPlayerHostView.HEIGHT_CHANGE_EVENT),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
companion object {
|
|
34
|
+
/** Must match the name given to codegenNativeComponent in
|
|
35
|
+
* src/InstareadNativePlayerNativeComponent.ts. */
|
|
36
|
+
const val NAME = "InstareadNativePlayer"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
package com.instaread.reactnative
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import android.widget.FrameLayout
|
|
5
|
+
import co.instaread.sdk.InstareadPlayerView
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.bridge.Arguments
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
9
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
10
|
+
import com.facebook.react.uimanager.events.Event
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hosts one native `InstareadPlayerView` and reports how tall it wants to be —
|
|
14
|
+
* the Android counterpart of InstareadPlayerHost.swift, and the only place this
|
|
15
|
+
* wrapper touches the SDK. It uses the SDK's public API exactly as a plain
|
|
16
|
+
* Android app would and adds nothing to it.
|
|
17
|
+
*
|
|
18
|
+
* The player sizes itself (zero height, then the loading placeholder, then the
|
|
19
|
+
* player), but a React Native view's height comes from JS. So this measures the
|
|
20
|
+
* player after every layout pass and hands the height to `onHeightChange`; JS
|
|
21
|
+
* turns it into the component's height. Same contract as iOS, which reads
|
|
22
|
+
* `intrinsicContentSize` for the same reason.
|
|
23
|
+
*/
|
|
24
|
+
class InstareadPlayerHostView(private val reactContext: ThemedReactContext) :
|
|
25
|
+
FrameLayout(reactContext) {
|
|
26
|
+
|
|
27
|
+
private var player: InstareadPlayerView? = null
|
|
28
|
+
private var publication = ""
|
|
29
|
+
private var articleUrl = ""
|
|
30
|
+
private var dark = false
|
|
31
|
+
private var reportedHeightDp = 0.0
|
|
32
|
+
private var pendingPublication = ""
|
|
33
|
+
private var pendingArticleUrl = ""
|
|
34
|
+
private var pendingDark = false
|
|
35
|
+
|
|
36
|
+
// Props arrive one at a time; the player is built once both have landed,
|
|
37
|
+
// from onAfterUpdateTransaction. Building on each setter would load the
|
|
38
|
+
// article twice whenever both change together.
|
|
39
|
+
fun setPublicationProp(value: String?) {
|
|
40
|
+
pendingPublication = value ?: ""
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun setArticleUrlProp(value: String?) {
|
|
44
|
+
pendingArticleUrl = value ?: ""
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fun setDarkProp(value: Boolean) {
|
|
48
|
+
pendingDark = value
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fun commitProps() {
|
|
52
|
+
if (pendingPublication == publication && pendingArticleUrl == articleUrl && pendingDark == dark) return
|
|
53
|
+
publication = pendingPublication
|
|
54
|
+
articleUrl = pendingArticleUrl
|
|
55
|
+
dark = pendingDark
|
|
56
|
+
rebuildPlayer()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun rebuildPlayer() {
|
|
60
|
+
player?.let { removeView(it) }
|
|
61
|
+
player = null
|
|
62
|
+
reportedHeightDp = 0.0
|
|
63
|
+
|
|
64
|
+
// Nothing to show without an article; an empty publication falls back
|
|
65
|
+
// to the one passed to InstareadPlayer.configure.
|
|
66
|
+
if (articleUrl.isNotEmpty()) {
|
|
67
|
+
val created = if (publication.isEmpty()) {
|
|
68
|
+
InstareadPlayerView(context, articleUrl, null, dark)
|
|
69
|
+
} else {
|
|
70
|
+
InstareadPlayerView(context, articleUrl, publication, dark)
|
|
71
|
+
}
|
|
72
|
+
addView(created, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
73
|
+
player = created
|
|
74
|
+
}
|
|
75
|
+
requestLayout()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* React Native lays its own views out and never runs Android's layout pass
|
|
80
|
+
* on their children, so a plain `requestLayout()` from the player — which
|
|
81
|
+
* is how it announces its measured height once the page has rendered —
|
|
82
|
+
* would go nowhere. Re-running measure/layout here is what makes the
|
|
83
|
+
* player's own sizing reach JS at all.
|
|
84
|
+
*/
|
|
85
|
+
override fun requestLayout() {
|
|
86
|
+
super.requestLayout()
|
|
87
|
+
post(measureAndLayout)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private val measureAndLayout = Runnable {
|
|
91
|
+
measure(
|
|
92
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
93
|
+
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
|
|
94
|
+
)
|
|
95
|
+
layout(left, top, right, bottom)
|
|
96
|
+
reportHeightIfNeeded()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private fun reportHeightIfNeeded() {
|
|
100
|
+
// In density-independent pixels: JS style values are dp, the measured
|
|
101
|
+
// value is px. Sending px would make the player several times too tall
|
|
102
|
+
// on any real handset.
|
|
103
|
+
val density = resources.displayMetrics.density
|
|
104
|
+
val heightDp = ((player?.measuredHeight ?: 0) / density).toDouble()
|
|
105
|
+
if (kotlin.math.abs(heightDp - reportedHeightDp) < 0.5) return
|
|
106
|
+
reportedHeightDp = heightDp
|
|
107
|
+
|
|
108
|
+
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
|
|
109
|
+
UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
|
|
110
|
+
?.dispatchEvent(HeightChangeEvent(surfaceId, id, heightDp))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Dispatched through the EventDispatcher rather than RCTEventEmitter, so
|
|
114
|
+
* the same code path works on both the old and the new architecture. */
|
|
115
|
+
private class HeightChangeEvent(
|
|
116
|
+
surfaceId: Int,
|
|
117
|
+
viewId: Int,
|
|
118
|
+
private val heightDp: Double,
|
|
119
|
+
) : Event<HeightChangeEvent>(surfaceId, viewId) {
|
|
120
|
+
override fun getEventName() = EVENT_NAME
|
|
121
|
+
|
|
122
|
+
override fun getEventData(): WritableMap =
|
|
123
|
+
Arguments.createMap().apply { putDouble("height", heightDp) }
|
|
124
|
+
|
|
125
|
+
companion object {
|
|
126
|
+
const val EVENT_NAME = "onHeightChange"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
companion object {
|
|
131
|
+
const val HEIGHT_CHANGE_EVENT = HeightChangeEvent.EVENT_NAME
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.instaread.reactnative
|
|
2
|
+
|
|
3
|
+
import co.instaread.sdk.InstareadPlayer
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReactMethod
|
|
7
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `InstareadPlayer.configure(publication)` for JS — the Android half of
|
|
11
|
+
* InstareadBridge in InstareadPlayerHost.swift.
|
|
12
|
+
*
|
|
13
|
+
* Declared as a plain module rather than against the generated TurboModule
|
|
14
|
+
* spec so one class serves both architectures: JS reaches it through
|
|
15
|
+
* `TurboModuleRegistry.get`, which falls back to the legacy registry.
|
|
16
|
+
*/
|
|
17
|
+
@ReactModule(name = InstareadReactNativeModule.NAME)
|
|
18
|
+
class InstareadReactNativeModule(reactContext: ReactApplicationContext) :
|
|
19
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
20
|
+
|
|
21
|
+
override fun getName() = NAME
|
|
22
|
+
|
|
23
|
+
@ReactMethod
|
|
24
|
+
fun configure(publication: String) {
|
|
25
|
+
val application = reactApplicationContext.applicationContext as? android.app.Application ?: return
|
|
26
|
+
// The SDK's own entry point, unchanged: sets the publication every
|
|
27
|
+
// player uses and brings up the mini player.
|
|
28
|
+
InstareadPlayer.configure(application, publication)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
/** Must match the name in src/NativeInstareadReactNative.ts. */
|
|
33
|
+
const val NAME = "InstareadReactNative"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.instaread.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* What React Native's autolinking finds. Registers the two things this package
|
|
10
|
+
* exposes: the player view and `InstareadPlayer.configure`.
|
|
11
|
+
*/
|
|
12
|
+
class InstareadReactNativePackage : ReactPackage {
|
|
13
|
+
|
|
14
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
15
|
+
listOf(InstareadReactNativeModule(reactContext))
|
|
16
|
+
|
|
17
|
+
override fun createViewManagers(
|
|
18
|
+
reactContext: ReactApplicationContext,
|
|
19
|
+
): List<ViewManager<*, *>> = listOf(InstareadNativePlayerViewManager())
|
|
20
|
+
}
|
package/android/src/newarch/java/com/instaread/reactnative/InstareadNativePlayerViewManager.kt
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package com.instaread.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
5
|
+
import com.facebook.react.viewmanagers.InstareadNativePlayerManagerDelegate
|
|
6
|
+
import com.facebook.react.viewmanagers.InstareadNativePlayerManagerInterface
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* New architecture (Fabric). Props arrive through the delegate generated from
|
|
10
|
+
* src/InstareadNativePlayerNativeComponent.ts, so this class only forwards
|
|
11
|
+
* them; everything else is in the shared base.
|
|
12
|
+
*/
|
|
13
|
+
@ReactModule(name = InstareadNativePlayerViewManagerBase.NAME)
|
|
14
|
+
class InstareadNativePlayerViewManager :
|
|
15
|
+
InstareadNativePlayerViewManagerBase(),
|
|
16
|
+
InstareadNativePlayerManagerInterface<InstareadPlayerHostView> {
|
|
17
|
+
|
|
18
|
+
private val delegate = InstareadNativePlayerManagerDelegate(this)
|
|
19
|
+
|
|
20
|
+
override fun getDelegate(): ViewManagerDelegate<InstareadPlayerHostView> = delegate
|
|
21
|
+
|
|
22
|
+
override fun setPublication(view: InstareadPlayerHostView, value: String?) {
|
|
23
|
+
view.setPublicationProp(value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
override fun setArticleUrl(view: InstareadPlayerHostView, value: String?) {
|
|
27
|
+
view.setArticleUrlProp(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun setDark(view: InstareadPlayerHostView, value: Boolean) {
|
|
31
|
+
view.setDarkProp(value)
|
|
32
|
+
}
|
|
33
|
+
}
|