react-native-dynamic-resource-loader 0.2.0
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/DynamicResourceLoader.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +207 -0
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/dynamicresourceloader/DynamicResourceLoaderModule.kt +154 -0
- package/android/src/main/java/com/dynamicresourceloader/DynamicResourceLoaderPackage.kt +31 -0
- package/ios/DynamicResourceLoader.h +5 -0
- package/ios/DynamicResourceLoader.mm +83 -0
- package/ios/DynamicResourceLoaderImpl.swift +110 -0
- package/lib/module/NativeDynamicResourceLoader.js +5 -0
- package/lib/module/NativeDynamicResourceLoader.js.map +1 -0
- package/lib/module/codegen-types.d.js +2 -0
- package/lib/module/codegen-types.d.js.map +1 -0
- package/lib/module/index.js +22 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeDynamicResourceLoader.d.ts +20 -0
- package/lib/typescript/src/NativeDynamicResourceLoader.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +9 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +169 -0
- package/src/NativeDynamicResourceLoader.ts +21 -0
- package/src/codegen-types.d.ts +9 -0
- package/src/index.tsx +40 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "DynamicResourceLoader"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/Rob117//react-native-dynamic-resource-loader.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robert Sherling
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# react-native-dynamic-resource-loader
|
|
2
|
+
|
|
3
|
+
Load iOS [On-Demand Resources](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/On_Demand_Resources_Guide/) and Android [Play Asset Delivery](https://developer.android.com/guide/playcore/asset-delivery) from React Native. Download tagged assets at runtime to reduce your initial app size.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install react-native-dynamic-resource-loader
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Xcode Setup
|
|
12
|
+
|
|
13
|
+
Before using the library, you need to configure On-Demand Resources in your Xcode project:
|
|
14
|
+
|
|
15
|
+
1. **Enable ODR** — In your Xcode project, go to Build Settings and set `ENABLE_ON_DEMAND_RESOURCES = YES`.
|
|
16
|
+
|
|
17
|
+
2. **Add resources and assign tags** — Add files (images, data files, etc.) to your Xcode project's Resources build phase. Select each file in Xcode, open the File Inspector, and add one or more tags under "On Demand Resource Tags".
|
|
18
|
+
|
|
19
|
+
3. **Register tags** — Xcode does this automatically when you assign tags through the UI. You'll see them listed in your project's attributes under `KnownAssetTags`.
|
|
20
|
+
|
|
21
|
+
4. **Debug builds** — When running from the command line (e.g. `yarn ios`), Xcode's local asset server isn't available. Set `EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = YES` in your Debug build settings so ODR assets are embedded directly in the app bundle during development.
|
|
22
|
+
|
|
23
|
+
Multiple resources can share the same tag. When you download a tag, iOS fetches all resources with that tag together.
|
|
24
|
+
|
|
25
|
+
## Android Setup
|
|
26
|
+
|
|
27
|
+
Android uses [Play Asset Delivery](https://developer.android.com/guide/playcore/asset-delivery) (PAD) to deliver assets on demand. Each iOS "tag" maps to an Android "asset pack".
|
|
28
|
+
|
|
29
|
+
### 1. Add the PAD dependency
|
|
30
|
+
|
|
31
|
+
In your app's `android/app/build.gradle`:
|
|
32
|
+
|
|
33
|
+
```groovy
|
|
34
|
+
dependencies {
|
|
35
|
+
implementation("com.google.android.play:asset-delivery:2.3.0")
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Create an asset pack module
|
|
40
|
+
|
|
41
|
+
For each tag (e.g. `kichilogo`), create a directory at `android/kichilogo/` with this structure:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
android/kichilogo/
|
|
45
|
+
├── build.gradle
|
|
46
|
+
└── src/main/assets/
|
|
47
|
+
└── your_file.png
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`android/kichilogo/build.gradle`:
|
|
51
|
+
```groovy
|
|
52
|
+
apply plugin: 'com.android.asset-pack'
|
|
53
|
+
|
|
54
|
+
assetPack {
|
|
55
|
+
packName = "kichilogo"
|
|
56
|
+
dynamicDelivery {
|
|
57
|
+
deliveryType = "on-demand"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Wire up Gradle
|
|
63
|
+
|
|
64
|
+
In `android/settings.gradle`, add:
|
|
65
|
+
```groovy
|
|
66
|
+
include ':kichilogo'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
In `android/app/build.gradle`, inside the `android {}` block, add:
|
|
70
|
+
```groovy
|
|
71
|
+
assetPacks = [":kichilogo"]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 4. Local testing
|
|
75
|
+
|
|
76
|
+
On-demand asset packs are **not included** in a regular `./gradlew installDebug` APK. There is no Android equivalent to iOS's `EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE`. To test locally, use `bundletool` with the `--local-testing` flag, which tells the Play Core library to serve on-demand packs from the device's local storage instead of the Play Store.
|
|
77
|
+
|
|
78
|
+
Install `bundletool`:
|
|
79
|
+
```bash
|
|
80
|
+
brew install bundletool
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Build and install:
|
|
84
|
+
```bash
|
|
85
|
+
cd android
|
|
86
|
+
|
|
87
|
+
# 1. Build an AAB (Android App Bundle)
|
|
88
|
+
./gradlew bundleDebug
|
|
89
|
+
|
|
90
|
+
# 2. Convert to APKs with --local-testing
|
|
91
|
+
bundletool build-apks \
|
|
92
|
+
--bundle=app/build/outputs/bundle/debug/app-debug.aab \
|
|
93
|
+
--output=app.apks \
|
|
94
|
+
--local-testing
|
|
95
|
+
|
|
96
|
+
# 3. Install on device/emulator
|
|
97
|
+
bundletool install-apks --apks=app.apks
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The `AssetPackManager` API works normally — `fetch()` will "download" instantly from the local copy.
|
|
101
|
+
|
|
102
|
+
> **Note:** You need to re-run these steps whenever you change assets or native code. JS-only changes still work with Metro hot reload after the initial install.
|
|
103
|
+
|
|
104
|
+
## Usage
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
import {
|
|
108
|
+
checkResourcesAvailable,
|
|
109
|
+
downloadResources,
|
|
110
|
+
endAccessingResources,
|
|
111
|
+
getResourcePath,
|
|
112
|
+
setPreservationPriority,
|
|
113
|
+
} from 'react-native-dynamic-resource-loader';
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Download resources and get file paths
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
// Download all resources tagged "level1"
|
|
120
|
+
const success = await downloadResources(['level1']);
|
|
121
|
+
|
|
122
|
+
// Look up individual files by name (you know which files you tagged)
|
|
123
|
+
const spritePath = await getResourcePath('enemy_sprite', 'png');
|
|
124
|
+
const mapPath = await getResourcePath('level_map', 'json');
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Check if resources are already cached
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
const available = await checkResourcesAvailable(['level1']);
|
|
131
|
+
if (!available) {
|
|
132
|
+
await downloadResources(['level1']);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Release resources when done
|
|
137
|
+
|
|
138
|
+
Tell the OS it can purge these resources when storage is low (on Android, removes the asset pack):
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
endAccessingResources(['level1']);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Set preservation priority
|
|
145
|
+
|
|
146
|
+
Control which resources iOS purges first (0.0 = purge first, 1.0 = keep longest). No-op on Android:
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
setPreservationPriority(0.8, ['level1']);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## API
|
|
153
|
+
|
|
154
|
+
| Method | Returns | Description |
|
|
155
|
+
|--------|---------|-------------|
|
|
156
|
+
| `downloadResources(tags)` | `Promise<boolean>` | Download resources for the given tags. Checks cache first. |
|
|
157
|
+
| `checkResourcesAvailable(tags)` | `Promise<boolean>` | Check if tagged resources are already on device without downloading. |
|
|
158
|
+
| `endAccessingResources(tags)` | `void` | Release resources so the OS can purge them (iOS) or remove the pack (Android). |
|
|
159
|
+
| `getResourcePath(name, type)` | `Promise<string>` | Get the absolute file path for a resource after download. |
|
|
160
|
+
| `setPreservationPriority(priority, tags)` | `void` | Set purge priority (0.0-1.0). iOS only; no-op on Android. |
|
|
161
|
+
|
|
162
|
+
## Full Example
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import { useEffect, useState } from 'react';
|
|
166
|
+
import {
|
|
167
|
+
downloadResources,
|
|
168
|
+
getResourcePath,
|
|
169
|
+
endAccessingResources,
|
|
170
|
+
} from 'react-native-dynamic-resource-loader';
|
|
171
|
+
|
|
172
|
+
function MyComponent() {
|
|
173
|
+
const [imagePath, setImagePath] = useState(null);
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
let mounted = true;
|
|
177
|
+
|
|
178
|
+
downloadResources(['avatars'])
|
|
179
|
+
.then(() => getResourcePath('profile_pic', 'png'))
|
|
180
|
+
.then((path) => {
|
|
181
|
+
if (mounted) setImagePath(path);
|
|
182
|
+
})
|
|
183
|
+
.catch((e) => console.log('ODR failed:', e.message));
|
|
184
|
+
|
|
185
|
+
return () => {
|
|
186
|
+
mounted = false;
|
|
187
|
+
endAccessingResources(['avatars']);
|
|
188
|
+
};
|
|
189
|
+
}, []);
|
|
190
|
+
|
|
191
|
+
return imagePath ? <Image source={{ uri: `file://${imagePath}` }} /> : null;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
|
|
197
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
198
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
199
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.DynamicResourceLoader = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return DynamicResourceLoader[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.dynamicresourceloader"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies {
|
|
66
|
+
implementation "com.facebook.react:react-android"
|
|
67
|
+
implementation "com.google.android.play:asset-delivery:2.3.0"
|
|
68
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
package com.dynamicresourceloader
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.bridge.ReadableArray
|
|
7
|
+
import com.google.android.play.core.assetpacks.AssetPackManager
|
|
8
|
+
import com.google.android.play.core.assetpacks.AssetPackManagerFactory
|
|
9
|
+
import com.google.android.play.core.assetpacks.AssetPackState
|
|
10
|
+
import com.google.android.play.core.assetpacks.AssetPackStateUpdateListener
|
|
11
|
+
import com.google.android.play.core.assetpacks.AssetPackStates
|
|
12
|
+
import com.google.android.play.core.assetpacks.model.AssetPackStatus
|
|
13
|
+
import java.io.File
|
|
14
|
+
|
|
15
|
+
class DynamicResourceLoaderModule(reactContext: ReactApplicationContext) :
|
|
16
|
+
NativeDynamicResourceLoaderSpec(reactContext) {
|
|
17
|
+
|
|
18
|
+
private val assetPackManager: AssetPackManager =
|
|
19
|
+
AssetPackManagerFactory.getInstance(reactContext)
|
|
20
|
+
|
|
21
|
+
private fun emitProgress(tag: String, bytesDownloaded: Long, totalBytes: Long, fraction: Double, status: String) {
|
|
22
|
+
emitOnDownloadProgress(Arguments.createMap().apply {
|
|
23
|
+
putString("tag", tag)
|
|
24
|
+
putDouble("bytesDownloaded", bytesDownloaded.toDouble())
|
|
25
|
+
putDouble("totalBytes", totalBytes.toDouble())
|
|
26
|
+
putDouble("fractionCompleted", fraction)
|
|
27
|
+
putString("status", status)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private fun ReadableArray.toStringList(): List<String> {
|
|
32
|
+
val list = mutableListOf<String>()
|
|
33
|
+
for (i in 0 until size()) {
|
|
34
|
+
getString(i)?.let { list.add(it) }
|
|
35
|
+
}
|
|
36
|
+
return list
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override fun checkResourcesAvailable(tags: ReadableArray, promise: Promise) {
|
|
40
|
+
val packNames = tags.toStringList()
|
|
41
|
+
assetPackManager.getPackStates(packNames)
|
|
42
|
+
.addOnSuccessListener { states: AssetPackStates ->
|
|
43
|
+
val allCompleted = packNames.all { name ->
|
|
44
|
+
states.packStates()[name]?.status() == AssetPackStatus.COMPLETED
|
|
45
|
+
}
|
|
46
|
+
promise.resolve(allCompleted)
|
|
47
|
+
}
|
|
48
|
+
.addOnFailureListener { e ->
|
|
49
|
+
promise.reject("ERR_CHECK_RESOURCES", e.message, e)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun downloadResources(tags: ReadableArray, promise: Promise) {
|
|
54
|
+
val packNames = tags.toStringList()
|
|
55
|
+
|
|
56
|
+
assetPackManager.getPackStates(packNames)
|
|
57
|
+
.addOnSuccessListener { states: AssetPackStates ->
|
|
58
|
+
val allCompleted = packNames.all { name ->
|
|
59
|
+
states.packStates()[name]?.status() == AssetPackStatus.COMPLETED
|
|
60
|
+
}
|
|
61
|
+
if (allCompleted) {
|
|
62
|
+
for (name in packNames) {
|
|
63
|
+
emitProgress(name, 1, 1, 1.0, "completed")
|
|
64
|
+
}
|
|
65
|
+
promise.resolve(true)
|
|
66
|
+
return@addOnSuccessListener
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
val pending = packNames.filter { name ->
|
|
70
|
+
states.packStates()[name]?.status() != AssetPackStatus.COMPLETED
|
|
71
|
+
}.toMutableSet()
|
|
72
|
+
var settled = false
|
|
73
|
+
|
|
74
|
+
val listener = object : AssetPackStateUpdateListener {
|
|
75
|
+
override fun onStateUpdate(state: AssetPackState) {
|
|
76
|
+
if (settled) return
|
|
77
|
+
|
|
78
|
+
val name = state.name()
|
|
79
|
+
val bytesDownloaded = state.bytesDownloaded()
|
|
80
|
+
val totalBytes = state.totalBytesToDownload()
|
|
81
|
+
val fraction = if (totalBytes > 0) bytesDownloaded.toDouble() / totalBytes.toDouble() else 0.0
|
|
82
|
+
|
|
83
|
+
when (state.status()) {
|
|
84
|
+
AssetPackStatus.DOWNLOADING, AssetPackStatus.TRANSFERRING -> {
|
|
85
|
+
emitProgress(name, bytesDownloaded, totalBytes, fraction, "downloading")
|
|
86
|
+
}
|
|
87
|
+
AssetPackStatus.WAITING_FOR_WIFI -> {
|
|
88
|
+
emitProgress(name, bytesDownloaded, totalBytes, fraction, "waiting")
|
|
89
|
+
}
|
|
90
|
+
AssetPackStatus.COMPLETED -> {
|
|
91
|
+
emitProgress(name, totalBytes, totalBytes, 1.0, "completed")
|
|
92
|
+
pending.remove(name)
|
|
93
|
+
if (pending.isEmpty()) {
|
|
94
|
+
settled = true
|
|
95
|
+
assetPackManager.unregisterListener(this)
|
|
96
|
+
promise.resolve(true)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
AssetPackStatus.FAILED -> {
|
|
100
|
+
emitProgress(name, bytesDownloaded, totalBytes, fraction, "failed")
|
|
101
|
+
settled = true
|
|
102
|
+
assetPackManager.unregisterListener(this)
|
|
103
|
+
promise.reject(
|
|
104
|
+
"ERR_DOWNLOAD_FAILED",
|
|
105
|
+
"Asset pack '$name' failed with error code ${state.errorCode()}"
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
AssetPackStatus.REQUIRES_USER_CONFIRMATION -> {
|
|
109
|
+
settled = true
|
|
110
|
+
assetPackManager.unregisterListener(this)
|
|
111
|
+
promise.reject(
|
|
112
|
+
"ERR_REQUIRES_USER_CONFIRMATION",
|
|
113
|
+
"Asset pack '$name' requires user confirmation"
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
assetPackManager.registerListener(listener)
|
|
121
|
+
assetPackManager.fetch(packNames)
|
|
122
|
+
}
|
|
123
|
+
.addOnFailureListener { e ->
|
|
124
|
+
promise.reject("ERR_DOWNLOAD_RESOURCES", e.message, e)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override fun endAccessingResources(tags: ReadableArray) {
|
|
129
|
+
val packNames = tags.toStringList()
|
|
130
|
+
for (name in packNames) {
|
|
131
|
+
assetPackManager.removePack(name)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
override fun getResourcePath(resourceName: String, ofType: String, promise: Promise) {
|
|
136
|
+
val locations = assetPackManager.packLocations
|
|
137
|
+
for ((_, location) in locations) {
|
|
138
|
+
val file = File(location.assetsPath(), "$resourceName.$ofType")
|
|
139
|
+
if (file.exists()) {
|
|
140
|
+
promise.resolve(file.absolutePath)
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
promise.reject("RESOURCE_NOT_FOUND", "Could not find $resourceName.$ofType in any asset pack")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
override fun setPreservationPriority(priority: Double, tags: ReadableArray) {
|
|
148
|
+
// No-op on Android — Play Asset Delivery has no preservation priority equivalent
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
companion object {
|
|
152
|
+
const val NAME = NativeDynamicResourceLoaderSpec.NAME
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package com.dynamicresourceloader
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class DynamicResourceLoaderPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == DynamicResourceLoaderModule.NAME) {
|
|
13
|
+
DynamicResourceLoaderModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
20
|
+
mapOf(
|
|
21
|
+
DynamicResourceLoaderModule.NAME to ReactModuleInfo(
|
|
22
|
+
name = DynamicResourceLoaderModule.NAME,
|
|
23
|
+
className = DynamicResourceLoaderModule.NAME,
|
|
24
|
+
canOverrideExistingModule = false,
|
|
25
|
+
needsEagerInit = false,
|
|
26
|
+
isCxxModule = false,
|
|
27
|
+
isTurboModule = true
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#import "DynamicResourceLoader.h"
|
|
2
|
+
#import <DynamicResourceLoader-Swift.h>
|
|
3
|
+
|
|
4
|
+
@implementation DynamicResourceLoader {
|
|
5
|
+
DynamicResourceLoaderImpl *_impl;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
- (instancetype)init {
|
|
9
|
+
self = [super init];
|
|
10
|
+
if (self) {
|
|
11
|
+
_impl = [[DynamicResourceLoaderImpl alloc] init];
|
|
12
|
+
}
|
|
13
|
+
return self;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
- (void)checkResourcesAvailable:(NSArray<NSString *> *)tags
|
|
17
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
18
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
19
|
+
[_impl checkResourcesAvailable:tags
|
|
20
|
+
resolve:^(BOOL available) {
|
|
21
|
+
resolve(@(available));
|
|
22
|
+
}
|
|
23
|
+
reject:^(NSString *code, NSString *message, NSError *error) {
|
|
24
|
+
reject(code, message, error);
|
|
25
|
+
}];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
- (void)downloadResources:(NSArray<NSString *> *)tags
|
|
29
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
30
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
31
|
+
[_impl downloadResources:tags
|
|
32
|
+
progress:^(NSString *tag, int64_t bytesDownloaded, int64_t totalBytes, double fractionCompleted, NSString *status) {
|
|
33
|
+
[self emitOnDownloadProgress:@{
|
|
34
|
+
@"tag": tag,
|
|
35
|
+
@"bytesDownloaded": @(bytesDownloaded),
|
|
36
|
+
@"totalBytes": @(totalBytes),
|
|
37
|
+
@"fractionCompleted": @(fractionCompleted),
|
|
38
|
+
@"status": status,
|
|
39
|
+
}];
|
|
40
|
+
}
|
|
41
|
+
resolve:^(BOOL success) {
|
|
42
|
+
resolve(@(success));
|
|
43
|
+
}
|
|
44
|
+
reject:^(NSString *code, NSString *message, NSError *error) {
|
|
45
|
+
reject(code, message, error);
|
|
46
|
+
}];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
- (void)endAccessingResources:(NSArray<NSString *> *)tags {
|
|
50
|
+
[_impl endAccessingResources:tags];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (void)getResourcePath:(NSString *)resourceName
|
|
54
|
+
ofType:(NSString *)ofType
|
|
55
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
56
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
57
|
+
[_impl getResourcePath:resourceName
|
|
58
|
+
ofType:ofType
|
|
59
|
+
resolve:^(NSString *path) {
|
|
60
|
+
resolve(path);
|
|
61
|
+
}
|
|
62
|
+
reject:^(NSString *code, NSString *message, NSError *error) {
|
|
63
|
+
reject(code, message, error);
|
|
64
|
+
}];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
- (void)setPreservationPriority:(double)priority
|
|
68
|
+
tags:(NSArray<NSString *> *)tags {
|
|
69
|
+
[_impl setPreservationPriority:priority forTags:tags];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
73
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
74
|
+
{
|
|
75
|
+
return std::make_shared<facebook::react::NativeDynamicResourceLoaderSpecJSI>(params);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
+ (NSString *)moduleName
|
|
79
|
+
{
|
|
80
|
+
return @"DynamicResourceLoader";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
@objcMembers
|
|
4
|
+
public class DynamicResourceLoaderImpl: NSObject {
|
|
5
|
+
private var activeRequests: [String: NSBundleResourceRequest] = [:]
|
|
6
|
+
private var progressObservations: [String: NSKeyValueObservation] = [:]
|
|
7
|
+
|
|
8
|
+
private func requestKey(for tags: [String]) -> String {
|
|
9
|
+
return tags.sorted().joined(separator: ",")
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public func checkResourcesAvailable(
|
|
13
|
+
_ tags: [String],
|
|
14
|
+
resolve: @escaping (Bool) -> Void,
|
|
15
|
+
reject: @escaping (String, String, NSError) -> Void
|
|
16
|
+
) {
|
|
17
|
+
let tagSet = Set(tags)
|
|
18
|
+
let request = NSBundleResourceRequest(tags: tagSet)
|
|
19
|
+
|
|
20
|
+
request.conditionallyBeginAccessingResources { available in
|
|
21
|
+
if available {
|
|
22
|
+
request.endAccessingResources()
|
|
23
|
+
}
|
|
24
|
+
resolve(available)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public func downloadResources(
|
|
29
|
+
_ tags: [String],
|
|
30
|
+
progress progressCallback: ((String, Int64, Int64, Double, String) -> Void)?,
|
|
31
|
+
resolve: @escaping (Bool) -> Void,
|
|
32
|
+
reject: @escaping (String, String, NSError) -> Void
|
|
33
|
+
) {
|
|
34
|
+
let key = requestKey(for: tags)
|
|
35
|
+
let tagSet = Set(tags)
|
|
36
|
+
let request = NSBundleResourceRequest(tags: tagSet)
|
|
37
|
+
|
|
38
|
+
request.conditionallyBeginAccessingResources { [weak self] available in
|
|
39
|
+
if available {
|
|
40
|
+
self?.activeRequests[key] = request
|
|
41
|
+
for tag in tags {
|
|
42
|
+
progressCallback?(tag, 1, 1, 1.0, "completed")
|
|
43
|
+
}
|
|
44
|
+
resolve(true)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if let progressCallback = progressCallback {
|
|
49
|
+
let observation = request.progress.observe(\.fractionCompleted) { progress, _ in
|
|
50
|
+
for tag in tags {
|
|
51
|
+
progressCallback(
|
|
52
|
+
tag,
|
|
53
|
+
progress.completedUnitCount,
|
|
54
|
+
progress.totalUnitCount,
|
|
55
|
+
progress.fractionCompleted,
|
|
56
|
+
"downloading"
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
self?.progressObservations[key] = observation
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
request.beginAccessingResources { [weak self] error in
|
|
64
|
+
self?.progressObservations.removeValue(forKey: key)
|
|
65
|
+
if let error = error as NSError? {
|
|
66
|
+
for tag in tags {
|
|
67
|
+
progressCallback?(tag, 0, 0, 0.0, "failed")
|
|
68
|
+
}
|
|
69
|
+
reject("DOWNLOAD_FAILED", error.localizedDescription, error)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
self?.activeRequests[key] = request
|
|
73
|
+
for tag in tags {
|
|
74
|
+
progressCallback?(tag, 1, 1, 1.0, "completed")
|
|
75
|
+
}
|
|
76
|
+
resolve(true)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public func endAccessingResources(_ tags: [String]) {
|
|
82
|
+
let key = requestKey(for: tags)
|
|
83
|
+
guard let request = activeRequests[key] else { return }
|
|
84
|
+
request.endAccessingResources()
|
|
85
|
+
activeRequests.removeValue(forKey: key)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public func getResourcePath(
|
|
89
|
+
_ resourceName: String,
|
|
90
|
+
ofType type: String,
|
|
91
|
+
resolve: @escaping (String) -> Void,
|
|
92
|
+
reject: @escaping (String, String, NSError) -> Void
|
|
93
|
+
) {
|
|
94
|
+
if let path = Bundle.main.path(forResource: resourceName, ofType: type) {
|
|
95
|
+
resolve(path)
|
|
96
|
+
} else {
|
|
97
|
+
let error = NSError(
|
|
98
|
+
domain: "DynamicResourceLoader",
|
|
99
|
+
code: 1,
|
|
100
|
+
userInfo: [NSLocalizedDescriptionKey: "Resource '\(resourceName).\(type)' not found"]
|
|
101
|
+
)
|
|
102
|
+
reject("RESOURCE_NOT_FOUND", error.localizedDescription, error)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public func setPreservationPriority(_ priority: Double, forTags tags: [String]) {
|
|
107
|
+
let tagSet = Set(tags)
|
|
108
|
+
Bundle.main.setPreservationPriority(priority, forTags: tagSet)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeDynamicResourceLoader.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAoBpE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,uBAAuB,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["codegen-types.d.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import DynamicResourceLoader from "./NativeDynamicResourceLoader.js";
|
|
4
|
+
export function checkResourcesAvailable(tags) {
|
|
5
|
+
return DynamicResourceLoader.checkResourcesAvailable(tags);
|
|
6
|
+
}
|
|
7
|
+
export function downloadResources(tags) {
|
|
8
|
+
return DynamicResourceLoader.downloadResources(tags);
|
|
9
|
+
}
|
|
10
|
+
export function endAccessingResources(tags) {
|
|
11
|
+
DynamicResourceLoader.endAccessingResources(tags);
|
|
12
|
+
}
|
|
13
|
+
export function getResourcePath(resourceName, ofType) {
|
|
14
|
+
return DynamicResourceLoader.getResourcePath(resourceName, ofType);
|
|
15
|
+
}
|
|
16
|
+
export function setPreservationPriority(priority, tags) {
|
|
17
|
+
DynamicResourceLoader.setPreservationPriority(priority, tags);
|
|
18
|
+
}
|
|
19
|
+
export function onDownloadProgress(handler) {
|
|
20
|
+
return DynamicResourceLoader.onDownloadProgress(handler);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["DynamicResourceLoader","checkResourcesAvailable","tags","downloadResources","endAccessingResources","getResourcePath","resourceName","ofType","setPreservationPriority","priority","onDownloadProgress","handler"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,qBAAqB,MAAM,kCAA+B;AAKjE,OAAO,SAASC,uBAAuBA,CACrCC,IAA2B,EACT;EAClB,OAAOF,qBAAqB,CAACC,uBAAuB,CAACC,IAAI,CAAC;AAC5D;AAEA,OAAO,SAASC,iBAAiBA,CAC/BD,IAA2B,EACT;EAClB,OAAOF,qBAAqB,CAACG,iBAAiB,CAACD,IAAI,CAAC;AACtD;AAEA,OAAO,SAASE,qBAAqBA,CAACF,IAA2B,EAAQ;EACvEF,qBAAqB,CAACI,qBAAqB,CAACF,IAAI,CAAC;AACnD;AAEA,OAAO,SAASG,eAAeA,CAC7BC,YAAoB,EACpBC,MAAc,EACG;EACjB,OAAOP,qBAAqB,CAACK,eAAe,CAACC,YAAY,EAAEC,MAAM,CAAC;AACpE;AAEA,OAAO,SAASC,uBAAuBA,CACrCC,QAAgB,EAChBP,IAA2B,EACrB;EACNF,qBAAqB,CAACQ,uBAAuB,CAACC,QAAQ,EAAEP,IAAI,CAAC;AAC/D;AAEA,OAAO,SAASQ,kBAAkBA,CAChCC,OAA+C,EAC/C;EACA,OAAOX,qBAAqB,CAACU,kBAAkB,CAACC,OAAO,CAAC;AAC1D","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type TurboModule } from 'react-native';
|
|
2
|
+
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';
|
|
3
|
+
export type DownloadProgressEvent = {
|
|
4
|
+
tag: string;
|
|
5
|
+
bytesDownloaded: number;
|
|
6
|
+
totalBytes: number;
|
|
7
|
+
fractionCompleted: number;
|
|
8
|
+
status: string;
|
|
9
|
+
};
|
|
10
|
+
export interface Spec extends TurboModule {
|
|
11
|
+
checkResourcesAvailable(tags: ReadonlyArray<string>): Promise<boolean>;
|
|
12
|
+
downloadResources(tags: ReadonlyArray<string>): Promise<boolean>;
|
|
13
|
+
endAccessingResources(tags: ReadonlyArray<string>): void;
|
|
14
|
+
getResourcePath(resourceName: string, ofType: string): Promise<string>;
|
|
15
|
+
setPreservationPriority(priority: number, tags: ReadonlyArray<string>): void;
|
|
16
|
+
readonly onDownloadProgress: EventEmitter<DownloadProgressEvent>;
|
|
17
|
+
}
|
|
18
|
+
declare const _default: Spec;
|
|
19
|
+
export default _default;
|
|
20
|
+
//# sourceMappingURL=NativeDynamicResourceLoader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeDynamicResourceLoader.d.ts","sourceRoot":"","sources":["../../../src/NativeDynamicResourceLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,uBAAuB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvE,iBAAiB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,qBAAqB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACzD,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC7E,QAAQ,CAAC,kBAAkB,EAAE,YAAY,CAAC,qBAAqB,CAAC,CAAC;CAClE;;AAED,wBAA+E"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DownloadProgressEvent } from './NativeDynamicResourceLoader';
|
|
2
|
+
export type { DownloadProgressEvent };
|
|
3
|
+
export declare function checkResourcesAvailable(tags: ReadonlyArray<string>): Promise<boolean>;
|
|
4
|
+
export declare function downloadResources(tags: ReadonlyArray<string>): Promise<boolean>;
|
|
5
|
+
export declare function endAccessingResources(tags: ReadonlyArray<string>): void;
|
|
6
|
+
export declare function getResourcePath(resourceName: string, ofType: string): Promise<string>;
|
|
7
|
+
export declare function setPreservationPriority(priority: number, tags: ReadonlyArray<string>): void;
|
|
8
|
+
export declare function onDownloadProgress(handler: (event: DownloadProgressEvent) => void): import("react-native").EventSubscription;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,YAAY,EAAE,qBAAqB,EAAE,CAAC;AAEtC,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,IAAI,CAEvE;AAED,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAC1B,IAAI,CAEN;AAED,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,4CAGhD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-dynamic-resource-loader",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Load dynamic files from the App and Play stores",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"source": "./src/index.tsx",
|
|
10
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
11
|
+
"default": "./lib/module/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"ios",
|
|
20
|
+
"cpp",
|
|
21
|
+
"*.podspec",
|
|
22
|
+
"react-native.config.js",
|
|
23
|
+
"!ios/build",
|
|
24
|
+
"!android/build",
|
|
25
|
+
"!android/gradle",
|
|
26
|
+
"!android/gradlew",
|
|
27
|
+
"!android/gradlew.bat",
|
|
28
|
+
"!android/local.properties",
|
|
29
|
+
"!**/__tests__",
|
|
30
|
+
"!**/__fixtures__",
|
|
31
|
+
"!**/__mocks__",
|
|
32
|
+
"!**/.*"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"example": "yarn workspace react-native-dynamic-resource-loader-example",
|
|
36
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
37
|
+
"prepare": "bob build",
|
|
38
|
+
"typecheck": "tsc",
|
|
39
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
40
|
+
"test": "jest",
|
|
41
|
+
"release": "release-it --only-version"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react-native",
|
|
45
|
+
"ios",
|
|
46
|
+
"android"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/Rob117//react-native-dynamic-resource-loader.git"
|
|
51
|
+
},
|
|
52
|
+
"author": "Robert Sherling <5183016+Rob117@users.noreply.github.com> (https://github.com/Rob117/)",
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/Rob117//react-native-dynamic-resource-loader/issues"
|
|
56
|
+
},
|
|
57
|
+
"homepage": "https://github.com/Rob117//react-native-dynamic-resource-loader#readme",
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"registry": "https://registry.npmjs.org/"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
63
|
+
"@eslint/compat": "^1.3.2",
|
|
64
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
65
|
+
"@eslint/js": "^9.35.0",
|
|
66
|
+
"@react-native/babel-preset": "0.83.0",
|
|
67
|
+
"@react-native/eslint-config": "0.83.0",
|
|
68
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
69
|
+
"@types/jest": "^29.5.14",
|
|
70
|
+
"@types/react": "^19.2.0",
|
|
71
|
+
"commitlint": "^19.8.1",
|
|
72
|
+
"del-cli": "^6.0.0",
|
|
73
|
+
"eslint": "^9.35.0",
|
|
74
|
+
"eslint-config-prettier": "^10.1.8",
|
|
75
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
76
|
+
"jest": "^29.7.0",
|
|
77
|
+
"lefthook": "^2.0.3",
|
|
78
|
+
"prettier": "^2.8.8",
|
|
79
|
+
"react": "19.2.0",
|
|
80
|
+
"react-native": "0.83.0",
|
|
81
|
+
"react-native-builder-bob": "^0.40.18",
|
|
82
|
+
"release-it": "^19.0.4",
|
|
83
|
+
"turbo": "^2.5.6",
|
|
84
|
+
"typescript": "^5.9.2"
|
|
85
|
+
},
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"react": "*",
|
|
88
|
+
"react-native": "*"
|
|
89
|
+
},
|
|
90
|
+
"workspaces": [
|
|
91
|
+
"example"
|
|
92
|
+
],
|
|
93
|
+
"packageManager": "yarn@4.11.0",
|
|
94
|
+
"react-native-builder-bob": {
|
|
95
|
+
"source": "src",
|
|
96
|
+
"output": "lib",
|
|
97
|
+
"targets": [
|
|
98
|
+
[
|
|
99
|
+
"module",
|
|
100
|
+
{
|
|
101
|
+
"esm": true
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
[
|
|
105
|
+
"typescript",
|
|
106
|
+
{
|
|
107
|
+
"project": "tsconfig.build.json"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"codegenConfig": {
|
|
113
|
+
"name": "DynamicResourceLoaderSpec",
|
|
114
|
+
"type": "modules",
|
|
115
|
+
"jsSrcsDir": "src",
|
|
116
|
+
"android": {
|
|
117
|
+
"javaPackageName": "com.dynamicresourceloader"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"prettier": {
|
|
121
|
+
"quoteProps": "consistent",
|
|
122
|
+
"singleQuote": true,
|
|
123
|
+
"tabWidth": 2,
|
|
124
|
+
"trailingComma": "es5",
|
|
125
|
+
"useTabs": false
|
|
126
|
+
},
|
|
127
|
+
"jest": {
|
|
128
|
+
"preset": "react-native",
|
|
129
|
+
"modulePathIgnorePatterns": [
|
|
130
|
+
"<rootDir>/example/node_modules",
|
|
131
|
+
"<rootDir>/lib/"
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"commitlint": {
|
|
135
|
+
"extends": [
|
|
136
|
+
"@commitlint/config-conventional"
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
"release-it": {
|
|
140
|
+
"git": {
|
|
141
|
+
"commitMessage": "chore: release ${version}",
|
|
142
|
+
"tagName": "v${version}"
|
|
143
|
+
},
|
|
144
|
+
"npm": {
|
|
145
|
+
"publish": true
|
|
146
|
+
},
|
|
147
|
+
"github": {
|
|
148
|
+
"release": true
|
|
149
|
+
},
|
|
150
|
+
"plugins": {
|
|
151
|
+
"@release-it/conventional-changelog": {
|
|
152
|
+
"preset": {
|
|
153
|
+
"name": "angular"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"create-react-native-library": {
|
|
159
|
+
"type": "turbo-module",
|
|
160
|
+
"languages": "kotlin-objc",
|
|
161
|
+
"tools": [
|
|
162
|
+
"eslint",
|
|
163
|
+
"jest",
|
|
164
|
+
"lefthook",
|
|
165
|
+
"release-it"
|
|
166
|
+
],
|
|
167
|
+
"version": "0.57.1"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
2
|
+
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';
|
|
3
|
+
|
|
4
|
+
export type DownloadProgressEvent = {
|
|
5
|
+
tag: string;
|
|
6
|
+
bytesDownloaded: number;
|
|
7
|
+
totalBytes: number;
|
|
8
|
+
fractionCompleted: number;
|
|
9
|
+
status: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export interface Spec extends TurboModule {
|
|
13
|
+
checkResourcesAvailable(tags: ReadonlyArray<string>): Promise<boolean>;
|
|
14
|
+
downloadResources(tags: ReadonlyArray<string>): Promise<boolean>;
|
|
15
|
+
endAccessingResources(tags: ReadonlyArray<string>): void;
|
|
16
|
+
getResourcePath(resourceName: string, ofType: string): Promise<string>;
|
|
17
|
+
setPreservationPriority(priority: number, tags: ReadonlyArray<string>): void;
|
|
18
|
+
readonly onDownloadProgress: EventEmitter<DownloadProgressEvent>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('DynamicResourceLoader');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Type declarations for react-native codegen types that are blocked
|
|
2
|
+
// by the react-native-strict-api custom condition in tsconfig.json.
|
|
3
|
+
declare module 'react-native/Libraries/Types/CodegenTypes' {
|
|
4
|
+
import type { EventSubscription } from 'react-native';
|
|
5
|
+
|
|
6
|
+
export type EventEmitter<T> = (
|
|
7
|
+
handler: (event: T) => void | Promise<void>
|
|
8
|
+
) => EventSubscription;
|
|
9
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import DynamicResourceLoader from './NativeDynamicResourceLoader';
|
|
2
|
+
import type { DownloadProgressEvent } from './NativeDynamicResourceLoader';
|
|
3
|
+
|
|
4
|
+
export type { DownloadProgressEvent };
|
|
5
|
+
|
|
6
|
+
export function checkResourcesAvailable(
|
|
7
|
+
tags: ReadonlyArray<string>
|
|
8
|
+
): Promise<boolean> {
|
|
9
|
+
return DynamicResourceLoader.checkResourcesAvailable(tags);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function downloadResources(
|
|
13
|
+
tags: ReadonlyArray<string>
|
|
14
|
+
): Promise<boolean> {
|
|
15
|
+
return DynamicResourceLoader.downloadResources(tags);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function endAccessingResources(tags: ReadonlyArray<string>): void {
|
|
19
|
+
DynamicResourceLoader.endAccessingResources(tags);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getResourcePath(
|
|
23
|
+
resourceName: string,
|
|
24
|
+
ofType: string
|
|
25
|
+
): Promise<string> {
|
|
26
|
+
return DynamicResourceLoader.getResourcePath(resourceName, ofType);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function setPreservationPriority(
|
|
30
|
+
priority: number,
|
|
31
|
+
tags: ReadonlyArray<string>
|
|
32
|
+
): void {
|
|
33
|
+
DynamicResourceLoader.setPreservationPriority(priority, tags);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function onDownloadProgress(
|
|
37
|
+
handler: (event: DownloadProgressEvent) => void
|
|
38
|
+
) {
|
|
39
|
+
return DynamicResourceLoader.onDownloadProgress(handler);
|
|
40
|
+
}
|