fixed error with progressInterval. corrected types & ts config. removed percent from progress event. changed params to object in all events. updated readme. made onProgress starting on app start

This commit is contained in:
Kesha Antonov
2023-12-24 22:11:13 +03:00
parent bd1e4adfc6
commit 168d35a9a5
13 changed files with 1546 additions and 1624 deletions

495
.eslintrc
View File

@@ -1,495 +0,0 @@
/*
* Eko's ESLint JSON Config file (eslint allows JavaScript-style comments in JSON config files).
*/
{
// Enable the ESLint recommended rules as a starting point.
// These are rules that report common problems, see https://eslint.org/docs/rules/
"extends": "eslint:recommended",
"parser": "babel-eslint",
// Specify the envs (an environment defines global variables that are predefined).
// See https://eslint.org/docs/user-guide/configuring#specifying-environments
"env": {
// Browser global variables.
"browser": false,
// Node.js global variables and Node.js scoping.
"node": true,
// CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack).
"commonjs": true,
// Globals common to both Node.js and Browser.
"shared-node-browser": true,
// enable all ECMAScript 6 features except for modules (this automatically sets the ecmaVersion parser option to 6).
"es6": true,
// web workers global variables.
"worker": true
},
// https://eslint.org/docs/rules
"rules": {
///////////////////////////////////////////////////////////////////////////////////
// Possible Errors https://eslint.org/docs/rules/#possible-errors
///////////////////////////////////////////////////////////////////////////////////
// https://eslint.org/docs/rules/no-console
"no-console": ["error", { "allow": ["error"] }],
// https://eslint.org/docs/rules/valid-jsdoc
// TODO - valid-jsdoc
///////////////////////////////////////////////////////////////////////////////////
// Best Practices https://eslint.org/docs/rules/#best-practices
///////////////////////////////////////////////////////////////////////////////////
// https://eslint.org/docs/rules/accessor-pairs
"accessor-pairs": "error",
// https://eslint.org/docs/rules/array-callback-return
"array-callback-return": ["error", { "allowImplicit": true }],
// https://eslint.org/docs/rules/block-scoped-var
"block-scoped-var": "error",
// https://eslint.org/docs/rules/curly
"curly": ["error", "all"],
// https://eslint.org/docs/rules/default-case
"default-case": "error",
// https://eslint.org/docs/rules/dot-location
"dot-location": ["error", "property"],
// https://eslint.org/docs/rules/dot-notation
"dot-notation": "error",
// https://eslint.org/docs/rules/eqeqeq
"eqeqeq": ["error", "always"],
// https://eslint.org/docs/rules/no-alert
"no-alert": "error",
// https://eslint.org/docs/rules/no-caller
"no-caller": "error",
// https://eslint.org/docs/rules/no-else-return
"no-else-return": ["error", { "allowElseIf": false }],
// https://eslint.org/docs/rules/no-eq-null
"no-eq-null": "error",
// https://eslint.org/docs/rules/no-eval
"no-eval": "error",
// https://eslint.org/docs/rules/no-extra-bind
"no-extra-bind": "error",
// https://eslint.org/docs/rules/no-fallthrough
"no-fallthrough": ["error", { "commentPattern": "fall-?thr(ough|u)" }],
// https://eslint.org/docs/rules/no-floating-decimal
"no-floating-decimal": "error",
// https://eslint.org/docs/rules/no-implicit-coercion
"no-implicit-coercion": ["error", { "allow": ["!!"] }],
// https://eslint.org/docs/rules/no-implicit-globals
"no-implicit-globals": "error",
// https://eslint.org/docs/rules/no-implied-eval
"no-implied-eval": "error",
// https://eslint.org/docs/rules/no-invalid-this
"no-invalid-this": "error",
// https://eslint.org/docs/rules/no-iterator
"no-iterator": "error",
// https://eslint.org/docs/rules/no-labels
"no-labels": "error",
// https://eslint.org/docs/rules/no-lone-blocks
"no-lone-blocks": "error",
// https://eslint.org/docs/rules/no-loop-func
"no-loop-func": "error",
// https://eslint.org/docs/rules/no-magic-numbers
"no-magic-numbers": ["error", {
"ignore": [
0, 1, -1,
10, 100, 1000, 10000, 100000, 1000000,
1024, 8, 2,
24, 60
]
}],
// https://eslint.org/docs/rules/no-multi-spaces
"no-multi-spaces": ["error", { "ignoreEOLComments": true, "exceptions": {
"Property": true,
"VariableDeclarator": true,
"ImportDeclaration": true
}}],
// https://eslint.org/docs/rules/no-multi-str
"no-multi-str": "error",
// https://eslint.org/docs/rules/no-new
"no-new": "error",
// https://eslint.org/docs/rules/no-new-wrappers
"no-new-wrappers": "error",
// https://eslint.org/docs/rules/no-octal-escape
"no-octal-escape": "error",
// https://eslint.org/docs/rules/no-proto
"no-proto": "error",
// https://eslint.org/docs/rules/no-return-assign
"no-return-assign": "error",
// https://eslint.org/docs/rules/no-return-await
"no-return-await": "error",
// https://eslint.org/docs/rules/no-script-url
"no-script-url": "error",
// https://eslint.org/docs/rules/no-self-compare
"no-self-compare": "error",
// https://eslint.org/docs/rules/no-sequences
"no-sequences": "error",
// https://eslint.org/docs/rules/no-throw-literal
"no-throw-literal": "error",
// https://eslint.org/docs/rules/no-unmodified-loop-condition
"no-unmodified-loop-condition": "error",
// https://eslint.org/docs/rules/no-unused-expressions
"no-unused-expressions": "error",
// https://eslint.org/docs/rules/no-useless-call
"no-useless-call": "error",
// https://eslint.org/docs/rules/no-useless-concat
"no-useless-concat": "error",
// https://eslint.org/docs/rules/no-useless-return
"no-useless-return": "error",
// https://eslint.org/docs/rules/no-void
"no-void": "error",
// https://eslint.org/docs/rules/no-with
"no-with": "error",
// https://eslint.org/docs/rules/prefer-promise-reject-errors
"prefer-promise-reject-errors": ["error", { "allowEmptyReject": true }],
// https://eslint.org/docs/rules/radix
"radix": ["error", "always"],
// https://eslint.org/docs/rules/require-await
"require-await": "error",
// https://eslint.org/docs/rules/wrap-iife
"wrap-iife": ["error", "any"],
///////////////////////////////////////////////////////////////////////////////////
// Strict Mode https://eslint.org/docs/rules/#strict-mode
///////////////////////////////////////////////////////////////////////////////////
// https://eslint.org/docs/rules/strict
"strict": ["error", "safe"],
///////////////////////////////////////////////////////////////////////////////////
// Variables https://eslint.org/docs/rules/#variables
///////////////////////////////////////////////////////////////////////////////////
// https://eslint.org/docs/rules/no-shadow
"no-shadow": ["error", { "builtinGlobals": true, "hoist": "all", "allow": [] }],
// https://eslint.org/docs/rules/no-shadow-restricted-names
"no-shadow-restricted-names": "error",
// https://eslint.org/docs/rules/no-undef-init
"no-undef-init": "error",
// https://eslint.org/docs/rules/no-use-before-define
"no-use-before-define": ["error", { "functions": true, "classes": true, "variables": true }],
///////////////////////////////////////////////////////////////////////////////////
// Node.js and CommonJS https://eslint.org/docs/rules/#nodejs-and-commonjs
///////////////////////////////////////////////////////////////////////////////////
// https://eslint.org/docs/rules/global-require
"global-require": "error",
// https://eslint.org/docs/rules/no-buffer-constructor
"no-buffer-constructor": "error",
// https://eslint.org/docs/rules/no-new-require
"no-new-require": "error",
// https://eslint.org/docs/rules/no-path-concat
"no-path-concat": "error",
///////////////////////////////////////////////////////////////////////////////////
// Stylistic Issues https://eslint.org/docs/rules/#stylistic-issues
///////////////////////////////////////////////////////////////////////////////////
// https://eslint.org/docs/rules/array-bracket-newline
"array-bracket-newline": ["warn", { "multiline": true }],
// https://eslint.org/docs/rules/array-bracket-spacing
"array-bracket-spacing": ["warn", "never"],
// https://eslint.org/docs/rules/block-spacing
"block-spacing": ["warn", "always"],
// https://eslint.org/docs/rules/brace-style
"brace-style": ["warn", "1tbs", { "allowSingleLine": true }],
// https://eslint.org/docs/rules/camelcase
"camelcase": ["warn", { "properties": "always" }],
// https://eslint.org/docs/rules/capitalized-comments
"capitalized-comments": ["warn", "always", { "ignoreInlineComments": true, "ignoreConsecutiveComments": true }],
// https://eslint.org/docs/rules/comma-dangle
"comma-dangle": ["warn", "only-multiline"],
// https://eslint.org/docs/rules/comma-spacing
"comma-spacing": ["warn", { "before": false, "after": true }],
// https://eslint.org/docs/rules/comma-style
"comma-style": ["warn", "last"],
// https://eslint.org/docs/rules/computed-property-spacing
"computed-property-spacing": ["warn", "never"],
// TODO - discuss with TEAM!
// https://eslint.org/docs/rules/consistent-this
"consistent-this": ["warn", "that", "_this", "self"],
// https://eslint.org/docs/rules/eol-last
"eol-last": ["warn", "always"],
// https://eslint.org/docs/rules/func-call-spacing
"func-call-spacing": ["warn", "never"],
// https://eslint.org/docs/rules/func-name-matching
"func-name-matching": ["warn", "always"],
// https://eslint.org/docs/rules/implicit-arrow-linebreak
"implicit-arrow-linebreak": ["warn", "beside"],
// https://eslint.org/docs/rules/indent
"indent": ["warn", 4, {
"SwitchCase": 1,
"FunctionDeclaration": {
"parameters": 2,
"body": 1
},
"FunctionExpression": {
"parameters": 2,
"body": 1
},
"CallExpression": {
"arguments": 1
},
"ArrayExpression": 1,
"ObjectExpression": 1,
"ImportDeclaration": 1,
"ignoredNodes": [
"ConditionalExpression"
]
}],
// https://eslint.org/docs/rules/jsx-quotes
"jsx-quotes": ["warn", "prefer-double"],
// https://eslint.org/docs/rules/key-spacing
"key-spacing": [
"warn",
{
"singleLine": {
"beforeColon": false,
"afterColon": true,
"mode": "strict"
},
"multiLine": {
"beforeColon": false,
"afterColon": true,
"mode": "minimum"
}
}
],
// https://eslint.org/docs/rules/keyword-spacing
"keyword-spacing": ["warn", { "before": true, "after": true }],
// https://eslint.org/docs/rules/linebreak-style
"linebreak-style": ["warn", "unix"],
// https://eslint.org/docs/rules/lines-around-comment
"lines-around-comment": ["warn", {
"beforeBlockComment": true,
"afterBlockComment": false,
"beforeLineComment": true,
"afterLineComment": false,
"allowBlockStart": true,
"allowBlockEnd": false,
"allowClassStart": true,
"allowClassEnd": false,
"allowObjectStart": true,
"allowObjectEnd": false,
"allowArrayStart": true,
"allowArrayEnd": false
}],
// https://eslint.org/docs/rules/max-len
"max-len": ["warn", {
"code": 120,
"tabWidth": 4,
"ignoreUrls": true,
"ignoreComments": true
}],
// https://eslint.org/docs/rules/max-lines
"max-lines": ["warn", {
"max": 500,
"skipBlankLines": true,
"skipComments": true
}],
// https://eslint.org/docs/rules/max-statements
"max-statements": ["warn", 30],
// https://eslint.org/docs/rules/max-statements-per-line
"max-statements-per-line": ["warn", {
"max": 1
}],
// https://eslint.org/docs/rules/multiline-ternary
"multiline-ternary": ["warn", "always-multiline"],
// https://eslint.org/docs/rules/new-cap
"new-cap": ["warn", { "newIsCap": true, "capIsNew": true, "properties": true }],
// https://eslint.org/docs/rules/new-parens
"new-parens": "warn",
// https://eslint.org/docs/rules/newline-per-chained-call
"newline-per-chained-call": ["warn", { "ignoreChainWithDepth": 2 }],
// https://eslint.org/docs/rules/no-array-constructor
"no-array-constructor": "warn",
// https://eslint.org/docs/rules/no-bitwise
"no-bitwise": "warn",
// https://eslint.org/docs/rules/no-lonely-if
"no-lonely-if": "warn",
// https://eslint.org/docs/rules/no-mixed-operators
"no-mixed-operators": "warn",
// https://eslint.org/docs/rules/no-multi-assign
"no-multi-assign": "warn",
// https://eslint.org/docs/rules/no-multiple-empty-lines
"no-multiple-empty-lines": ["warn", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
// https://eslint.org/docs/rules/no-negated-condition
"no-negated-condition": "warn",
// https://eslint.org/docs/rules/no-new-object
"no-new-object": "warn",
// https://eslint.org/docs/rules/no-tabs
"no-tabs": "warn",
// https://eslint.org/docs/rules/no-trailing-spaces
"no-trailing-spaces": ["warn", { "skipBlankLines": false, "ignoreComments": false }],
// https://eslint.org/docs/rules/no-unneeded-ternary
"no-unneeded-ternary": ["warn", { "defaultAssignment": false }],
// https://eslint.org/docs/rules/no-whitespace-before-property
"no-whitespace-before-property": "warn",
// https://eslint.org/docs/rules/object-curly-newline
"object-curly-newline": ["warn", { "consistent": true }],
// https://eslint.org/docs/rules/object-curly-spacing
"object-curly-spacing": ["warn", "always"],
// https://eslint.org/docs/rules/one-var
"one-var": ["error", "never"],
// https://eslint.org/docs/rules/operator-linebreak
"operator-linebreak": ["warn", "after"],
// https://eslint.org/docs/rules/padded-blocks
"padded-blocks": ["warn", "never"],
// https://eslint.org/docs/rules/quote-props
"quote-props": ["warn", "as-needed"],
// https://eslint.org/docs/rules/quotes
"quotes": ["warn", "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
// https://eslint.org/docs/rules/semi
"semi": ["warn", "always"],
// https://eslint.org/docs/rules/semi-spacing
"semi-spacing": ["warn", { "before": false, "after": true }],
// https://eslint.org/docs/rules/semi-style
"semi-style": ["warn", "last"],
// https://eslint.org/docs/rules/space-before-blocks
"space-before-blocks": ["warn", "always"],
// https://eslint.org/docs/rules/space-before-function-paren
"space-before-function-paren": ["warn", "never"],
// https://eslint.org/docs/rules/space-in-parens
"space-in-parens": ["warn", "never"],
// https://eslint.org/docs/rules/space-infix-ops
"space-infix-ops": "warn",
// https://eslint.org/docs/rules/space-unary-ops
"space-unary-ops": ["warn", { "words": true, "nonwords": false }],
// https://eslint.org/docs/rules/spaced-comment
"spaced-comment": ["warn", "always", { "exceptions": ["-", "/", "=", "*"] }],
// https://eslint.org/docs/rules/switch-colon-spacing
"switch-colon-spacing": ["warn", { "after": true, "before": false }],
// https://eslint.org/docs/rules/unicode-bom
"unicode-bom": ["error", "never"],
///////////////////////////////////////////////////////////////////////////////////
// ECMAScript 6 https://eslint.org/docs/rules/#ecmascript-6
///////////////////////////////////////////////////////////////////////////////////
// https://eslint.org/docs/rules/arrow-spacing
"arrow-spacing": ["warn", { "before": true, "after": true }]
// TODO - more ES6 rules
},
"parserOptions": {
"sourceType": "module"
}
}

View File

@@ -20,6 +20,8 @@ module.exports = {
plugins: [
'react',
'react-hooks',
'jest',
'@typescript-eslint',
],
settings: {
react: {
@@ -63,9 +65,15 @@ module.exports = {
'no-class-assign': 'off',
'no-useless-escape': 'off',
curly: [2, 'multi', 'consistent'],
'react/prop-types': 'off', // TODO: TURN ON AND FIX ALL WARNINGS
'react/display-name': 'off',
'react-hooks/exhaustive-deps': ['warn', {
}],
},
overrides: [{
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
extends: ['plugin:@typescript-eslint/recommended'],
}],
globals: {
describe: 'readonly',
test: 'readonly',

View File

@@ -111,10 +111,10 @@ let task = download({
metadata: {}
}).begin(({ expectedBytes, headers }) => {
console.log(`Going to download ${expectedBytes} bytes!`)
}).progress(percent => {
console.log(`Downloaded: ${percent * 100}%`)
}).done(() => {
console.log('Download is done!')
}).progress(({ bytesDownloaded, bytesTotal }) => {
console.log(`Downloaded: ${bytesDownloaded / bytesTotal * 100}%`)
}).done(({ bytesDownloaded, bytesTotal }) => {
console.log('Download is done!', { bytesDownloaded, bytesTotal })
// PROCESS YOUR STUFF
@@ -148,10 +148,10 @@ import RNBackgroundDownloader from '@kesha-antonov/react-native-background-downl
let lostTasks = await RNBackgroundDownloader.checkForExistingDownloads()
for (let task of lostTasks) {
console.log(`Task ${task.id} was found!`)
task.progress(percent => {
console.log(`Downloaded: ${percent * 100}%`)
}).done(() => {
console.log('Download is done!')
task.progress(({ bytesDownloaded, bytesTotal }) => {
console.log(`Downloaded: ${bytesDownloaded / bytesTotal * 100}%`)
}).done(({ bytesDownloaded, bytesTotal }) => {
console.log('Download is done!', { bytesDownloaded, bytesTotal })
}).error(error => {
console.log('Download canceled due to error: ', error)
})
@@ -182,10 +182,10 @@ let task = RNBackgroundDownloader.download({
}
}).begin(({ expectedBytes, headers }) => {
console.log(`Going to download ${expectedBytes} bytes!`)
}).progress(percent => {
console.log(`Downloaded: ${percent * 100}%`)
}).done(() => {
console.log('Download is done!')
}).progress(({ bytesDownloaded, bytesTotal }) => {
console.log(`Downloaded: ${bytesDownloaded / bytesTotal * 100}%`)
}).done(({ bytesDownloaded, bytesTotal }) => {
console.log('Download is done!', { bytesDownloaded, bytesTotal })
}).error(error => {
console.log('Download canceled due to error: ', error)
})
@@ -243,7 +243,6 @@ A class representing a download task created by `RNBackgroundDownloader.download
| -------------- | ------ | ---------------------------------------------------------------------------------------------------- |
| `id` | String | The id you gave the task when calling `RNBackgroundDownloader.download` |
| `metadata` | Object | The metadata you gave the task when calling `RNBackgroundDownloader.download` |
| `percent` | Number | The current percent of completion of the task between 0 and 1 |
| `bytesDownloaded` | Number | The number of bytes currently written by the task |
| `bytesTotal` | Number | The number bytes expected to be written by this task or more plainly, the file size being downloaded |
@@ -304,12 +303,12 @@ Use these methods to stay updated on what's happening with the task.
All callback methods return the current instance of the `DownloadTask` for chaining.
| Function | Callback Arguments | Info |
| ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Function | Callback Arguments | Info|
| ---------- | --------------------------------- | ---- |
| `begin` | { expectedBytes, headers } | Called when the first byte is received. 💡: this is good place to check if the device has enough storage space for this download |
| `progress` | percent, bytesDownloaded, bytesTotal | Called at max every 1.5s so you can update your progress bar accordingly |
| `done` | | Called when the download is done, the file is at the destination you've set |
| `error` | error | Called when the download stops due to an error |
| `progress` | { bytesDownloaded, bytesTotal } | Called at max every 1.5s so you can update your progress bar accordingly |
| `done` | { bytesDownloaded, bytesTotal } | Called when the download is done, the file is at the destination you've set |
| `error` | { error, errorCode } | Called when the download stops due to an error |
### `pause()`
Pauses the download

View File

@@ -19,13 +19,12 @@ public class OnProgress extends Thread {
private Cursor cursor;
private int lastBytesDownloaded;
private int bytesTotal;
private int progressInterval = 300;
private RNBGDTaskConfig config;
private DeviceEventManagerModule.RCTDeviceEventEmitter ee;
public OnProgress(RNBGDTaskConfig config, long downloadId,
DeviceEventManagerModule.RCTDeviceEventEmitter ee, Downloader downloader, int progressInterval) {
DeviceEventManagerModule.RCTDeviceEventEmitter ee, Downloader downloader) {
this.config = config;
this.downloadId = downloadId;
@@ -34,9 +33,6 @@ public class OnProgress extends Thread {
this.ee = ee;
this.downloader = downloader;
if (progressInterval > 0) {
this.progressInterval = progressInterval;
}
}
private void handleInterrupt() {
@@ -56,7 +52,7 @@ public class OnProgress extends Thread {
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-1. downloadId " + downloadId);
while (downloadId > 0) {
try {
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2. downloadId " + downloadId);
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2. downloadId " + downloadId + " destination " + config.destination);
cursor = downloader.downloadManager.query(query);
@@ -77,7 +73,7 @@ public class OnProgress extends Thread {
Thread.sleep(1000);
} else {
Log.d("RNBackgroundDownloader", "RNBD: OnProgress-2.3. downloadId " + downloadId);
Thread.sleep(progressInterval);
Thread.sleep(config.progressInterval);
}
// get total bytes of the file
@@ -101,7 +97,6 @@ public class OnProgress extends Thread {
params.putString("id", config.id);
params.putInt("bytesDownloaded", (int) lastBytesDownloaded);
params.putInt("bytesTotal", (int) bytesTotal);
params.putDouble("percent", ((double) lastBytesDownloaded / bytesTotal));
HashMap<String, WritableMap> progressReports = new HashMap<>();
progressReports.put(config.id, params);

View File

@@ -8,12 +8,14 @@ public class RNBGDTaskConfig implements Serializable {
public String destination;
public String metadata = "{}";
public boolean reportedBegin;
public int progressInterval;
public RNBGDTaskConfig(String id, String url, String destination, String metadata) {
public RNBGDTaskConfig(String id, String url, String destination, String metadata, int progressInterval) {
this.id = id;
this.url = url;
this.destination = destination;
this.metadata = metadata;
this.reportedBegin = false;
this.progressInterval = progressInterval;
}
}

View File

@@ -121,6 +121,8 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
WritableMap params = Arguments.createMap();
params.putString("id", config.id);
params.putString("location", config.destination);
params.putInt("bytesDownloaded", downloadStatus.getInt("bytesDownloaded"));
params.putInt("bytesTotal", downloadStatus.getInt("bytesTotal"));
ee.emit("downloadComplete", params);
break;
@@ -148,13 +150,20 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
public RNBackgroundDownloaderModule(ReactApplicationContext reactContext) {
super(reactContext);
ReadableMap emptyMap = Arguments.createMap();
this.initDownloader(emptyMap);
loadConfigMap();
downloader = new Downloader(reactContext);
IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
compatRegisterReceiver(reactContext, downloadReceiver, filter, true);
// iterate over downloadIdToConfig
for(Map.Entry<Long, RNBGDTaskConfig> entry : downloadIdToConfig.entrySet()) {
Long downloadId = entry.getKey();
RNBGDTaskConfig config = entry.getValue();
startReportingTasks(downloadId, config);
}
}
// TAKEN FROM
@@ -220,15 +229,6 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
return constants;
}
@ReactMethod
public void initDownloader(ReadableMap options) {
Log.d(getName(), "RNBD: initDownloader");
loadConfigMap();
// TODO. MAYBE REINIT DOWNLOADER
}
private void removeFromMaps(long downloadId) {
Log.d(getName(), "RNBD: removeFromMaps");
@@ -291,7 +291,7 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
return;
}
RNBGDTaskConfig config = new RNBGDTaskConfig(id, url, destination, metadata);
RNBGDTaskConfig config = new RNBGDTaskConfig(id, url, destination, metadata, progressInterval);
final Request request = new Request(Uri.parse(url));
request.setAllowedOverRoaming(isAllowedOverRoaming);
request.setAllowedOverMetered(isAllowedOverMetered);
@@ -333,29 +333,36 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
return;
}
startReportingTasks(downloadId, config);
}
}
private void startReportingTasks(Long downloadId, RNBGDTaskConfig config) {
Log.d(getName(), "RNBD: startReportingTasks-1 downloadId " + downloadId + " config.id " + config.id);
config.reportedBegin = true;
downloadIdToConfig.put(downloadId, config);
saveConfigMap();
Log.d(getName(), "RNBD: download-2 downloadId: " + downloadId);
Log.d(getName(), "RNBD: startReportingTasks-2 downloadId: " + downloadId);
// report begin & progress
//
// overlaped with thread to not block main thread
new Thread(new Runnable() {
public void run() {
try {
Log.d(getName(), "RNBD: download-3 downloadId: " + downloadId);
Log.d(getName(), "RNBD: startReportingTasks-3 downloadId: " + downloadId);
while (true) {
WritableMap downloadStatus = downloader.checkDownloadStatus(downloadId);
int status = downloadStatus.getInt("status");
Log.d(getName(), "RNBD: download-3.1 " + status + " downloadId: " + downloadId);
Log.d(getName(), "RNBD: startReportingTasks-3.1 " + status + " downloadId: " + downloadId);
if (status == DownloadManager.STATUS_RUNNING) {
break;
}
if (status == DownloadManager.STATUS_FAILED || status == DownloadManager.STATUS_SUCCESSFUL) {
Log.d(getName(), "RNBD: download-3.2 " + status + " downloadId: " + downloadId);
Log.d(getName(), "RNBD: startReportingTasks-3.2 " + status + " downloadId: " + downloadId);
Thread.currentThread().interrupt();
}
@@ -368,17 +375,17 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
// wait for onBeginTh to finish
onBeginTh.join();
Log.d(getName(), "RNBD: download-4 downloadId: " + downloadId);
OnProgress onProgressTh = new OnProgress(config, downloadId, ee, downloader, progressInterval);
Log.d(getName(), "RNBD: startReportingTasks-4 downloadId: " + downloadId);
OnProgress onProgressTh = new OnProgress(config, downloadId, ee, downloader);
onProgressThreads.put(config.id, onProgressTh);
onProgressTh.start();
Log.d(getName(), "RNBD: download-5 downloadId: " + downloadId);
Log.d(getName(), "RNBD: startReportingTasks-5 downloadId: " + downloadId);
} catch (Exception e) {
Log.d(getName(), "RNBD: Runnable e: " + e.toString());
}
}
}).start();
Log.d(getName(), "RNBD: download-6 downloadId: " + downloadId);
}
Log.d(getName(), "RNBD: startReportingTasks-6 downloadId: " + downloadId);
}
// TODO: NOT WORKING WITH DownloadManager FOR NOW
@@ -453,8 +460,8 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
if (cursor.moveToFirst()) {
do {
WritableMap result = downloader.getDownloadStatus(cursor);
Long downloadId = Long.parseLong(result.getString("downloadId"));
WritableMap downloadStatus = downloader.getDownloadStatus(cursor);
Long downloadId = Long.parseLong(downloadStatus.getString("downloadId"));
if (downloadIdToConfig.containsKey(downloadId)) {
Log.d(getName(), "RNBD: checkForExistingDownloads-2");
@@ -462,15 +469,9 @@ public class RNBackgroundDownloaderModule extends ReactContextBaseJavaModule {
WritableMap params = Arguments.createMap();
params.putString("id", config.id);
params.putString("metadata", config.metadata);
params.putInt("state", stateMap.get(result.getInt("status")));
int bytesDownloaded = result.getInt("bytesDownloaded");
params.putInt("bytesDownloaded", bytesDownloaded);
int bytesTotal = result.getInt("bytesTotal");
params.putInt("bytesTotal", bytesTotal);
params.putDouble("percent", ((double) bytesDownloaded / bytesTotal));
params.putInt("state", stateMap.get(downloadStatus.getInt("status")));
params.putInt("bytesDownloaded", downloadStatus.getInt("bytesDownloaded"));
params.putInt("bytesTotal", downloadStatus.getInt("bytesTotal"));
foundIds.pushMap(params);

View File

@@ -1,4 +1,87 @@
module.exports = {
root: true,
extends: '@react-native',
};
env: {
es2020: true,
jest: true,
},
parser: '@babel/eslint-parser',
extends: [
'standard',
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 11,
sourceType: 'module',
},
plugins: [
'react',
'react-hooks',
],
settings: {
react: {
version: 'detect',
},
},
rules: {
indent: [
'error',
2, {
SwitchCase: 1,
ignoredNodes: [
'TemplateLiteral',
],
},
],
'template-curly-spacing': 'off',
'linebreak-style': [
'error',
'unix',
],
quotes: [
'error',
'single',
],
semi: [
'error',
'never',
],
'comma-dangle': [
'error',
{
arrays: 'always-multiline',
objects: 'always-multiline',
imports: 'always-multiline',
exports: 'never',
functions: 'never',
},
],
'no-func-assign': 'off',
'no-class-assign': 'off',
'no-useless-escape': 'off',
curly: [2, 'multi', 'consistent'],
'react/prop-types': 'off', // TODO: TURN ON AND FIX ALL WARNINGS
'react/display-name': 'off',
'react-hooks/exhaustive-deps': ['warn', {
additionalHooks: '(useAnimatedStyle|useSharedValue|useAnimatedGestureHandler|useAnimatedScrollHandler|useAnimatedProps|useDerivedValue|useAnimatedRef|useAnimatedReact)',
// useAnimatedReaction
// USE RULE FUNC/FUNC/DEPS
}],
},
globals: {
describe: 'readonly',
test: 'readonly',
jest: 'readonly',
expect: 'readonly',
fetch: 'readonly',
navigator: 'readonly',
__DEV__: 'readonly',
XMLHttpRequest: 'readonly',
FormData: 'readonly',
React$Element: 'readonly',
requestAnimationFrame: 'readonly',
},
}

105
index.d.ts vendored
View File

@@ -11,50 +11,68 @@ export interface DownloadHeaders {
type SetHeaders = (h: DownloadHeaders) => void;
export interface TaskInfoObject {
id: string;
metadata: object | string;
percent?: number;
bytesDownloaded?: number;
bytesTotal?: number;
beginHandler?: Function;
progressHandler?: Function;
doneHandler?: Function;
errorHandler?: Function;
}
export type TaskInfo = TaskInfoObject;
export interface BeginHandlerObject {
expectedBytes: number;
headers: { [key: string]: string };
}
export type BeginHandler = ({
expectedBytes,
headers,
}: BeginHandlerObject) => any;
export type ProgressHandler = (
percent: number,
bytesDownloaded: number,
}: BeginHandlerObject) => void;
export interface ProgressHandlerObject {
bytesDownloaded: number
bytesTotal: number
) => any;
export type DoneHandler = () => any;
export type ErrorHandler = (error: any, errorCode: any) => any;
}
export type ProgressHandler = ({
bytesDownloaded,
bytesTotal,
}: ProgressHandlerObject) => void;
export interface DoneHandlerObject {
bytesDownloaded: number
bytesTotal: number
}
export type DoneHandler = ({
bytesDownloaded,
bytesTotal,
}: DoneHandlerObject) => void;
export interface ErrorHandlerObject {
error: string
errorCode: number
}
export type ErrorHandler = ({
error,
errorCode,
}: ErrorHandlerObject) => void;
export interface TaskInfoObject {
id: string;
metadata: object | string;
bytesDownloaded?: number;
bytesTotal?: number;
beginHandler?: BeginHandler;
progressHandler?: ProgressHandler;
doneHandler?: DoneHandler;
errorHandler?: ErrorHandler;
}
export type TaskInfo = TaskInfoObject;
export type DownloadTaskState =
| "DOWNLOADING"
| "PAUSED"
| "DONE"
| "FAILED"
| "STOPPED";
| 'DOWNLOADING'
| 'PAUSED'
| 'DONE'
| 'FAILED'
| 'STOPPED';
export interface DownloadTask {
constructor: (taskInfo: TaskInfo) => DownloadTask;
id: string;
state: DownloadTaskState;
percent: number;
bytesDownloaded: number;
bytesTotal: number;
@@ -68,18 +86,14 @@ export interface DownloadTask {
_doneHandler: DoneHandler;
_errorHandler: ErrorHandler;
pause: () => any;
resume: () => any;
stop: () => any;
pause: () => void;
resume: () => void;
stop: () => void;
}
export type CheckForExistingDownloads = () => Promise<DownloadTask[]>;
export type EnsureDownloadsAreRunning = () => Promise<void>;
export interface InitDownloaderOptions {
}
export type InitDownloader = (options: InitDownloaderOptions) => undefined;
export interface DownloadOption {
id: string;
url: string;
@@ -88,6 +102,7 @@ export interface DownloadOption {
metadata?: object;
isAllowedOverRoaming?: boolean;
isAllowedOverMetered?: boolean;
progressInterval?: number;
}
export type Download = (options: DownloadOption) => DownloadTask;
@@ -97,17 +112,15 @@ export interface Directories {
documents: string;
}
export const setHeaders: SetHeaders;
export const checkForExistingDownloads: CheckForExistingDownloads;
export const ensureDownloadsAreRunning: EnsureDownloadsAreRunning;
export const initDownloader: InitDownloader;
export const download: Download;
export const completeHandler: CompleteHandler;
export const directories: Directories;
export const setHeaders: SetHeaders
export const checkForExistingDownloads: CheckForExistingDownloads
export const ensureDownloadsAreRunning: EnsureDownloadsAreRunning
export const download: Download
export const completeHandler: CompleteHandler
export const directories: Directories
export interface RNBackgroundDownloader {
setHeaders: SetHeaders;
initDownloader: InitDownloader;
checkForExistingDownloads: CheckForExistingDownloads;
ensureDownloadsAreRunning: EnsureDownloadsAreRunning;
download: Download;
@@ -115,5 +128,5 @@ export interface RNBackgroundDownloader {
directories: Directories;
}
declare const RNBackgroundDownloader: RNBackgroundDownloader;
export default RNBackgroundDownloader;
declare const RNBackgroundDownloader: RNBackgroundDownloader
export default RNBackgroundDownloader

View File

@@ -1,4 +1,4 @@
import { NativeModules, NativeEventEmitter, Platform } from 'react-native'
import { NativeModules, NativeEventEmitter } from 'react-native'
import DownloadTask from './lib/DownloadTask'
const { RNBackgroundDownloader } = NativeModules
@@ -7,50 +7,46 @@ const RNBackgroundDownloaderEmitter = new NativeEventEmitter(RNBackgroundDownloa
const tasksMap = new Map()
let headers = {}
RNBackgroundDownloaderEmitter.addListener('downloadBegin', ({ id, expectedBytes, headers }) => {
console.log('[RNBackgroundDownloader] downloadBegin', id, expectedBytes, headers)
RNBackgroundDownloaderEmitter.addListener('downloadBegin', ({ id, ...rest }) => {
console.log('[RNBackgroundDownloader] downloadBegin', id, rest)
const task = tasksMap.get(id)
task?.onBegin({ expectedBytes, headers })
task?.onBegin(rest)
})
RNBackgroundDownloaderEmitter.addListener('downloadProgress', events => {
// console.log('[RNBackgroundDownloader] downloadProgress-1', events, tasksMap)
for (const event of events) {
const task = tasksMap.get(event.id)
// console.log('[RNBackgroundDownloader] downloadProgress-2', event.id, task)
task?.onProgress(event.percent, event.bytesDownloaded, event.bytesTotal)
const { id, ...rest } = event
const task = tasksMap.get(id)
// console.log('[RNBackgroundDownloader] downloadProgress-2', id, task)
task?.onProgress(rest)
}
})
RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, location }) => {
console.log('[RNBackgroundDownloader] downloadComplete', id, location)
RNBackgroundDownloaderEmitter.addListener('downloadComplete', ({ id, ...rest }) => {
console.log('[RNBackgroundDownloader] downloadComplete', id, rest)
const task = tasksMap.get(id)
task?.onDone({ location })
task?.onDone(rest)
tasksMap.delete(id)
})
RNBackgroundDownloaderEmitter.addListener('downloadFailed', event => {
console.log('[RNBackgroundDownloader] downloadFailed', event)
const task = tasksMap.get(event.id)
task?.onError(event.error, event.errorCode)
RNBackgroundDownloaderEmitter.addListener('downloadFailed', ({ id, ...rest }) => {
console.log('[RNBackgroundDownloader] downloadFailed', id, rest)
const task = tasksMap.get(id)
task?.onError(rest)
tasksMap.delete(event.id)
tasksMap.delete(id)
})
export function setHeaders(h = {}) {
export function setHeaders (h = {}) {
if (typeof h !== 'object')
throw new Error('[RNBackgroundDownloader] headers must be an object')
headers = h
}
export function initDownloader(options = {}) {
if (Platform.OS === 'android')
RNBackgroundDownloader.initDownloader(options)
}
export function checkForExistingDownloads() {
export function checkForExistingDownloads () {
console.log('[RNBackgroundDownloader] checkForExistingDownloads-1')
return RNBackgroundDownloader.checkForExistingDownloads()
.then(foundTasks => {
@@ -80,7 +76,7 @@ export function checkForExistingDownloads() {
})
}
export function ensureDownloadsAreRunning() {
export function ensureDownloadsAreRunning () {
console.log('[RNBackgroundDownloader] ensureDownloadsAreRunning')
return checkForExistingDownloads()
.then(tasks => {
@@ -92,7 +88,7 @@ export function ensureDownloadsAreRunning() {
})
}
export function completeHandler(jobId: string) {
export function completeHandler (jobId: string) {
if (jobId == null) {
console.warn('[RNBackgroundDownloader] completeHandler: jobId is empty')
return
@@ -109,9 +105,10 @@ type DownloadOptions = {
metadata?: object,
isAllowedOverRoaming?: boolean,
isAllowedOverMetered?: boolean,
progressInterval?: number,
}
export function download(options: DownloadOptions) {
export function download (options: DownloadOptions) {
console.log('[RNBackgroundDownloader] download', options)
if (!options.id || !options.url || !options.destination)
throw new Error('[RNBackgroundDownloader] id, url and destination are required')
@@ -125,6 +122,7 @@ export function download(options: DownloadOptions) {
if (options.isAllowedOverRoaming == null) options.isAllowedOverRoaming = true
if (options.isAllowedOverMetered == null) options.isAllowedOverMetered = true
if (options.progressInterval == null) options.progressInterval = 1000
const task = new DownloadTask({
id: options.id,
@@ -145,7 +143,6 @@ export const directories = {
}
export default {
initDownloader,
download,
checkForExistingDownloads,
ensureDownloadsAreRunning,

View File

@@ -19,7 +19,6 @@ static CompletionHandler storedCompletionHandler;
NSMutableDictionary<NSNumber *, RNBGDTaskConfig *> *taskToConfigMap;
NSMutableDictionary<NSString *, NSURLSessionDownloadTask *> *idToTaskMap;
NSMutableDictionary<NSString *, NSData *> *idToResumeDataMap;
NSMutableDictionary<NSString *, NSNumber *> *idToPercentMap;
NSMutableDictionary<NSString *, NSDictionary *> *progressReports;
NSDate *lastProgressReport;
NSNumber *sharedLock;
@@ -63,7 +62,6 @@ RCT_EXPORT_MODULE();
}
idToTaskMap = [[NSMutableDictionary alloc] init];
idToResumeDataMap = [[NSMutableDictionary alloc] init];
idToPercentMap = [[NSMutableDictionary alloc] init];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
NSString *sessonIdentifier = [bundleIdentifier stringByAppendingString:@".backgrounddownloadtask"];
sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessonIdentifier];
@@ -149,7 +147,6 @@ RCT_EXPORT_MODULE();
if (taskConfig) {
[idToTaskMap removeObjectForKey:taskConfig.id];
[idToPercentMap removeObjectForKey:taskConfig.id];
}
// TOREMOVE - GIVES ERROR IN JS ON HOT RELOAD
// if (taskToConfigMap.count == 0) {
@@ -224,7 +221,6 @@ RCT_EXPORT_METHOD(download: (NSDictionary *) options) {
[[NSUserDefaults standardUserDefaults] setObject:[self serialize: taskToConfigMap] forKey:ID_TO_CONFIG_MAP_KEY];
idToTaskMap[identifier] = task;
idToPercentMap[identifier] = @0.0;
[task resume];
lastProgressReport = [[NSDate alloc] init];
@@ -280,20 +276,17 @@ RCT_EXPORT_METHOD(checkForExistingDownloads: (RCTPromiseResolveBlock)resolve rej
}
[task resume];
}
NSNumber *percent = task.countOfBytesExpectedToReceive > 0 ? [NSNumber numberWithFloat:(float)task.countOfBytesReceived/(float)task.countOfBytesExpectedToReceive] : @0.0;
[idsFound addObject:@{
@"id": taskConfig.id,
@"metadata": taskConfig.metadata,
@"state": [NSNumber numberWithInt: task.state],
@"bytesDownloaded": [NSNumber numberWithLongLong:task.countOfBytesReceived],
@"bytesTotal": [NSNumber numberWithLongLong:task.countOfBytesExpectedToReceive],
@"percent": percent
@"bytesTotal": [NSNumber numberWithLongLong:task.countOfBytesExpectedToReceive]
}];
taskConfig.reportedBegin = YES;
taskToConfigMap[@(task.taskIdentifier)] = taskConfig;
idToTaskMap[taskConfig.id] = task;
idToPercentMap[taskConfig.id] = percent;
} else {
[task cancel];
}
@@ -331,6 +324,7 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
if (self.bridge) {
if (error == nil) {
NSDictionary *responseHeaders = ((NSHTTPURLResponse *)downloadTask.response).allHeaderFields;
// TODO: SEND bytesDownloaded AND bytesTotal
[self sendEventWithName:@"downloadComplete" body:@{@"id": taskConfig.id, @"headers": responseHeaders, @"location": taskConfig.destination}];
} else {
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskConfig.id, @"error": [error localizedDescription]}];
@@ -363,12 +357,11 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
taskCofig.reportedBegin = YES;
}
NSNumber *prevPercent = idToPercentMap[taskCofig.id];
NSNumber *percent = [NSNumber numberWithFloat:(float)bytesTotalWritten/(float)bytesTotalExpectedToWrite];
if ([percent floatValue] - [prevPercent floatValue] > 0.01f) {
progressReports[taskCofig.id] = @{@"id": taskCofig.id, @"bytesDownloaded": [NSNumber numberWithLongLong: bytesTotalWritten], @"bytesTotal": [NSNumber numberWithLongLong: bytesTotalExpectedToWrite], @"percent": percent};
idToPercentMap[taskCofig.id] = percent;
}
progressReports[taskCofig.id] = @{
@"id": taskCofig.id,
@"bytesDownloaded": [NSNumber numberWithLongLong: bytesTotalWritten],
@"bytesTotal": [NSNumber numberWithLongLong: bytesTotalExpectedToWrite]
};
NSDate *now = [[NSDate alloc] init];
if ([now timeIntervalSinceDate:lastProgressReport] > 0.25 && progressReports.count > 0) {
@@ -376,6 +369,8 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
[self sendEventWithName:@"downloadProgress" body:[progressReports allValues]];
}
lastProgressReport = now;
// TODO: SHOULD REMOVE ALL progressReports ?
// IS IT ALL SENT?
[progressReports removeAllObjects];
}
}
@@ -393,6 +388,7 @@ RCT_EXPORT_METHOD(completeHandler:(nonnull NSString *)jobId
return;
if (self.bridge) {
// TODO: SEND error AS IN OBJECT
[self sendEventWithName:@"downloadFailed" body:@{@"id": taskCofig.id, @"error": [error localizedDescription]}];
}
// IF WE CAN'T RESUME TO DOWNLOAD LATER

View File

@@ -15,13 +15,11 @@ export default class DownloadTask {
state = 'PENDING'
metadata = {}
percent = 0
bytesDownloaded = 0
bytesTotal = 0
constructor (taskInfo: TaskInfo, originalTask?: TaskInfo) {
this.id = taskInfo.id
this.percent = taskInfo.percent ?? 0
this.bytesDownloaded = taskInfo.bytesDownloaded ?? 0
this.bytesTotal = taskInfo.bytesTotal ?? 0
@@ -61,26 +59,27 @@ export default class DownloadTask {
return this
}
onBegin ({ expectedBytes, headers }) {
onBegin (params) {
this.state = 'DOWNLOADING'
this.beginHandler?.({ expectedBytes, headers })
this.beginHandler?.(params)
}
onProgress (percent, bytesDownloaded, bytesTotal) {
this.percent = percent
onProgress ({ bytesDownloaded, bytesTotal }) {
this.bytesDownloaded = bytesDownloaded
this.bytesTotal = bytesTotal
this.progressHandler?.(percent, bytesDownloaded, bytesTotal)
this.progressHandler?.({ bytesDownloaded, bytesTotal })
}
onDone ({ location }) {
onDone (params) {
this.state = 'DONE'
this.doneHandler?.({ location })
this.bytesDownloaded = params.bytesDownloaded
this.bytesTotal = params.bytesTotal
this.doneHandler?.(params)
}
onError (error, errorCode) {
onError (params) {
this.state = 'FAILED'
this.errorHandler?.(error, errorCode)
this.errorHandler?.(params)
}
pause () {

View File

@@ -1,6 +1,6 @@
{
"name": "@kesha-antonov/react-native-background-downloader",
"version": "3.0.0-alpha.0",
"version": "3.0.0-alpha.1",
"description": "A library for React-Native to help you download large files on iOS and Android both in the foreground and most importantly in the background.",
"keywords": [
"react-native",
@@ -15,9 +15,8 @@
"homepage": "https://github.com/kesha-antonov/react-native-background-downloader",
"license": "Apache-2.0",
"author": {
"name": "Eko labs",
"email": "dev@helloeko.com",
"url": "https://developer.helloeko.com"
"name": "Eko labs, Kesha Antonov",
"email": "innokenty.longway@gmail.com"
},
"contributors": [
{
@@ -56,17 +55,21 @@
]
},
"devDependencies": {
"@babel/core": "^7.23.2",
"@babel/eslint-parser": "^7.22.15",
"@babel/preset-env": "^7.23.2",
"@babel/runtime": "^7.23.2",
"@react-native-community/eslint-config": "^3.2.0",
"@babel/core": "^7.23.6",
"@babel/eslint-parser": "^7.23.3",
"@babel/preset-env": "^7.23.6",
"@babel/preset-typescript": "^7.23.3",
"@babel/runtime": "^7.23.6",
"@react-native/babel-preset": "^0.74.0",
"@react-native/eslint-config": "^0.74.0",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"babel-jest": "^29.7.0",
"eslint": "8.52.0",
"eslint": "^8.56.0",
"eslint-config-standard": "^17.1.0",
"eslint-config-standard-jsx": "^11.0.0",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.5.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.33.2",
@@ -77,10 +80,11 @@
"lint-staged": ">=15",
"metro-react-native-babel-preset": "^0.77.0",
"react": "18.2.0",
"react-native": "0.72.6",
"react-native": "0.73.0",
"react-native-fs": "^2.20.0",
"react-native-vector-icons": "^10.0.1",
"react-test-renderer": "18.2.0"
"react-native-vector-icons": "^10.0.3",
"react-test-renderer": "18.2.0",
"typescript": "5.3.3"
},
"peerDependencies": {
"react-native": ">=0.57.0"

2170
yarn.lock

File diff suppressed because it is too large Load Diff