diff --git a/package-lock.json b/package-lock.json
index 382d61ba269..42065d2330f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5746,6 +5746,7 @@
},
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0",
+ "extraneous": true,
"inBundle": true,
"license": "MIT",
"engines": {
@@ -7076,6 +7077,27 @@
"react": ">=16.8.0"
}
},
+ "node_modules/@dr.pogodin/react-native-fs": {
+ "version": "2.32.1",
+ "resolved": "https://registry.npmjs.org/@dr.pogodin/react-native-fs/-/react-native-fs-2.32.1.tgz",
+ "integrity": "sha512-vNPgyPpw4OGaooIay8RUx0qUaFExas0ce6kmud1oTKjqeohEqwzuFZBa7gJ1bmF2cEkplp8sUzw2BVv4NDPrVA==",
+ "license": "MIT",
+ "workspaces": [
+ "example"
+ ],
+ "dependencies": {
+ "buffer": "^6.0.3",
+ "http-status-codes": "^2.3.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/birdofpreyru"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/@dual-bundle/import-meta-resolve": {
"version": "4.1.0",
"dev": true,
@@ -12681,6 +12703,635 @@
"node": ">=14"
}
},
+ "node_modules/@firebase/ai": {
+ "version": "2.13.0",
+ "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.13.0.tgz",
+ "integrity": "sha512-nJJDQKqjAcbkZdZGT/5WTVLrGZ+pYhWbwKC90nNzmvtoRTtnOJaNS34fhKSHQeB9SALgD2kxuWT5I4AkytdZ/Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.4",
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x",
+ "@firebase/app-types": "0.x"
+ }
+ },
+ "node_modules/@firebase/analytics": {
+ "version": "0.10.22",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.22.tgz",
+ "integrity": "sha512-8BSaq/QRGU1+xyi8L2PTLTJU7MH9aMA72RQdIxrbhWFauOZY9OXo8f2YDN/972xA8d588tlnNVEQ2Mo69pT9Ow==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/installations": "0.6.22",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/analytics-compat": {
+ "version": "0.2.28",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.28.tgz",
+ "integrity": "sha512-lIAlqUUbBu93FJMlQfslryQtBwwzdzvp23ePC6FNgymXk6Ook5v4Uvc0vdutvoIeqmyA3LfP0ZeRFK8+11kOOQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/analytics": "0.10.22",
+ "@firebase/analytics-types": "0.8.4",
+ "@firebase/component": "0.7.3",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/analytics-types": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.4.tgz",
+ "integrity": "sha512-zQ+XTgkwH6CY/eUSHJRP7e4LxM30RCxlCmob5sy2axs25GE3Ny0XdgpDscMTHHQIGqWkxPXad4w2Mw9sCgT8zQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/app": {
+ "version": "0.14.13",
+ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.13.tgz",
+ "integrity": "sha512-H89Jeyp31+EZk9GPu6vaeL9mEmoXgM3nASB7UPBYYS/lqAks21mO1BU1dF8NbsVTL6tgGZkGUtiGJgxtDiwHkw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "idb": "7.1.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/app-check": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.4.tgz",
+ "integrity": "sha512-G8EsbVJV9gSfoibx0dNoNOUrvr+PkL7J//+W/BST/oUassimkZeq9bjj3bKkB0pn4og5GMQ9qs7FefwP00kkgg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/app-check-compat": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.4.tgz",
+ "integrity": "sha512-9iP0MvmaVagulNXmrca96U3tqNAI3j98wsC1z7rj62nnOTajlrHM//jjB9VoHqRw6/islMskp6RsKnM7vhLDqA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-check": "0.11.4",
+ "@firebase/app-check-types": "0.5.4",
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/app-check-interop-types": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.4.tgz",
+ "integrity": "sha512-zz3i6e13B8BfWiLy8MABtTh8aGIACgKbf9UVnyHcWs+yQzJXgQcl8A46b0zfaiJHdQ+niF0ouAfcpuf+3LMPQg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/app-check-types": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.4.tgz",
+ "integrity": "sha512-xV7JsIyzVr15aA7f3Pi0rB9gdBuVubs89FGA8VkRYA4g0l78poADgdfrScgf7NndSg9mm7cR7PJyY0+t22KaGw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/app-compat": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.13.tgz",
+ "integrity": "sha512-pn3FvXwUR34kWPccDQfCKsNZcM2wD1OS+J1jeEgzM1ZNXoxR2NaF6e5DjDuRrnTwR6LN2XQQt0IqE6yKmgpCQg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app": "0.14.13",
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/app-types": {
+ "version": "0.9.5",
+ "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.5.tgz",
+ "integrity": "sha512-YevqTjvo7Iujsa9Dwowmd6dSoElhzmD63ZSrq6bzjvQ6POjYgNjOFHLmNIgJs48eNO093NCERibuFnxbfOvU7A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/logger": "0.5.1"
+ }
+ },
+ "node_modules/@firebase/app/node_modules/idb": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
+ "license": "ISC"
+ },
+ "node_modules/@firebase/auth": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.13.2.tgz",
+ "integrity": "sha512-B4w0iS7MxRg28oIh2fJFTE6cM0lYdBrW19eHpc42jqEcloUjlYyVrpPqZvqA4+v9KFEVSKEs2SfWyta7hbzkJQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x",
+ "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@react-native-async-storage/async-storage": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@firebase/auth-compat": {
+ "version": "0.6.7",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.7.tgz",
+ "integrity": "sha512-XgKnOgY1Siq7gylAmLkYtHAlRxNeWEAspH+nO3gJZJnfHqoTHbr9UjJ3nHNFALYXV5CfpQlyPROyB2ztySBHBQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/auth": "1.13.2",
+ "@firebase/auth-types": "0.13.1",
+ "@firebase/component": "0.7.3",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/auth-interop-types": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.5.tgz",
+ "integrity": "sha512-1Li/YuBDBAXcKv7BzY4U28gontUmAaw53sYiqbaVOMCFb2lFKK/c3CGMUWqtwe7+TXrl3poWnTCL5umYBg85Eg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/auth-types": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.1.tgz",
+ "integrity": "sha512-0c1Mnid0uMDfGJHeUS4zfvBa4/CedJXotGy/n/NZJnBjwiJawt0ZYU+wH2VAVLiRCEfG2ncCkAX3yd1/2nrB7g==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x",
+ "@firebase/util": "1.x"
+ }
+ },
+ "node_modules/@firebase/component": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.3.tgz",
+ "integrity": "sha512-wFofIaa2879ogD/WvkjYXJxRmfnL0scen6ORgaC3na1FNOR9ASIUANQdhqQcmWu/h77/pVHY7ch5flewa5Bcew==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/data-connect": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.7.1.tgz",
+ "integrity": "sha512-2LbUU8mmSA63HknxQMmWHjpzuNLBKflvVwQc2tpoVKg0biWleNEJX031ELks0vzFs+dDjOUkCJR72RP6mQHFOg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/auth-interop-types": "0.2.5",
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/database": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.3.tgz",
+ "integrity": "sha512-XwWCa+E4TvNGpGwXrycLRNfdogADwFcvuhyow6wDWma9W54roaQIhe+4PM0KiLsIftBdSCGI7OKCXrdSRHbIhw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.4",
+ "@firebase/auth-interop-types": "0.2.5",
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "faye-websocket": "0.11.4",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database-compat": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.4.tgz",
+ "integrity": "sha512-3pK35F1MAgmqFJQlf2nhQl44vtAXQO1uaCaQOEUI9kCRtLFqi7N+QRKR7lFZPg+xIZIyubgxQaxY69YgfZRZWg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/database": "1.1.3",
+ "@firebase/database-types": "1.0.20",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/database-types": {
+ "version": "1.0.20",
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.20.tgz",
+ "integrity": "sha512-kegbOk/w8iU64pr0q6k2ItyNGjnQBMHFhwS7ohdWI4W+pc0/zhhdGXTdFj6X1oxItRjPoYOsSQmERgBkn/ihxw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-types": "0.9.5",
+ "@firebase/util": "1.15.1"
+ }
+ },
+ "node_modules/@firebase/firestore": {
+ "version": "4.15.0",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.15.0.tgz",
+ "integrity": "sha512-Fj9osqYkz2Rqr7kW3/A8BRd8CyJ7yA5K8YjhihRdyJWbL+FsELVcR6DpoCplrp1IyU+xeGgTubo1UOySXpY+EA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "@firebase/webchannel-wrapper": "1.0.6",
+ "@grpc/grpc-js": "~1.9.0",
+ "@grpc/proto-loader": "^0.7.8",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/firestore-compat": {
+ "version": "0.4.10",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.10.tgz",
+ "integrity": "sha512-yMP3FADDjikdrQv4YmvL4EkIny6Hw+N+a2O5T40rlHiniyMpRPxgYkKiFOvMZnsqKLqBVnKqCAElC0pa/IZtdw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/firestore": "4.15.0",
+ "@firebase/firestore-types": "3.0.4",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/firestore-types": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.4.tgz",
+ "integrity": "sha512-jGn+JSS4X9zZsrfu7Yw66v5YRdOLD1oyQh4USR0xWl4CUqV/DA6bNIXRPpxH/cUl3iVTNiP6MN7g+EL42A4qfA==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x",
+ "@firebase/util": "1.x"
+ }
+ },
+ "node_modules/@firebase/functions": {
+ "version": "0.13.5",
+ "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.5.tgz",
+ "integrity": "sha512-bWCx713f4kE/uFV7gdFOLBS7lDoiZj48MRkbAqe35gkXcCeWF4QjRNO07Jhmve7EJIoQOBczL29y2r8VRuN1kw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/app-check-interop-types": "0.3.4",
+ "@firebase/auth-interop-types": "0.2.5",
+ "@firebase/component": "0.7.3",
+ "@firebase/messaging-interop-types": "0.2.5",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/functions-compat": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.5.tgz",
+ "integrity": "sha512-10qlUXGY25G5/1g9UihqksPp2po+ZqSE7LEizsrdUP7vrTmkysXxGSZCDyojSEp6mQe/ecRDdDDI+z4XRdb4wQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/functions": "0.13.5",
+ "@firebase/functions-types": "0.6.4",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/functions-types": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.4.tgz",
+ "integrity": "sha512-zV6kgqtduR4rUAdC/ilS7kmb93XD7bEZoJDlVBZqlOw2uGGGCNBQBuleww2rr0Ulr3L9o2TDjumEt68/l1f9DQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/installations": {
+ "version": "0.6.22",
+ "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.22.tgz",
+ "integrity": "sha512-ef6nn3GGQTdReCfotRMG77PJZu8CqEbiK5pEoBnM0gTu/Z9v0i/az2p3HABsa/1beQmmyh1OsOjf7P5+pgwdZw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/util": "1.15.1",
+ "idb": "7.1.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/installations-compat": {
+ "version": "0.2.22",
+ "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.22.tgz",
+ "integrity": "sha512-C/zpAuTP5S9OgKSPvXRupw3hoY/JZSlA1wFjD/Sb7LIQE0FNbcMdO8Y4KXVEkjVzma/DDDDIAzxEXqKMAzc88w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/installations": "0.6.22",
+ "@firebase/installations-types": "0.5.4",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/installations-types": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.4.tgz",
+ "integrity": "sha512-U2eFapdHwjb43Vx9o+Pmj4dFfvcHEK1IirEFLqMtWrTHvmdrS3gBpBD1kmJk/9HjsOtoHZxJ2Paoe79e+L1ZPg==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x"
+ }
+ },
+ "node_modules/@firebase/installations/node_modules/idb": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
+ "license": "ISC"
+ },
+ "node_modules/@firebase/logger": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.1.tgz",
+ "integrity": "sha512-vZKLsqE1ABOy8OjQiE7cUTFn4gvaqlk88yp8N94Pk/sDpq61YqZGqmVFZTvOyflTwuYFcWirBdYGoJgbDaXKYQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/messaging": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.13.0.tgz",
+ "integrity": "sha512-GZoo0uGRvEbszo83xcgbjJp4FpkmBEr4l8Z4hi8gl+P1Spn/MTK3HapanMzSX4yUHuTEiF5hasWRxOaz+o5sxQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/installations": "0.6.22",
+ "@firebase/messaging-interop-types": "0.2.5",
+ "@firebase/util": "1.15.1",
+ "idb": "7.1.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/messaging-compat": {
+ "version": "0.2.27",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.27.tgz",
+ "integrity": "sha512-JNOiu1PPgdHzEPEtoFiNxQuu0x9bm4bfETSQCpGfcTlgWkhlSK7uh7nlsjC10TQLUNgYetLmuutaYTh8aeYLVA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/messaging": "0.13.0",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/messaging-interop-types": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.5.tgz",
+ "integrity": "sha512-tUEKnaAP2Y/MNIqgnriPpV6e5l13Vs/+p2yrd6NGlncPJT9O3a8muYZtdnWe+IJ4fgKLHJVC79n/asxk/N5Msw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/messaging/node_modules/idb": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
+ "license": "ISC"
+ },
+ "node_modules/@firebase/performance": {
+ "version": "0.7.12",
+ "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.12.tgz",
+ "integrity": "sha512-fe7nV8teUU3OBHlMUZ9Lw4gLhCW2k4m5Uc3pfWGV+fl8uwJQBGp9Q3lqsJ+HSrFu3Q2pJyLAgrClPGSKyDeYgQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/installations": "0.6.22",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0",
+ "web-vitals": "^4.2.4"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/performance-compat": {
+ "version": "0.2.25",
+ "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.25.tgz",
+ "integrity": "sha512-q6NjTXpIPoFuUmCmMN/maCdTgzT6aExs9xZo+PxfVLj6uLVGvpyAD6XWjmcrb7jChsFBYbq7E5dyNDF7Zhy9kA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/performance": "0.7.12",
+ "@firebase/performance-types": "0.2.4",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/performance-types": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.4.tgz",
+ "integrity": "sha512-kJSEk7b0uhpcPRyL4SQ/GPujLqk52XNKcXlnsKDbWGAb9vugcLvOU3u6zfEdwd+d8hWJb5S5ZizV1JFFI0nkKg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/remote-config": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.4.tgz",
+ "integrity": "sha512-lslywR5lGvHWTu4z/MPoYs3UwS3CKdeY+ELXY87087VsOpBpkD+9Orra23tA9GW683arPTDOM3CM6eKmtiOO3g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/installations": "0.6.22",
+ "@firebase/logger": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/remote-config-compat": {
+ "version": "0.2.25",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.25.tgz",
+ "integrity": "sha512-FnA5S4IxFJAAFrCnYzWlO0FCaizlYdqhe42ygFMA+wE/mUP+w36iXzHyKj1OO1A+2gyMFjeRHyg8HhkJ6c5vRA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/logger": "0.5.1",
+ "@firebase/remote-config": "0.8.4",
+ "@firebase/remote-config-types": "0.5.1",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/remote-config-types": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.1.tgz",
+ "integrity": "sha512-cX/1LT6KQwkXzck2eSzeKnuvXZCyr8qaPpDcikoJs7jmI+oBOXixpDLeDtWj1U6GNMkIoXrEDNoyT2Ypcyp5/A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@firebase/storage": {
+ "version": "0.14.3",
+ "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.3.tgz",
+ "integrity": "sha512-YX4/YL6P6/fufSSeGnVhjWddcIXbFq2cWIhMKFTZo1E/Rtcl2mJj/BYUQTwJfcE1Tl8un1FOya4L05jcSLN/Eg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app": "0.x"
+ }
+ },
+ "node_modules/@firebase/storage-compat": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.3.tgz",
+ "integrity": "sha512-gruVqjtUGX8tEoeNbaWXZm0Zfcfcb7fvmDmBxV8yPAbWvExRnZYLO2+qw9idxNE7BvPXt5csyjSYHy//dAizxw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/component": "0.7.3",
+ "@firebase/storage": "0.14.3",
+ "@firebase/storage-types": "0.8.4",
+ "@firebase/util": "1.15.1",
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "@firebase/app-compat": "0.x"
+ }
+ },
+ "node_modules/@firebase/storage-types": {
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.4.tgz",
+ "integrity": "sha512-BT7cwxJOx8SWwlQfrlC+bD/Sk3Cw+1odCi8UZNFNWTVZoPsBnA5W+mqtZzVnvsdJpXCFGSGQ7R7vOR6dtM/BRA==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@firebase/app-types": "0.x",
+ "@firebase/util": "1.x"
+ }
+ },
+ "node_modules/@firebase/util": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.15.1.tgz",
+ "integrity": "sha512-LUdM4Wg7YM9Pq/49nGYySJA0CSQEKnGffFzWV8+6gXN7mGxn+FL1IqvFbuZUtAQcfZgHYDwCE1wwlK7rB7gl2g==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/@firebase/webchannel-wrapper": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.6.tgz",
+ "integrity": "sha512-Vr/Mqu79dMwGRAyGbJ4uN4+BtXB3/mRTdzetD1daWNeG8QaWuzhhbG77GltO5c0yYmYls8i250iX73624GJd7Q==",
+ "license": "Apache-2.0"
+ },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
@@ -12884,6 +13535,66 @@
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
+ "node_modules/@grpc/grpc-js": {
+ "version": "1.9.16",
+ "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz",
+ "integrity": "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@grpc/proto-loader": "^0.7.8",
+ "@types/node": ">=12.12.47"
+ },
+ "engines": {
+ "node": "^8.13.0 || >=10.10.0"
+ }
+ },
+ "node_modules/@grpc/proto-loader": {
+ "version": "0.7.15",
+ "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
+ "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lodash.camelcase": "^4.3.0",
+ "long": "^5.0.0",
+ "protobufjs": "^7.2.5",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@grpc/proto-loader/node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@grpc/proto-loader/node_modules/protobufjs": {
+ "version": "7.6.6",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz",
+ "integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.5",
+ "@protobufjs/eventemitter": "^1.1.1",
+ "@protobufjs/fetch": "^1.1.1",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.1",
+ "@types/node": ">=13.7.0",
+ "long": "^5.3.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"dev": true,
@@ -17511,6 +18222,15 @@
"node": ">= 8"
}
},
+ "node_modules/@notifee/react-native": {
+ "version": "9.1.8",
+ "resolved": "https://registry.npmjs.org/@notifee/react-native/-/react-native-9.1.8.tgz",
+ "integrity": "sha512-Az/dueoPerJsbbjRxu8a558wKY+gONUrfoy3Hs++5OqbeMsR0dYe6P+4oN6twrLFyzAhEA1tEoZRvQTFDRmvQg==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native": "*"
+ }
+ },
"node_modules/@npmcli/config": {
"version": "6.4.0",
"dev": true,
@@ -19500,19 +20220,24 @@
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
- "version": "2.0.4",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
+ "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
- "version": "1.1.0",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
+ "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
"license": "BSD-3-Clause",
"dependencies": {
- "@protobufjs/aspromise": "^1.1.1",
- "@protobufjs/inquire": "^1.1.0"
+ "@protobufjs/aspromise": "^1.1.1"
}
},
"node_modules/@protobufjs/float": {
@@ -19532,7 +20257,9 @@
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
- "version": "1.1.0",
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
+ "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
"license": "BSD-3-Clause"
},
"node_modules/@radix-ui/colors": {
@@ -24214,6 +24941,40 @@
"react-native": "*"
}
},
+ "node_modules/@react-native-firebase/app": {
+ "version": "24.1.1",
+ "resolved": "https://registry.npmjs.org/@react-native-firebase/app/-/app-24.1.1.tgz",
+ "integrity": "sha512-NKCUgBeilQvchHQeKwB6RuE3Ne2AZMlClXjJBVySkCAF2FNO73ML2brqrAyegRkuVswtqHsB6wAgypmP82WnnA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "firebase": "12.14.0"
+ },
+ "peerDependencies": {
+ "expo": ">=47.0.0",
+ "react": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@react-native-firebase/messaging": {
+ "version": "24.1.1",
+ "resolved": "https://registry.npmjs.org/@react-native-firebase/messaging/-/messaging-24.1.1.tgz",
+ "integrity": "sha512-tMb75+4ehbQshlH+PifbxKbP+pyDgK/dfBIvlFJeDVEAbdaM2nVpMkVyV6s5WJ3WRPUt6v+hyU1P3krQPYhkig==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@react-native-firebase/app": "24.1.1",
+ "expo": ">=47.0.0"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.79.5",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz",
@@ -24982,10 +25743,6 @@
"semver": "bin/semver.js"
}
},
- "node_modules/@react-native/normalize-color": {
- "version": "2.1.0",
- "license": "MIT"
- },
"node_modules/@react-native/normalize-colors": {
"version": "0.74.85",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.85.tgz",
@@ -54385,15 +55142,6 @@
"node": ">= 0.6"
}
},
- "node_modules/deprecated-react-native-prop-types": {
- "version": "2.3.0",
- "license": "MIT",
- "dependencies": {
- "@react-native/normalize-color": "*",
- "invariant": "*",
- "prop-types": "*"
- }
- },
"node_modules/dequal": {
"version": "2.0.3",
"license": "MIT",
@@ -60322,6 +61070,18 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/faye-websocket": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "websocket-driver": ">=0.5.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/fb-watchman": {
"version": "2.0.2",
"license": "Apache-2.0",
@@ -60786,6 +61546,42 @@
"node": ">=8"
}
},
+ "node_modules/firebase": {
+ "version": "12.14.0",
+ "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.14.0.tgz",
+ "integrity": "sha512-aEZ/lniDR1hOCYpx/x/V8Nrrqq9pepKDNkqP/4WGZFC69gTv6F59Z4/54W/SUP4L/hFlrRNmWj35aweQq+IHow==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@firebase/ai": "2.13.0",
+ "@firebase/analytics": "0.10.22",
+ "@firebase/analytics-compat": "0.2.28",
+ "@firebase/app": "0.14.13",
+ "@firebase/app-check": "0.11.4",
+ "@firebase/app-check-compat": "0.4.4",
+ "@firebase/app-compat": "0.5.13",
+ "@firebase/app-types": "0.9.5",
+ "@firebase/auth": "1.13.2",
+ "@firebase/auth-compat": "0.6.7",
+ "@firebase/data-connect": "0.7.1",
+ "@firebase/database": "1.1.3",
+ "@firebase/database-compat": "2.1.4",
+ "@firebase/firestore": "4.15.0",
+ "@firebase/firestore-compat": "0.4.10",
+ "@firebase/functions": "0.13.5",
+ "@firebase/functions-compat": "0.4.5",
+ "@firebase/installations": "0.6.22",
+ "@firebase/installations-compat": "0.2.22",
+ "@firebase/messaging": "0.13.0",
+ "@firebase/messaging-compat": "0.2.27",
+ "@firebase/performance": "0.7.12",
+ "@firebase/performance-compat": "0.2.25",
+ "@firebase/remote-config": "0.8.4",
+ "@firebase/remote-config-compat": "0.2.25",
+ "@firebase/storage": "0.14.3",
+ "@firebase/storage-compat": "0.4.3",
+ "@firebase/util": "1.15.1"
+ }
+ },
"node_modules/fix-hmr": {
"version": "1.0.2",
"license": "MIT",
@@ -65332,6 +66128,12 @@
"version": "1.0.0",
"license": "ISC"
},
+ "node_modules/http-parser-js": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
+ "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
+ "license": "MIT"
+ },
"node_modules/http-proxy-agent": {
"version": "5.0.0",
"dev": true,
@@ -70657,7 +71459,6 @@
},
"node_modules/lodash.camelcase": {
"version": "4.3.0",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash.clonedeep": {
@@ -85576,23 +86377,6 @@
"prop-types": "^15.5.10"
}
},
- "node_modules/react-native-fs": {
- "version": "2.20.0",
- "license": "MIT",
- "dependencies": {
- "base-64": "^0.1.0",
- "utf8": "^3.0.0"
- },
- "peerDependencies": {
- "react-native": "*",
- "react-native-windows": "*"
- },
- "peerDependenciesMeta": {
- "react-native-windows": {
- "optional": true
- }
- }
- },
"node_modules/react-native-get-random-values": {
"version": "1.10.0",
"license": "MIT",
@@ -85729,13 +86513,6 @@
}
}
},
- "node_modules/react-native-radial-gradient": {
- "version": "1.1.4",
- "license": "MIT",
- "dependencies": {
- "deprecated-react-native-prop-types": "^2.3.0"
- }
- },
"node_modules/react-native-randombytes": {
"version": "3.6.1",
"license": "ISC",
@@ -103252,6 +104029,12 @@
"node": ">= 8"
}
},
+ "node_modules/web-vitals": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
+ "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
+ "license": "Apache-2.0"
+ },
"node_modules/web-worker": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz",
@@ -105964,6 +106747,29 @@
"node": ">=4.0.0"
}
},
+ "node_modules/websocket-driver": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz",
+ "integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/websocket/node_modules/debug": {
"version": "2.6.9",
"license": "MIT",
@@ -118920,7 +119726,7 @@
},
"packages/mobile": {
"name": "@audius/mobile",
- "version": "1.5.185",
+ "version": "1.5.187",
"dependencies": {
"@amplitude/analytics-react-native": "1.4.11",
"@audius/common": "*",
@@ -118930,6 +119736,7 @@
"@audius/sdk": "*",
"@bravemobile/react-native-code-push": "^12.3.2",
"@coinflowlabs/react-native": "4.5.2",
+ "@dr.pogodin/react-native-fs": "2.32.1",
"@ebay/nice-modal-react": "^1.2.13",
"@emotion/native": "^11.11.0",
"@emotion/react": "11.14.0",
@@ -118938,6 +119745,7 @@
"@hcaptcha/react-native-hcaptcha": "1.6.0",
"@noble/hashes": "1.1.5",
"@noble/secp256k1": "1.7.0",
+ "@notifee/react-native": "^9.1.8",
"@optimizely/optimizely-sdk": "4.9.2",
"@peculiar/webcrypto": "1.4.3",
"@react-native-async-storage/async-storage": "^2.0.0",
@@ -118948,6 +119756,8 @@
"@react-native-community/netinfo": "^11.3.3",
"@react-native-community/slider": "4.5.0",
"@react-native-documents/picker": "^10.1.2",
+ "@react-native-firebase/app": "^24.1.1",
+ "@react-native-firebase/messaging": "^24.1.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "7.19.2",
"@react-navigation/core": "7.22.1",
@@ -118998,7 +119808,6 @@
"react-native-draggable-flatlist": "4.0.1",
"react-native-dynamic": "1.0.0",
"react-native-fast-crypto": "3.0.0",
- "react-native-fs": "^2.18.0",
"react-native-gesture-handler": "2.25.0",
"react-native-get-random-values": "1.10.0",
"react-native-gifted-charts": "1.4.46",
@@ -119011,15 +119820,12 @@
"react-native-intersection-observer": "0.2.1",
"react-native-keyboard-aware-scroll-view": "0.9.5",
"react-native-keyboard-controller": "1.19.0",
- "react-native-linear-gradient": "2.8.3",
"react-native-markdown-display": "7.0.2",
"react-native-modal": "^13.0.0",
"react-native-modal-datetime-picker": "18.0.0",
- "react-native-notifications": "5.1.0",
"react-native-pager-view": "6.7.1",
"react-native-permissions": "5.4.4",
"react-native-qrcode-svg": "6.2.0",
- "react-native-radial-gradient": "1.1.4",
"react-native-randombytes": "3.6.1",
"react-native-reanimated": "~3.19.5",
"react-native-restart": "0.0.27",
@@ -121233,14 +122039,6 @@
"react-native": "*"
}
},
- "packages/mobile/node_modules/react-native-linear-gradient": {
- "version": "2.8.3",
- "license": "MIT",
- "peerDependencies": {
- "react": "*",
- "react-native": "*"
- }
- },
"packages/mobile/node_modules/react-native-markdown-display": {
"version": "7.0.2",
"license": "MIT",
@@ -121267,14 +122065,6 @@
"react-native": ">=0.65.0"
}
},
- "packages/mobile/node_modules/react-native-notifications": {
- "version": "5.1.0",
- "license": "MIT",
- "peerDependencies": {
- "react": "*",
- "react-native": "*"
- }
- },
"packages/mobile/node_modules/react-native-qrcode-svg": {
"version": "6.2.0",
"license": "MIT",
diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle
index bbac1531a87..4aa27585396 100755
--- a/packages/mobile/android/app/build.gradle
+++ b/packages/mobile/android/app/build.gradle
@@ -134,7 +134,7 @@ android {
// versionCode is automatically incremented in CI
versionCode 1
// Make sure this is above the currently released Android version in the play store if your changes touch native code:
- versionName "1.1.535"
+ versionName "1.1.536"
resValue "string", "build_config_package", "co.audius.app"
resConfigs "en"
}
@@ -194,7 +194,6 @@ dependencies {
implementation 'androidx.core:core-splashscreen:1.0.0'
implementation "androidx.appcompat:appcompat:1.0.0"
- implementation project(':react-native-notifications')
implementation platform('com.google.firebase:firebase-bom:31.1.1')
implementation 'com.google.firebase:firebase-messaging'
implementation 'com.google.firebase:firebase-analytics'
diff --git a/packages/mobile/android/app/src/main/AndroidManifest.xml b/packages/mobile/android/app/src/main/AndroidManifest.xml
index 8ca6c1c1ab5..a769bac310a 100755
--- a/packages/mobile/android/app/src/main/AndroidManifest.xml
+++ b/packages/mobile/android/app/src/main/AndroidManifest.xml
@@ -92,6 +92,11 @@
+
+
+ if (subproject.name == 'dr.pogodin_react-native-fs' && findProperty('newArchEnabled') != 'true') {
+ subproject.plugins.withId('com.android.library') {
+ subproject.android.sourceSets.main.java.srcDirs += ["${subproject.projectDir}/generated/java"]
+ }
+ }
+}
+
apply plugin: "com.facebook.react.rootproject"
allprojects {
@@ -39,4 +56,4 @@ allprojects {
dirs "$rootDir/libs"
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/mobile/android/settings.gradle b/packages/mobile/android/settings.gradle
index 7b69b74c854..db9cbaba190 100755
--- a/packages/mobile/android/settings.gradle
+++ b/packages/mobile/android/settings.gradle
@@ -4,15 +4,9 @@ extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autoli
rootProject.name = 'AudiusReactNative'
include ':react-native-randombytes'
project(':react-native-randombytes').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-randombytes/android')
-include ':react-native-fs'
-project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-fs/android')
-include ':react-native-radial-gradient'
-project(':react-native-radial-gradient').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-radial-gradient/android')
include ':react-native-config'
project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android')
include ':react-native-google-cast'
project(':react-native-google-cast').projectDir = new File(rootProject.projectDir, '../../../node_modules/react-native-google-cast/android')
-include ':react-native-notifications'
-project(':react-native-notifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/lib/android/app')
include ':app'
includeBuild('../../../node_modules/@react-native/gradle-plugin')
diff --git a/packages/mobile/index.js b/packages/mobile/index.js
index 4911004dffd..8519a4eb676 100644
--- a/packages/mobile/index.js
+++ b/packages/mobile/index.js
@@ -20,6 +20,10 @@ import ViewReactNativeStyleAttributes from 'react-native/Libraries/Components/Vi
ViewReactNativeStyleAttributes.scaleY = true
import { AppRegistry, LogBox, Text, TextInput } from 'react-native'
import TrackPlayer from 'react-native-track-player'
+import {
+ getMessaging,
+ setBackgroundMessageHandler
+} from '@react-native-firebase/messaging'
import { Crypto } from '@peculiar/webcrypto'
import { name as appName } from './app.json'
@@ -52,3 +56,7 @@ TextInput.defaultProps.allowFontScaling = false
AppRegistry.registerComponent(appName, () => App)
TrackPlayer.registerPlaybackService(() => require('./audio-service'))
+// On Android, firebase starts a headless JS task for each push that arrives
+// while the app is in the background. The OS displays the push itself, so
+// there is nothing to do here.
+setBackgroundMessageHandler(getMessaging(), async () => {})
diff --git a/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj b/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj
index 5eca50b1fcb..b4fbde5fe32 100644
--- a/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj
+++ b/packages/mobile/ios/AudiusReactNative.xcodeproj/project.pbxproj
@@ -261,6 +261,7 @@
CBD3B687421F14165031EAF0 /* [CP] Embed Pods Frameworks */,
B73C21C532E1201FEFBAECDA /* [CP] Copy Pods Resources */,
23A43AB82E37E73000CDCDF2 /* Embed Foundation Extensions */,
+ CB6D10CC025B9D671A7354C5 /* [CP-User] [RNFB] Core Configuration */,
);
buildRules = (
);
@@ -434,6 +435,19 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AudiusReactNative/Pods-AudiusReactNative-resources.sh\"\n";
showEnvVarsInLog = 0;
};
+ CB6D10CC025B9D671A7354C5 /* [CP-User] [RNFB] Core Configuration */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)",
+ );
+ name = "[CP-User] [RNFB] Core Configuration";
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##########################################################################\n##########################################################################\n#\n# NOTE THAT IF YOU CHANGE THIS FILE YOU MUST RUN pod install AFTERWARDS\n#\n# This file is installed as an Xcode build script in the project file\n# by cocoapods, and you will not see your changes until you pod install\n#\n##########################################################################\n##########################################################################\n\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"note: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"note: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"note: -> RNFB build script started\"\necho \"note: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"note: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"note: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n if ! _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\"); then\n echo \"error: Failed to parse firebase.json, check for syntax errors.\"\n exit 1\n fi\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"error: python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"note: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"note: <- RNFB build script finished\"\n";
+ };
CBD3B687421F14165031EAF0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
diff --git a/packages/mobile/ios/AudiusReactNative/AppDelegate.mm b/packages/mobile/ios/AudiusReactNative/AppDelegate.mm
index c82bdf3a6ff..d985f2a5acb 100644
--- a/packages/mobile/ios/AudiusReactNative/AppDelegate.mm
+++ b/packages/mobile/ios/AudiusReactNative/AppDelegate.mm
@@ -8,7 +8,8 @@
#import
#import
#import
-#import "RNNotifications.h"
+#import
+#import "NotifeeCore+UNUserNotificationCenter.h"
@implementation AppDelegate {
NSDictionary *_launchOptions;
@@ -17,7 +18,12 @@ @implementation AppDelegate {
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
- [RNNotifications startMonitorNotifications];
+ [FIRApp configure];
+ // Our iOS pushes come straight from APNs (SNS), not FCM. Firebase messaging
+ // only reports taps on FCM pushes and passes the rest to the delegate it
+ // replaced, so notifee has to be that delegate to report them to JS.
+ // Both libraries install themselves after launch, so make notifee go first.
+ [[NotifeeCoreUNUserNotificationCenter instance] observe];
NSString *receiverAppID = @"222B31C8";
GCKDiscoveryCriteria *criteria = [[GCKDiscoveryCriteria alloc] initWithApplicationID:receiverAppID];
GCKCastOptions* options = [[GCKCastOptions alloc] initWithDiscoveryCriteria:criteria];
@@ -72,9 +78,10 @@ - (UIWindow *)loadReactNativeWindowInScene:(UIWindowScene *)scene
return window;
}
-// Scene apps get the launch URL, user activity and notification through the
-// connection options rather than launchOptions. Linking.getInitialURL and
-// Notifications.getInitialNotification read them from the bridge's launchOptions.
+// Scene apps get the launch URL and user activity through the connection
+// options rather than launchOptions. Linking.getInitialURL reads them from the
+// bridge's launchOptions. A tapped notification needs no copy: the notification
+// center delegate still receives it at launch.
- (NSDictionary *)launchOptionsWithConnectionOptions:(UISceneConnectionOptions *)connectionOptions
{
NSMutableDictionary *launchOptions = [NSMutableDictionary dictionaryWithDictionary:_launchOptions ?: @{}];
@@ -92,11 +99,6 @@ - (NSDictionary *)launchOptionsWithConnectionOptions:(UISceneConnectionOptions *
};
}
- NSDictionary *notification = connectionOptions.notificationResponse.notification.request.content.userInfo;
- if (notification != nil && launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] == nil) {
- launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] = notification;
- }
-
return launchOptions;
}
@@ -120,16 +122,4 @@ - (NSURL *)bundleURL
#endif
}
-- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
- [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
-}
-
-- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
- [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];
-}
-
-- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
- [RNNotifications didReceiveBackgroundNotification:userInfo withCompletionHandler:completionHandler];
-}
-
@end
diff --git a/packages/mobile/ios/AudiusReactNative/Info.plist b/packages/mobile/ios/AudiusReactNative/Info.plist
index 60fe39b4f65..244cdfe018c 100644
--- a/packages/mobile/ios/AudiusReactNative/Info.plist
+++ b/packages/mobile/ios/AudiusReactNative/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.1.199
+ 1.1.200
CFBundleSignature
????
CFBundleURLTypes
diff --git a/packages/mobile/ios/Podfile b/packages/mobile/ios/Podfile
index c5d685c23e7..2c519c4ca74 100644
--- a/packages/mobile/ios/Podfile
+++ b/packages/mobile/ios/Podfile
@@ -31,12 +31,16 @@ project 'AudiusReactNative',
'Prod.Release' => :release
target 'AudiusReactNative' do
- pod 'SRSRadialGradient', :path => '../../../node_modules/react-native-radial-gradient/ios'
pod 'ffmpeg-kit-ios-full-gpl', :podspec => './ffmpeg-kit-ios-full-gpl.podspec'
pod 'ffmpeg-kit-react-native', :subspecs => ['full-gpl'], :podspec => '../../../node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec'
pod 'nSure'
+ # Firebase's Swift pods import these, and pods link statically here, so they
+ # need module maps.
+ pod 'GoogleUtilities', :modular_headers => true
+ pod 'nanopb', :modular_headers => true
+
config = use_native_modules!
use_react_native!(
@@ -140,6 +144,24 @@ target 'AudiusReactNative' do
end
end
+ # SwiftAudioEx 1.x only shifts the current index for an insert in front
+ # of the current item when the queue already has more than one item. With
+ # a single item, inserting at 0 leaves the index one slot early, so the
+ # player replays the current track instead of advancing. Fixed upstream
+ # on main but not released. Fail if the line changes so an update can't
+ # silently drop the patch.
+ queue_manager = File.join(__dir__, 'Pods/SwiftAudioEx/Sources/SwiftAudioEx/QueueManager.swift')
+ buggy_line = 'if (self.items.count > 1 && currentIndex >= index) {'
+ fixed_line = 'if (self.items.count > 0 && currentIndex >= index) {'
+ text = File.read(queue_manager)
+ if text.include?(buggy_line)
+ File.chmod(0644, queue_manager)
+ File.write(queue_manager, text.sub(buggy_line, fixed_line))
+ Pod::UI.puts "Patched SwiftAudioEx QueueManager.swift: shift current index when inserting into a one-item queue".green
+ elsif !text.include?(fixed_line)
+ raise "SwiftAudioEx QueueManager.swift no longer contains '#{buggy_line}'. Check whether the insert index fix is still needed and update the Podfile patch."
+ end
+
end
end
diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock
index f9c1ce8ba5f..7d3f60ecc4c 100644
--- a/packages/mobile/ios/Podfile.lock
+++ b/packages/mobile/ios/Podfile.lock
@@ -3,23 +3,115 @@ PODS:
- React-Core
- Base64 (1.1.2)
- boost (1.84.0)
- - BVLinearGradient (2.8.3):
- - React-Core
- CodePush (12.3.2):
- Base64 (~> 1.1)
- JWT (~> 3.0.0-beta.12)
- React-Core
- SSZipArchive (~> 2.5.5)
- DoubleConversion (1.1.6)
+ - dr-pogodin-react-native-fs (2.32.1):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-hermes
+ - React-ImageManager
+ - React-jsi
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-renderercss
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- fast_float (6.1.4)
- FBLazyVector (0.79.5)
- ffmpeg-kit-ios-full-gpl (6.0)
- ffmpeg-kit-react-native/full-gpl (6.0.2):
- ffmpeg-kit-ios-full-gpl (= 6.0)
- React-Core
+ - Firebase/CoreOnly (12.10.0):
+ - FirebaseCore (~> 12.10.0)
+ - Firebase/Messaging (12.10.0):
+ - Firebase/CoreOnly
+ - FirebaseMessaging (~> 12.10.0)
+ - FirebaseCore (12.10.0):
+ - FirebaseCoreInternal (~> 12.10.0)
+ - GoogleUtilities/Environment (~> 8.1)
+ - GoogleUtilities/Logger (~> 8.1)
+ - FirebaseCoreExtension (12.10.0):
+ - FirebaseCore (~> 12.10.0)
+ - FirebaseCoreInternal (12.10.0):
+ - "GoogleUtilities/NSData+zlib (~> 8.1)"
+ - FirebaseInstallations (12.10.0):
+ - FirebaseCore (~> 12.10.0)
+ - GoogleUtilities/Environment (~> 8.1)
+ - GoogleUtilities/UserDefaults (~> 8.1)
+ - PromisesObjC (~> 2.4)
+ - FirebaseMessaging (12.10.0):
+ - FirebaseCore (~> 12.10.0)
+ - FirebaseInstallations (~> 12.10.0)
+ - GoogleDataTransport (~> 10.1)
+ - GoogleUtilities/AppDelegateSwizzler (~> 8.1)
+ - GoogleUtilities/Environment (~> 8.1)
+ - GoogleUtilities/Reachability (~> 8.1)
+ - GoogleUtilities/UserDefaults (~> 8.1)
+ - nanopb (~> 3.30910.0)
- fmt (11.0.2)
- glog (0.3.5)
- google-cast-sdk-dynamic-xcframework-no-bluetooth (4.7.1)
+ - GoogleDataTransport (10.1.1):
+ - nanopb (~> 3.30910.0)
+ - PromisesObjC (~> 2.4)
+ - GoogleUtilities (8.1.2):
+ - GoogleUtilities/AppDelegateSwizzler (= 8.1.2)
+ - GoogleUtilities/Environment (= 8.1.2)
+ - GoogleUtilities/Logger (= 8.1.2)
+ - GoogleUtilities/MethodSwizzler (= 8.1.2)
+ - GoogleUtilities/Network (= 8.1.2)
+ - "GoogleUtilities/NSData+zlib (= 8.1.2)"
+ - GoogleUtilities/Privacy (= 8.1.2)
+ - GoogleUtilities/Reachability (= 8.1.2)
+ - GoogleUtilities/SwizzlerTestHelpers (= 8.1.2)
+ - GoogleUtilities/UserDefaults (= 8.1.2)
+ - GoogleUtilities/AppDelegateSwizzler (8.1.2):
+ - GoogleUtilities/Environment
+ - GoogleUtilities/Logger
+ - GoogleUtilities/Network
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Environment (8.1.2):
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Logger (8.1.2):
+ - GoogleUtilities/Environment
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/MethodSwizzler (8.1.2):
+ - GoogleUtilities/Logger
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Network (8.1.2):
+ - GoogleUtilities/Logger
+ - "GoogleUtilities/NSData+zlib"
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Reachability
+ - "GoogleUtilities/NSData+zlib (8.1.2)":
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/Privacy (8.1.2)
+ - GoogleUtilities/Reachability (8.1.2):
+ - GoogleUtilities/Logger
+ - GoogleUtilities/Privacy
+ - GoogleUtilities/SwizzlerTestHelpers (8.1.2):
+ - GoogleUtilities/MethodSwizzler
+ - GoogleUtilities/UserDefaults (8.1.2):
+ - GoogleUtilities/Logger
+ - GoogleUtilities/Privacy
- hermes-engine (0.79.5):
- hermes-engine/Pre-built (= 0.79.5)
- hermes-engine/Pre-built (0.79.5)
@@ -51,6 +143,11 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - nanopb (3.30910.0):
+ - nanopb/decode (= 3.30910.0)
+ - nanopb/encode (= 3.30910.0)
+ - nanopb/decode (3.30910.0)
+ - nanopb/encode (3.30910.0)
- nSure (1.3.13)
- PromisesObjC (2.4.0)
- RCT-Folly (2024.11.18.00):
@@ -1497,8 +1594,6 @@ PODS:
- Yoga
- react-native-netinfo (11.4.1):
- React-Core
- - react-native-notifications (5.1.0):
- - React-Core
- react-native-pager-view (6.7.1):
- DoubleConversion
- glog
@@ -1553,9 +1648,9 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - react-native-track-player (4.0.1):
+ - react-native-track-player (4.1.2):
- React-Core
- - SwiftAudioEx (= 1.0.0)
+ - SwiftAudioEx (= 1.1.0)
- react-native-version-number (0.3.6):
- React
- react-native-video (6.18.0):
@@ -1987,8 +2082,9 @@ PODS:
- Yoga
- RNDateTimePicker (8.3.0):
- React-Core
- - RNFlashList (1.8.3):
+ - RNFBApp (24.1.1):
- DoubleConversion
+ - Firebase/CoreOnly (= 12.10.0)
- glog
- hermes-engine
- RCT-Folly (= 2024.11.18.00)
@@ -2011,8 +2107,57 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- - RNFS (2.20.0):
+ - RNFBMessaging (24.1.1):
+ - DoubleConversion
+ - Firebase/Messaging (= 12.10.0)
+ - FirebaseCoreExtension
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
- React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-hermes
+ - React-ImageManager
+ - React-jsi
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-renderercss
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - RNFBApp
+ - Yoga
+ - RNFlashList (1.8.3):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.11.18.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-hermes
+ - React-ImageManager
+ - React-jsi
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-renderercss
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- RNGestureHandler (2.25.0):
- DoubleConversion
- glog
@@ -2046,6 +2191,11 @@ PODS:
- React-Core
- React-RCTImage
- TOCropViewController (~> 2.7.4)
+ - RNNotifee (9.1.8):
+ - React-Core
+ - RNNotifee/NotifeeCore (= 9.1.8)
+ - RNNotifee/NotifeeCore (9.1.8):
+ - React-Core
- RNPermissions (5.4.4):
- React-Core
- RNReactNativeHapticFeedback (2.3.3):
@@ -2264,10 +2414,8 @@ PODS:
- SnapSDK/SCSDKLoginKit (1.15.0):
- SnapSDK/SCSDKCoreKit
- SocketRocket (0.7.1)
- - SRSRadialGradient (1.1.4):
- - React
- SSZipArchive (2.5.5)
- - SwiftAudioEx (1.0.0)
+ - SwiftAudioEx (1.1.0)
- tiktok-opensdk-react-native (0.10.7):
- DoubleConversion
- glog
@@ -2308,17 +2456,19 @@ PODS:
DEPENDENCIES:
- "amplitude-react-native (from `../../../node_modules/@amplitude/analytics-react-native`)"
- boost (from `../../../node_modules/react-native/third-party-podspecs/boost.podspec`)
- - BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- "CodePush (from `../../../node_modules/@bravemobile/react-native-code-push`)"
- DoubleConversion (from `../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
+ - "dr-pogodin-react-native-fs (from `../../../node_modules/@dr.pogodin/react-native-fs`)"
- fast_float (from `../../../node_modules/react-native/third-party-podspecs/fast_float.podspec`)
- FBLazyVector (from `../../../node_modules/react-native/Libraries/FBLazyVector`)
- ffmpeg-kit-ios-full-gpl (from `./ffmpeg-kit-ios-full-gpl.podspec`)
- ffmpeg-kit-react-native/full-gpl (from `../../../node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec`)
- fmt (from `../../../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../../../node_modules/react-native/third-party-podspecs/glog.podspec`)
+ - GoogleUtilities
- hermes-engine (from `../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- lottie-react-native (from `../node_modules/lottie-react-native`)
+ - nanopb
- nSure
- RCT-Folly (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
@@ -2364,13 +2514,12 @@ DEPENDENCIES:
- react-native-in-app-review (from `../node_modules/react-native-in-app-review`)
- react-native-keyboard-controller (from `../../../node_modules/react-native-keyboard-controller`)
- "react-native-netinfo (from `../../../node_modules/@react-native-community/netinfo`)"
- - react-native-notifications (from `../node_modules/react-native-notifications`)
- react-native-pager-view (from `../../../node_modules/react-native-pager-view`)
- react-native-randombytes (from `../../../node_modules/react-native-randombytes`)
- react-native-restart (from `../node_modules/react-native-restart`)
- react-native-safe-area-context (from `../../../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- - react-native-track-player (from `../node_modules/react-native-track-player`)
+ - react-native-track-player (from `../../../node_modules/react-native-track-player`)
- react-native-version-number (from `../../../node_modules/react-native-version-number`)
- react-native-video (from `../../../node_modules/react-native-video`)
- react-native-view-shot (from `../node_modules/react-native-view-shot`)
@@ -2411,10 +2560,12 @@ DEPENDENCIES:
- "RNCClipboard (from `../../../node_modules/@react-native-clipboard/clipboard`)"
- "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
+ - "RNFBApp (from `../../../node_modules/@react-native-firebase/app`)"
+ - "RNFBMessaging (from `../../../node_modules/@react-native-firebase/messaging`)"
- "RNFlashList (from `../../../node_modules/@shopify/flash-list`)"
- - RNFS (from `../../../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
+ - "RNNotifee (from `../../../node_modules/@notifee/react-native`)"
- RNPermissions (from `../../../node_modules/react-native-permissions`)
- RNReactNativeHapticFeedback (from `../../../node_modules/react-native-haptic-feedback`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
@@ -2423,16 +2574,24 @@ DEPENDENCIES:
- RNSVG (from `../node_modules/react-native-svg`)
- RNZipArchive (from `../../../node_modules/react-native-zip-archive`)
- "snap-kit-react-native (from `../../../node_modules/@snapchat/snap-kit-react-native`)"
- - SRSRadialGradient (from `../../../node_modules/react-native-radial-gradient/ios`)
- tiktok-opensdk-react-native (from `../../../node_modules/tiktok-opensdk-react-native`)
- Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
trunk:
- Base64
+ - Firebase
+ - FirebaseCore
+ - FirebaseCoreExtension
+ - FirebaseCoreInternal
+ - FirebaseInstallations
+ - FirebaseMessaging
- google-cast-sdk-dynamic-xcframework-no-bluetooth
+ - GoogleDataTransport
+ - GoogleUtilities
- JWT
- lottie-ios
+ - nanopb
- nSure
- PromisesObjC
- SnapSDK
@@ -2448,12 +2607,12 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/@amplitude/analytics-react-native"
boost:
:podspec: "../../../node_modules/react-native/third-party-podspecs/boost.podspec"
- BVLinearGradient:
- :path: "../node_modules/react-native-linear-gradient"
CodePush:
:path: "../../../node_modules/@bravemobile/react-native-code-push"
DoubleConversion:
:podspec: "../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
+ dr-pogodin-react-native-fs:
+ :path: "../../../node_modules/@dr.pogodin/react-native-fs"
fast_float:
:podspec: "../../../node_modules/react-native/third-party-podspecs/fast_float.podspec"
FBLazyVector:
@@ -2555,8 +2714,6 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-keyboard-controller"
react-native-netinfo:
:path: "../../../node_modules/@react-native-community/netinfo"
- react-native-notifications:
- :path: "../node_modules/react-native-notifications"
react-native-pager-view:
:path: "../../../node_modules/react-native-pager-view"
react-native-randombytes:
@@ -2568,7 +2725,7 @@ EXTERNAL SOURCES:
react-native-slider:
:path: "../node_modules/@react-native-community/slider"
react-native-track-player:
- :path: "../node_modules/react-native-track-player"
+ :path: "../../../node_modules/react-native-track-player"
react-native-version-number:
:path: "../../../node_modules/react-native-version-number"
react-native-video:
@@ -2649,14 +2806,18 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-masked-view/masked-view"
RNDateTimePicker:
:path: "../node_modules/@react-native-community/datetimepicker"
+ RNFBApp:
+ :path: "../../../node_modules/@react-native-firebase/app"
+ RNFBMessaging:
+ :path: "../../../node_modules/@react-native-firebase/messaging"
RNFlashList:
:path: "../../../node_modules/@shopify/flash-list"
- RNFS:
- :path: "../../../node_modules/react-native-fs"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNImageCropPicker:
:path: "../node_modules/react-native-image-crop-picker"
+ RNNotifee:
+ :path: "../../../node_modules/@notifee/react-native"
RNPermissions:
:path: "../../../node_modules/react-native-permissions"
RNReactNativeHapticFeedback:
@@ -2673,8 +2834,6 @@ EXTERNAL SOURCES:
:path: "../../../node_modules/react-native-zip-archive"
snap-kit-react-native:
:path: "../../../node_modules/@snapchat/snap-kit-react-native"
- SRSRadialGradient:
- :path: "../../../node_modules/react-native-radial-gradient/ios"
tiktok-opensdk-react-native:
:path: "../../../node_modules/tiktok-opensdk-react-native"
Yoga:
@@ -2684,20 +2843,29 @@ SPEC CHECKSUMS:
amplitude-react-native: 9d57e1bcc4175039e36283390aa3daeaea9441a5
Base64: cecfb41a004124895a7bcee567a89bae5a89d49b
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
- BVLinearGradient: 880f91a7854faff2df62518f0281afb1c60d49a3
CodePush: f52aac93456bc76d8219ca3673cc7ce81470429d
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
+ dr-pogodin-react-native-fs: 69314b5d4e9f561657febeae7a250c2bb8561874
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
FBLazyVector: d2a9cd223302b6c9aa4aa34c1a775e9db609eb52
ffmpeg-kit-ios-full-gpl: 7d416729f7b3604b64cee2a752c42608a8d228e0
ffmpeg-kit-react-native: 3cea88c9c5cfad62e1465279ea7d800dfbba3b00
+ Firebase: 99f203d3a114c6ba591f3b32263a9626e450af65
+ FirebaseCore: f4428e22415ea3b3eca652c2098413dabf2a23a9
+ FirebaseCoreExtension: 3473a6ec16a91aee29fb75994a86c0220d2cddf3
+ FirebaseCoreInternal: e7bbaeb00ab73011298f35ed223aa7371e212948
+ FirebaseInstallations: 047343aa91fd6a1ebfa3eb374ddecf36a8aaddfd
+ FirebaseMessaging: ed18fb50634e6e85b5d3e77e628c21e2c928cc53
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
google-cast-sdk-dynamic-xcframework-no-bluetooth: 1fa9e267df3fd6f8a1c6e3345142ca5286297968
+ GoogleDataTransport: a24e58982ab3ba2f64d79613e027fe7f57e88539
+ GoogleUtilities: 766ace00c6b10d8148408f329d10c4f051931850
hermes-engine: f03b0e06d3882d71e67e45b073bb827da1a21aae
JWT: ef71dfb03e1f842081e64dc42eef0e164f35d251
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
lottie-react-native: 173db75996ba0ebc176424e04cfc0cc0175b307d
+ nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
nSure: 2fc3fc973c44aa0be9a3446f84cb514adc475205
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809
@@ -2742,13 +2910,12 @@ SPEC CHECKSUMS:
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
react-native-keyboard-controller: c8c8a6ce30420ca67fae84dd1a9f9a861a988e2a
react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac
- react-native-notifications: 4601a5a8db4ced6ae7cfc43b44d35fe437ac50c4
react-native-pager-view: e4cf30bc28e3ccf864f6cfb90590d8cea13b7393
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162
react-native-safe-area-context: e48706fb09fde89f82e12bfb49b140cfdcb761f7
react-native-slider: f7d96d39d20d367a20d7af5bdae138aed3b2e75f
- react-native-track-player: 97d76dbbd35f27cc709e5f04540615e54264b3f9
+ react-native-track-player: 24ecd55d64da8a6471ba96484b67bc9cee6fc629
react-native-version-number: b415bbec6a13f2df62bf978e85bc0d699462f37f
react-native-video: 5efc3c2185906e2c49c81e7e2c763193f5c5e419
react-native-view-shot: 6b7ed61d77d88580fed10954d45fad0eb2d47688
@@ -2789,10 +2956,12 @@ SPEC CHECKSUMS:
RNCClipboard: dfeb43751adff21e588657b5b6c888c72f3aa68e
RNCMaskedView: dc63cbc7b2f614b8b4526e11ed677b36e92cd9fb
RNDateTimePicker: a793ed8822283f576dd0a205a0916c5098c2611f
+ RNFBApp: ef18c25022685d22dfc058983c2f287b1838e848
+ RNFBMessaging: fae02a2b3354255cf16e7c598d282a6d9531e5c7
RNFlashList: c68de6a14cce0a262b0a4fabdea690d9f3361063
- RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: 1070e4e691b23028309d796cc30652d9875138c7
RNImageCropPicker: 64b6bec9ec185ee59b71a93d7058816bd0e1916e
+ RNNotifee: 4a6ee5c7deaf00e005050052d73ee6315dff7ec9
RNPermissions: 0a18cdcfdd1773b969f817ce20d47c2b74724af8
RNReactNativeHapticFeedback: b837dd570ef9c316ed9f93274a2cdb152a8d78ab
RNReanimated: b61d01db005bc4114b04e7a88c3f32cbd3865dac
@@ -2803,15 +2972,14 @@ SPEC CHECKSUMS:
snap-kit-react-native: 751006199818fccb38c8a01196495167bc25e9fb
SnapSDK: 1e68aad748e080f49e3802559b3b76026666ef62
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
- SRSRadialGradient: 8363f6c3ce63636f5ffee74a7016f5c84a6df0a1
SSZipArchive: c69881e8ac5521f0e622291387add5f60f30f3c4
- SwiftAudioEx: 6f511018b7a0fdfd14ed1bb4081f953588245cc0
+ SwiftAudioEx: f6aa653770f3a0d3851edaf8d834a30aee4a7646
tiktok-opensdk-react-native: f6b1a34bb4c1889c0dae5838effe484c673ea0c0
TikTokOpenSDKCore: e6f34e48bd6e85e4d94f9c04782c13d5defafb55
TikTokOpenShareSDK: a7da017bc66c28d0aefea9342c0cfcc7e52ea2b7
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
Yoga: adb397651e1c00672c12e9495babca70777e411e
-PODFILE CHECKSUM: 7750e6bb179cd027ed6cf7522c2da1f14bef882b
+PODFILE CHECKSUM: 2f4f4e3fbbf9bb9553299f2691874304461629c9
COCOAPODS: 1.15.2
diff --git a/packages/mobile/ios/PrivacyInfo.xcprivacy b/packages/mobile/ios/PrivacyInfo.xcprivacy
index 906f4fa6116..657971cb2ec 100644
--- a/packages/mobile/ios/PrivacyInfo.xcprivacy
+++ b/packages/mobile/ios/PrivacyInfo.xcprivacy
@@ -6,35 +6,39 @@
NSPrivacyAccessedAPIType
- NSPrivacyAccessedAPICategoryFileTimestamp
+ NSPrivacyAccessedAPICategoryUserDefaults
NSPrivacyAccessedAPITypeReasons
- C617.1
- 3B52.1
+ CA92.1
+ 1C8F.1
+ C56D.1
NSPrivacyAccessedAPIType
- NSPrivacyAccessedAPICategoryUserDefaults
+ NSPrivacyAccessedAPICategoryFileTimestamp
NSPrivacyAccessedAPITypeReasons
- CA92.1
+ C617.1
+ 3B52.1
+ 0A2A.1
NSPrivacyAccessedAPIType
- NSPrivacyAccessedAPICategorySystemBootTime
+ NSPrivacyAccessedAPICategoryDiskSpace
NSPrivacyAccessedAPITypeReasons
- 35F9.1
+ 85F4.1
+ E174.1
NSPrivacyAccessedAPIType
- NSPrivacyAccessedAPICategoryDiskSpace
+ NSPrivacyAccessedAPICategorySystemBootTime
NSPrivacyAccessedAPITypeReasons
- E174.1
+ 35F9.1
diff --git a/packages/mobile/metro.config.js b/packages/mobile/metro.config.js
index 025e82786d1..f31d86dfd69 100644
--- a/packages/mobile/metro.config.js
+++ b/packages/mobile/metro.config.js
@@ -149,9 +149,15 @@ const config = {
// Aliases for '@audius/web' to allow for absolute paths
...getClientAliases(),
+ // react-native-gifted-charts requires this at module load
+ 'react-native-linear-gradient': path.resolve(
+ __dirname,
+ 'src/shims/react-native-linear-gradient.ts'
+ ),
+
// Various polyfills to enable @audius/sdk to run in react-native
child_process: emptyPolyfill,
- fs: resolveModule('react-native-fs'),
+ fs: resolveModule('@dr.pogodin/react-native-fs'),
net: emptyPolyfill,
tls: resolveModule('tls-browserify')
},
diff --git a/packages/mobile/package.json b/packages/mobile/package.json
index 9fd700ca66a..0a7579eeb93 100644
--- a/packages/mobile/package.json
+++ b/packages/mobile/package.json
@@ -1,6 +1,6 @@
{
"name": "@audius/mobile",
- "version": "1.5.186",
+ "version": "1.5.187",
"private": true,
"scripts": {
"android:dev": "ENVFILE=.env.dev turbo run android -- --mode=prodDebug",
@@ -50,6 +50,7 @@
"@audius/sdk": "*",
"@bravemobile/react-native-code-push": "^12.3.2",
"@coinflowlabs/react-native": "4.5.2",
+ "@dr.pogodin/react-native-fs": "2.32.1",
"@ebay/nice-modal-react": "^1.2.13",
"@emotion/native": "^11.11.0",
"@emotion/react": "11.14.0",
@@ -58,6 +59,7 @@
"@hcaptcha/react-native-hcaptcha": "1.6.0",
"@noble/hashes": "1.1.5",
"@noble/secp256k1": "1.7.0",
+ "@notifee/react-native": "^9.1.8",
"@optimizely/optimizely-sdk": "4.9.2",
"@peculiar/webcrypto": "1.4.3",
"@react-native-async-storage/async-storage": "^2.0.0",
@@ -68,6 +70,8 @@
"@react-native-community/netinfo": "^11.3.3",
"@react-native-community/slider": "4.5.0",
"@react-native-documents/picker": "^10.1.2",
+ "@react-native-firebase/app": "^24.1.1",
+ "@react-native-firebase/messaging": "^24.1.1",
"@react-native-masked-view/masked-view": "^0.3.2",
"@react-navigation/bottom-tabs": "7.19.2",
"@react-navigation/core": "7.22.1",
@@ -118,7 +122,6 @@
"react-native-draggable-flatlist": "4.0.1",
"react-native-dynamic": "1.0.0",
"react-native-fast-crypto": "3.0.0",
- "react-native-fs": "^2.18.0",
"react-native-gesture-handler": "2.25.0",
"react-native-get-random-values": "1.10.0",
"react-native-gifted-charts": "1.4.46",
@@ -131,15 +134,12 @@
"react-native-intersection-observer": "0.2.1",
"react-native-keyboard-aware-scroll-view": "0.9.5",
"react-native-keyboard-controller": "1.19.0",
- "react-native-linear-gradient": "2.8.3",
"react-native-markdown-display": "7.0.2",
"react-native-modal": "^13.0.0",
"react-native-modal-datetime-picker": "18.0.0",
- "react-native-notifications": "5.1.0",
"react-native-pager-view": "6.7.1",
"react-native-permissions": "5.4.4",
"react-native-qrcode-svg": "6.2.0",
- "react-native-radial-gradient": "1.1.4",
"react-native-randombytes": "3.6.1",
"react-native-reanimated": "~3.19.5",
"react-native-restart": "0.0.27",
diff --git a/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/BottomTabBarButton.tsx b/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/BottomTabBarButton.tsx
index 2eca59fcabb..00b5fd84458 100644
--- a/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/BottomTabBarButton.tsx
+++ b/packages/mobile/src/components/bottom-tab-bar/bottom-tab-bar-buttons/BottomTabBarButton.tsx
@@ -5,9 +5,10 @@ import { useTheme } from '@emotion/react'
import type { LottieViewProps } from 'lottie-react-native'
import LottieView from 'lottie-react-native'
import { Pressable, StyleSheet } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
import { usePrevious } from 'react-use'
+import { LinearGradient } from '@audius/harmony-native'
+
import { BOTTOM_BAR_BUTTON_HEIGHT, BOTTOM_BAR_TOP_PADDING } from '../constants'
export type BaseBottomTabBarButtonProps = {
diff --git a/packages/mobile/src/components/card/Card.tsx b/packages/mobile/src/components/card/Card.tsx
index cf8cab3819f..8f5df1c0111 100644
--- a/packages/mobile/src/components/card/Card.tsx
+++ b/packages/mobile/src/components/card/Card.tsx
@@ -3,9 +3,8 @@ import type { ComponentType, ReactNode } from 'react'
import type { User } from '@audius/common/models'
import type { StyleProp, TextStyle, ViewStyle } from 'react-native'
import { Text, View } from 'react-native'
-import type { LinearGradientProps } from 'react-native-linear-gradient'
-import type { ImageProps } from '@audius/harmony-native'
+import type { LinearGradientProps, ImageProps } from '@audius/harmony-native'
import type { TileProps } from 'app/components/core'
import { Tile } from 'app/components/core'
import { UserBadges } from 'app/components/user-badges'
diff --git a/packages/mobile/src/components/challenge-rewards-drawer/ReferralLinkCopyButton.tsx b/packages/mobile/src/components/challenge-rewards-drawer/ReferralLinkCopyButton.tsx
index 230199cadce..8c05a3b5693 100644
--- a/packages/mobile/src/components/challenge-rewards-drawer/ReferralLinkCopyButton.tsx
+++ b/packages/mobile/src/components/challenge-rewards-drawer/ReferralLinkCopyButton.tsx
@@ -2,9 +2,8 @@ import { useCallback } from 'react'
import Clipboard from '@react-native-clipboard/clipboard'
import { Animated, View, TouchableHighlight } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
-import { IconCopy } from '@audius/harmony-native'
+import { LinearGradient, IconCopy } from '@audius/harmony-native'
import Text from 'app/components/text'
import { usePressScaleAnimation } from 'app/hooks/usePressScaleAnimation'
import { useToast } from 'app/hooks/useToast'
@@ -70,9 +69,7 @@ export const ReferralLinkCopyButton = ({
>
diff --git a/packages/mobile/src/components/copy-text-tile/CopyTextTile.tsx b/packages/mobile/src/components/copy-text-tile/CopyTextTile.tsx
index 584a39c8888..443044791a0 100644
--- a/packages/mobile/src/components/copy-text-tile/CopyTextTile.tsx
+++ b/packages/mobile/src/components/copy-text-tile/CopyTextTile.tsx
@@ -2,9 +2,8 @@ import { useCallback } from 'react'
import Clipboard from '@react-native-clipboard/clipboard'
import { View } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
-import { IconCopy } from '@audius/harmony-native'
+import { LinearGradient, IconCopy } from '@audius/harmony-native'
import { Tile, Text } from 'app/components/core'
import { useToast } from 'app/hooks/useToast'
import { makeStyles } from 'app/styles'
diff --git a/packages/mobile/src/components/core/GradientIcon.tsx b/packages/mobile/src/components/core/GradientIcon.tsx
index bfa2dc14cdc..5c93856ecf1 100644
--- a/packages/mobile/src/components/core/GradientIcon.tsx
+++ b/packages/mobile/src/components/core/GradientIcon.tsx
@@ -1,9 +1,9 @@
import type { ComponentType } from 'react'
import MaskedView from '@react-native-masked-view/masked-view'
-import LinearGradient from 'react-native-linear-gradient'
import type { SvgProps } from 'react-native-svg'
+import { LinearGradient } from '@audius/harmony-native'
import { useThemeColors } from 'app/utils/theme'
type GradientIconProps = SvgProps & {
diff --git a/packages/mobile/src/components/core/GradientText.tsx b/packages/mobile/src/components/core/GradientText.tsx
index 51aa9d3b85e..e6b13b8e995 100644
--- a/packages/mobile/src/components/core/GradientText.tsx
+++ b/packages/mobile/src/components/core/GradientText.tsx
@@ -1,8 +1,8 @@
import MaskedView from '@react-native-masked-view/masked-view'
import type { TextProps } from 'react-native'
-import type { LinearGradientProps } from 'react-native-linear-gradient'
-import LinearGradient from 'react-native-linear-gradient'
+import type { LinearGradientProps } from '@audius/harmony-native'
+import { LinearGradient } from '@audius/harmony-native'
import Text from 'app/components/text'
import { useThemeColors } from 'app/utils/theme'
diff --git a/packages/mobile/src/components/core/LockedStatusBadge.tsx b/packages/mobile/src/components/core/LockedStatusBadge.tsx
index f5a959bbc5e..ed91330ff7c 100644
--- a/packages/mobile/src/components/core/LockedStatusBadge.tsx
+++ b/packages/mobile/src/components/core/LockedStatusBadge.tsx
@@ -1,7 +1,11 @@
import { View } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
-import { IconLock, IconLockUnlocked, useTheme } from '@audius/harmony-native'
+import {
+ LinearGradient,
+ IconLock,
+ IconLockUnlocked,
+ useTheme
+} from '@audius/harmony-native'
import { makeStyles } from 'app/styles'
import { spacing } from 'app/styles/spacing'
import { useColor } from 'app/utils/theme'
diff --git a/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx b/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx
index f6e2ea32ae9..ae2cebf6c93 100644
--- a/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx
+++ b/packages/mobile/src/components/lineup-tile/LineupTileAccessStatus.tsx
@@ -14,11 +14,11 @@ import {
} from '@audius/common/store'
import { USDC } from '@audius/fixed-decimal'
import { TouchableOpacity } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
import { useDispatch, useSelector } from 'react-redux'
import type { FlexProps } from '@audius/harmony-native'
import {
+ LinearGradient,
Button,
Flex,
IconLock,
diff --git a/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx b/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx
index f9a018ea10a..c7da89c78fd 100644
--- a/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx
+++ b/packages/mobile/src/components/now-playing-drawer/TrackingBar.tsx
@@ -4,11 +4,11 @@ import { useCurrentTrack } from '@audius/common/hooks'
import { playbackSelectors, playbackRateValueMap } from '@audius/common/store'
import { Genre } from '@audius/common/utils'
import { Animated, Dimensions, Easing } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
import TrackPlayer, { useIsPlaying } from 'react-native-track-player'
import { useSelector } from 'react-redux'
import { useAsync } from 'react-use'
+import { LinearGradient } from '@audius/harmony-native'
import { makeStyles } from 'app/styles'
import { useThemeColors } from 'app/utils/theme'
@@ -143,7 +143,6 @@ export const TrackingBar = (props: TrackingBarProps) => {
{
style={[styles.skeleton, { transform: [{ translateX: shimmerPos }] }]}
>
& {
+ /** Two or more colors, evenly spaced unless `locations` is provided. */
+ colors: readonly string[]
+ /** Gradient start as a fraction of the box. Defaults to the top edge. */
+ start?: LinearGradientPoint
+ /** Gradient end as a fraction of the box. Defaults to the bottom edge. */
+ end?: LinearGradientPoint
+ /**
+ * Bearing in degrees (0 points up, increasing clockwise). Overrides `start`
+ * and `end`, and measures the box to place the gradient line like a CSS
+ * angle gradient.
+ */
+ angle?: number
+ /** Per-color stop positions in [0, 1]; should match `colors` length. */
+ locations?: readonly number[]
+ style?: StyleProp
+ children?: ReactNode
+}
+
+// Top to bottom by default.
+const DEFAULT_START: LinearGradientPoint = { x: 0.5, y: 0 }
+const DEFAULT_END: LinearGradientPoint = { x: 0.5, y: 1 }
+
+const BORDER_RADIUS_KEYS = [
+ 'borderRadius',
+ 'borderTopLeftRadius',
+ 'borderTopRightRadius',
+ 'borderBottomLeftRadius',
+ 'borderBottomRightRadius',
+ 'borderTopStartRadius',
+ 'borderTopEndRadius',
+ 'borderBottomStartRadius',
+ 'borderBottomEndRadius',
+ 'borderCurve'
+] as const
+
+// The gradient line passes through the center along the bearing and is long
+// enough that the perpendiculars through opposite corners meet its ends.
+export const getAngleStartEnd = (
+ angle: number,
+ width: number,
+ height: number
+): { start: LinearGradientPoint; end: LinearGradientPoint } => {
+ const w = width > 0 ? width : 1
+ const h = height > 0 ? height : 1
+ const radians = (angle * Math.PI) / 180
+ const sin = Math.sin(radians)
+ const cos = Math.cos(radians)
+ const halfLength = (w * Math.abs(sin) + h * Math.abs(cos)) / 2
+ const dx = (sin * halfLength) / w
+ const dy = (cos * halfLength) / h
+ return {
+ start: { x: 0.5 - dx, y: 0.5 + dy },
+ end: { x: 0.5 + dx, y: 0.5 - dy }
+ }
+}
+
+const styles = StyleSheet.create({
+ clip: { overflow: 'hidden' }
+})
+
+/**
+ * Linear gradient drawn with react-native-svg. `start` and `end` are fractions
+ * of the box (objectBoundingBox units), so no layout measurement is needed
+ * unless `angle` is set. The gradient renders behind any children and is
+ * clipped to the container's border radii.
+ */
+export const LinearGradient = (props: LinearGradientProps) => {
+ const {
+ colors,
+ locations,
+ start: startProp = DEFAULT_START,
+ end: endProp = DEFAULT_END,
+ angle,
+ style,
+ children,
+ onLayout,
+ ...other
+ } = props
+
+ const [size, setSize] = useState({ width: 0, height: 0 })
+ const handleLayout = useCallback(
+ (e: LayoutChangeEvent) => {
+ if (angle !== undefined) {
+ const { width, height } = e.nativeEvent.layout
+ setSize((prev) =>
+ prev.width === width && prev.height === height
+ ? prev
+ : { width, height }
+ )
+ }
+ onLayout?.(e)
+ },
+ [angle, onLayout]
+ )
+
+ const { start, end } =
+ angle === undefined
+ ? { start: startProp, end: endProp }
+ : getAngleStartEnd(angle, size.width, size.height)
+
+ // useId output is not a valid SVG id, so strip it to safe characters.
+ const gradientId = `linear-gradient-${useId().replace(/[^\w-]/g, '')}`
+
+ // Clip the gradient (not the children) to the container's corner radii.
+ const clipStyle = useMemo(() => {
+ const flat = StyleSheet.flatten(style) ?? {}
+ const radii = Object.fromEntries(
+ BORDER_RADIUS_KEYS.filter((key) => flat[key] !== undefined).map((key) => [
+ key,
+ flat[key]
+ ])
+ ) as ViewStyle
+ return [StyleSheet.absoluteFill, radii, styles.clip]
+ }, [style])
+
+ return (
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/packages/mobile/src/harmony-native/components/RadialGradient/RadialGradient.tsx b/packages/mobile/src/harmony-native/components/RadialGradient/RadialGradient.tsx
index 1fdf297915c..5c4f04a60c6 100644
--- a/packages/mobile/src/harmony-native/components/RadialGradient/RadialGradient.tsx
+++ b/packages/mobile/src/harmony-native/components/RadialGradient/RadialGradient.tsx
@@ -1,57 +1,94 @@
-import { useState } from 'react'
+import { useId, useState } from 'react'
import { css } from '@emotion/native'
-import { Platform, View } from 'react-native'
-import type { RadialGradientProps } from 'react-native-radial-gradient'
-import RNRadialGradient from 'react-native-radial-gradient'
+import type { ViewProps } from 'react-native'
+import { StyleSheet, View } from 'react-native'
+import Svg, {
+ Defs,
+ RadialGradient as SvgRadialGradient,
+ Rect,
+ Stop
+} from 'react-native-svg'
const fullSize = css({ height: '100%', width: '100%' })
-// RadialGradient that uses percentages instead of pixels for center and radius
-// This makes RadialGradient much more useful and dynamic
+export type RadialGradientProps = ViewProps & {
+ colors: string[]
+ center?: number[]
+ stops?: number[]
+ radius?: number
+}
+
+// RadialGradient that takes its center and radius as percentages of the
+// laid-out size instead of pixels. Drawn with react-native-svg behind any
+// children.
export const RadialGradient = (props: RadialGradientProps) => {
const {
- center: centerProp = [50, 50],
- radius: radiusProp,
+ colors,
+ stops,
+ center = [50, 50],
+ radius = 50,
style,
+ children,
+ onLayout,
...other
} = props
- const [{ height, width }, setDimensions] = useState({
- height: 0,
- width: 0
- })
-
- let center = centerProp
- ? [(centerProp[0] * width) / 100, (centerProp[1] * height) / 100]
- : undefined
+ const [{ height, width }, setDimensions] = useState({ height: 0, width: 0 })
- // We update the center prop to use x,y coordinates on iOS due to
- // bug where center prop cannot be updated on iOS
- if (Platform.OS === 'ios') {
- center = center
- ? ({
- x: center[0],
- y: center[1]
- } as unknown as RadialGradientProps['center'])
- : undefined
- }
+ // useId output is not a valid SVG id, so strip it to safe characters.
+ const gradientId = `radial-gradient-${useId().replace(/[^\w-]/g, '')}`
- // Since we cant have ellipse gradients, we use the average of height and width
- const radius = radiusProp
- ? (radiusProp * ((height + width) / 2)) / 100
- : undefined
+ const cx = (center[0] * width) / 100
+ const cy = (center[1] * height) / 100
+ // Use the average of width and height so the gradient stays circular.
+ const r = (radius * ((height + width) / 2)) / 100
return (
setDimensions(e.nativeEvent.layout)}
+ onLayout={(e) => {
+ setDimensions(e.nativeEvent.layout)
+ onLayout?.(e)
+ }}
>
-
+ {width > 0 && height > 0 ? (
+
+ ) : null}
+ {children}
)
}
diff --git a/packages/mobile/src/harmony-native/components/index.ts b/packages/mobile/src/harmony-native/components/index.ts
index 5e8f0475d4d..f82a532997b 100644
--- a/packages/mobile/src/harmony-native/components/index.ts
+++ b/packages/mobile/src/harmony-native/components/index.ts
@@ -25,6 +25,7 @@ export type { SocialButtonProps } from './Button/SocialButton/types'
export * from './Button/PlainButton/PlainButton'
export * from './Button/PlainButton/types'
export * from './CompletionCheck/CompletionCheck'
+export * from './LinearGradient/LinearGradient'
export * from './RadialGradient/RadialGradient'
export * from './Image/Image'
export * from './Radio'
diff --git a/packages/mobile/src/notifications.test.ts b/packages/mobile/src/notifications.test.ts
new file mode 100644
index 00000000000..b99ed304e84
--- /dev/null
+++ b/packages/mobile/src/notifications.test.ts
@@ -0,0 +1,211 @@
+const mockMessaging = {
+ getAPNSToken: jest.fn(),
+ getInitialNotification: jest.fn(),
+ getToken: jest.fn(),
+ isDeviceRegisteredForRemoteMessages: jest.fn(),
+ onNotificationOpenedApp: jest.fn(),
+ onTokenRefresh: jest.fn(),
+ registerDeviceForRemoteMessages: jest.fn(),
+ requestPermission: jest.fn()
+}
+const mockNotifee = {
+ onForegroundEvent: jest.fn(),
+ onBackgroundEvent: jest.fn(),
+ setBadgeCount: jest.fn()
+}
+const mockStorage: Record = {}
+
+jest.mock('@react-native-firebase/messaging', () => ({
+ getMessaging: () => 'messaging',
+ getAPNSToken: (...args: any[]) => mockMessaging.getAPNSToken(...args),
+ getInitialNotification: (...args: any[]) =>
+ mockMessaging.getInitialNotification(...args),
+ getToken: (...args: any[]) => mockMessaging.getToken(...args),
+ isDeviceRegisteredForRemoteMessages: (...args: any[]) =>
+ mockMessaging.isDeviceRegisteredForRemoteMessages(...args),
+ onNotificationOpenedApp: (...args: any[]) =>
+ mockMessaging.onNotificationOpenedApp(...args),
+ onTokenRefresh: (...args: any[]) => mockMessaging.onTokenRefresh(...args),
+ registerDeviceForRemoteMessages: (...args: any[]) =>
+ mockMessaging.registerDeviceForRemoteMessages(...args),
+ requestPermission: (...args: any[]) =>
+ mockMessaging.requestPermission(...args)
+}))
+jest.mock('@notifee/react-native', () => ({
+ __esModule: true,
+ default: mockNotifee,
+ EventType: { PRESS: 1, DISMISSED: 0 }
+}))
+jest.mock('@react-native-async-storage/async-storage', () => ({
+ getItem: async (key: string) => mockStorage[key] ?? null,
+ setItem: async (key: string, value: string) => {
+ mockStorage[key] = value
+ },
+ removeItem: async (key: string) => {
+ delete mockStorage[key]
+ }
+}))
+jest.mock('react-native-permissions', () => ({
+ requestNotifications: jest.fn()
+}))
+jest.mock('@audius/common/api', () => ({
+ getCurrentAccountQueryKey: () => ['account']
+}))
+jest.mock('app/services/analytics', () => ({
+ track: jest.fn(),
+ make: (e: any) => e
+}))
+jest.mock('app/services/audius-backend-instance', () => ({
+ audiusBackendInstance: {}
+}))
+jest.mock('app/services/query-client', () => ({
+ queryClient: { getQueryData: () => undefined }
+}))
+jest.mock('app/services/sdk/audius-sdk', () => ({ audiusSdk: jest.fn() }))
+jest.mock('app/types/analytics', () => ({
+ EventNames: { NOTIFICATIONS_OPEN_PUSH_NOTIFICATION: 'open' }
+}))
+
+const loadNotifications = (os: 'ios' | 'android') => {
+ let mod: any
+ jest.isolateModules(() => {
+ jest.doMock('react-native', () => ({ Platform: { OS: os } }))
+ mod = require('./notifications').default
+ })
+ return mod
+}
+
+const iosPress = (data: Record) => ({
+ type: 1,
+ detail: { notification: { title: 't', body: 'b', data } }
+})
+
+describe('PushNotifications', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ for (const key of Object.keys(mockStorage)) delete mockStorage[key]
+ })
+
+ describe('ios', () => {
+ it('registers the lowercase APNs token, not an FCM token', async () => {
+ mockMessaging.isDeviceRegisteredForRemoteMessages.mockReturnValue(false)
+ mockMessaging.getAPNSToken.mockResolvedValue('ABCDEF0123')
+ const notifications = loadNotifications('ios')
+
+ const token = await notifications.getToken()
+
+ expect(mockMessaging.registerDeviceForRemoteMessages).toHaveBeenCalled()
+ expect(mockMessaging.getToken).not.toHaveBeenCalled()
+ expect(token).toEqual({ token: 'abcdef0123', os: 'ios' })
+ expect(JSON.parse(mockStorage['@device-token'])).toEqual(token)
+ })
+
+ it('falls back to the stored token when APNs has none', async () => {
+ mockMessaging.isDeviceRegisteredForRemoteMessages.mockReturnValue(true)
+ mockMessaging.getAPNSToken.mockResolvedValue(null)
+ mockStorage['@device-token'] = JSON.stringify({
+ token: 'stored',
+ os: 'ios'
+ })
+ const notifications = loadNotifications('ios')
+
+ expect(await notifications.getToken()).toEqual({
+ token: 'stored',
+ os: 'ios'
+ })
+ })
+
+ it('holds a cold-start tap until navigation is ready, then navigates once', async () => {
+ const notifications = loadNotifications('ios')
+ const navigate = jest.fn()
+ const onForeground = mockNotifee.onForegroundEvent.mock.calls[0][0]
+
+ onForeground(
+ iosPress({ data: { type: 'Follow', id: 1 }, 'media-url': 'x' })
+ )
+ notifications.setNavigation({ navigate })
+ expect(navigate).not.toHaveBeenCalled()
+
+ await notifications.openInitialNotification()
+ expect(navigate).toHaveBeenCalledTimes(1)
+ expect(navigate).toHaveBeenCalledWith({ type: 'Follow', id: 1 })
+ expect(mockMessaging.getInitialNotification).not.toHaveBeenCalled()
+
+ // A later remount must not replay it
+ await notifications.openInitialNotification()
+ expect(navigate).toHaveBeenCalledTimes(1)
+ })
+
+ it('navigates immediately on a tap once ready, ignoring other events', async () => {
+ const notifications = loadNotifications('ios')
+ const navigate = jest.fn()
+ notifications.setNavigation({ navigate })
+ await notifications.openInitialNotification()
+ const onBackground = mockNotifee.onBackgroundEvent.mock.calls[0][0]
+
+ await onBackground({ type: 0, detail: { notification: { data: {} } } })
+ expect(navigate).not.toHaveBeenCalled()
+
+ await onBackground(iosPress({ data: { type: 'Repost', id: 2 } }))
+ expect(navigate).toHaveBeenCalledWith({ type: 'Repost', id: 2 })
+ })
+
+ it('requests permission through firebase and sets the badge', async () => {
+ const notifications = loadNotifications('ios')
+ await notifications.requestPermission()
+ expect(mockMessaging.requestPermission).toHaveBeenCalled()
+ notifications.setBadgeCount(0)
+ expect(mockNotifee.setBadgeCount).toHaveBeenCalledWith(0)
+ })
+ })
+
+ describe('android', () => {
+ it('registers the FCM token', async () => {
+ mockMessaging.getToken.mockResolvedValue('fcm:Token')
+ const notifications = loadNotifications('android')
+
+ expect(await notifications.getToken()).toEqual({
+ token: 'fcm:Token',
+ os: 'android'
+ })
+ expect(mockMessaging.getAPNSToken).not.toHaveBeenCalled()
+ })
+
+ it('opens the launch notification with parsed data', async () => {
+ mockMessaging.getInitialNotification.mockResolvedValue({
+ notification: { title: 't', body: 'b' },
+ data: { type: 'Follow', id: '12', userIds: '[1,2]' }
+ })
+ const notifications = loadNotifications('android')
+ const navigate = jest.fn()
+ notifications.setNavigation({ navigate })
+
+ await notifications.openInitialNotification()
+ expect(navigate).toHaveBeenCalledTimes(1)
+ expect(navigate).toHaveBeenCalledWith({
+ type: 'Follow',
+ id: 12,
+ userIds: [1, 2]
+ })
+ expect(mockNotifee.onForegroundEvent).not.toHaveBeenCalled()
+ })
+
+ it('opens a tap from the background', async () => {
+ mockMessaging.getInitialNotification.mockResolvedValue(null)
+ const notifications = loadNotifications('android')
+ const navigate = jest.fn()
+ notifications.setNavigation({ navigate })
+ await notifications.openInitialNotification()
+
+ const onOpened = mockMessaging.onNotificationOpenedApp.mock.calls[0][1]
+ onOpened({ data: { type: 'Repost', id: '3' } })
+ expect(navigate).toHaveBeenCalledWith({ type: 'Repost', id: 3 })
+ })
+
+ it('does not touch the iOS badge', () => {
+ const notifications = loadNotifications('android')
+ notifications.setBadgeCount(0)
+ expect(mockNotifee.setBadgeCount).not.toHaveBeenCalled()
+ })
+ })
+})
diff --git a/packages/mobile/src/notifications.ts b/packages/mobile/src/notifications.ts
index 036638238f6..1ca54ee4de2 100644
--- a/packages/mobile/src/notifications.ts
+++ b/packages/mobile/src/notifications.ts
@@ -1,10 +1,22 @@
import { getCurrentAccountQueryKey } from '@audius/common/api'
import { MobileOS } from '@audius/common/models'
import type { AccountState } from '@audius/common/store'
+import notifee, { EventType } from '@notifee/react-native'
+import type { Event as NotifeeEvent } from '@notifee/react-native'
import AsyncStorage from '@react-native-async-storage/async-storage'
+import {
+ getAPNSToken,
+ getInitialNotification,
+ getMessaging,
+ getToken as getFcmToken,
+ isDeviceRegisteredForRemoteMessages,
+ onNotificationOpenedApp,
+ onTokenRefresh,
+ registerDeviceForRemoteMessages,
+ requestPermission
+} from '@react-native-firebase/messaging'
+import type { FirebaseMessagingTypes } from '@react-native-firebase/messaging'
import { Platform } from 'react-native'
-import { Notifications } from 'react-native-notifications'
-import type { Registered, Notification } from 'react-native-notifications'
import { requestNotifications } from 'react-native-permissions'
import { track, make } from 'app/services/analytics'
@@ -52,7 +64,7 @@ function parseAndroidNotificationData(data: Record): any {
}
function extractNotificationCampaignIdFromPayload(
- payload: Notification['payload']
+ payload: Record | undefined
): string | undefined {
const target = payload?.data?.data ?? payload?.data ?? payload ?? undefined
if (!target || typeof target !== 'object') return undefined
@@ -87,29 +99,68 @@ async function reportNotificationCampaignPushOpen(
})
}
-// Set to true while the push notification service is registering with the os
-let isRegistering = false
+// A tapped push, normalized across platforms. `payload` matches what
+// react-native-notifications used to hand us: the APNs userInfo minus `aps` on
+// iOS, the FCM data map on Android.
+type OpenedNotification = {
+ title?: string
+ body?: string
+ payload: Record
+}
+
+const fromRemoteMessage = (
+ message: FirebaseMessagingTypes.RemoteMessage
+): OpenedNotification => ({
+ title: message.notification?.title,
+ body: message.notification?.body,
+ payload: message.data ?? {}
+})
+
+// Our iOS pushes go straight to APNs through SNS, not through FCM, so
+// @react-native-firebase/messaging ignores taps on them. Notifee reports them
+// as PRESS events instead (AppDelegate makes sure it sees them).
+const fromNotifeeEvent = ({
+ type,
+ detail
+}: NotifeeEvent): OpenedNotification | null => {
+ if (type !== EventType.PRESS || !detail.notification) return null
+ return {
+ title: detail.notification.title,
+ body: detail.notification.body,
+ payload: (detail.notification.data ?? {}) as Record
+ }
+}
// Singleton class
class PushNotifications {
- lastId: number
token: Token | null
navigation: NotificationNavigation | null
+ // A tap that arrived before navigation was ready (cold start)
+ private pendingOpen: OpenedNotification | null
+ private isNavigationReady: boolean
- // onNotification is a function passed in that is to be called when a
- // notification is to be emitted.
constructor() {
- this.configure()
- this.lastId = 0
this.token = null
this.navigation = null
+ this.pendingOpen = null
+ this.isNavigationReady = false
+ this.configure()
}
setNavigation = (navigation: NotificationNavigation) => {
this.navigation = navigation
}
- onNotification = (notification: Notification) => {
+ private handleOpened = (notification: OpenedNotification | null) => {
+ if (!notification) return
+ if (!this.isNavigationReady) {
+ this.pendingOpen = notification
+ return
+ }
+ this.onNotification(notification)
+ }
+
+ onNotification = (notification: OpenedNotification) => {
console.info(`Received notification ${JSON.stringify(notification)}`)
const { title, body, payload } = notification
const notificationCampaignId =
@@ -138,19 +189,43 @@ class PushNotifications {
this.navigation?.navigate(data)
}
- // Method used to open the push notification that the user pressed while the app was closed
+ // Called once navigation is ready. Opens the push the user tapped to launch
+ // the app, if any.
openInitialNotification = async () => {
- const notification = await Notifications.getInitialNotification()
+ this.isNavigationReady = true
+ if (Platform.OS === MobileOS.ANDROID) {
+ const message = await getInitialNotification(getMessaging())
+ if (message) {
+ this.pendingOpen = fromRemoteMessage(message)
+ }
+ }
+ const notification = this.pendingOpen
+ this.pendingOpen = null
if (notification) {
this.onNotification(notification)
}
}
- async onRegister(event: Registered) {
- const token = { token: event.deviceToken, os: Platform.OS }
+ private async persistToken(deviceToken: string) {
+ const token = { token: deviceToken, os: Platform.OS }
this.token = token
await AsyncStorage.setItem(DEVICE_TOKEN, JSON.stringify(token))
- isRegistering = false
+ return token
+ }
+
+ // identity-service registers iOS tokens with an SNS APNs platform app, so iOS
+ // must send the raw APNs token, not an FCM token. Lowercase hex matches what
+ // react-native-notifications sent, so existing rows and endpoints are reused.
+ private async fetchDeviceToken() {
+ if (Platform.OS === MobileOS.IOS) {
+ const messaging = getMessaging()
+ if (!isDeviceRegisteredForRemoteMessages(messaging)) {
+ await registerDeviceForRemoteMessages(messaging)
+ }
+ const apnsToken = await getAPNSToken(messaging)
+ return apnsToken ? apnsToken.toLowerCase() : null
+ }
+ return await getFcmToken(getMessaging())
}
deregister() {
@@ -158,10 +233,22 @@ class PushNotifications {
}
async configure() {
- Notifications.events().registerRemoteNotificationsRegistered(
- this.onRegister
- )
- Notifications.events().registerNotificationOpened(this.onNotification)
+ if (Platform.OS === MobileOS.IOS) {
+ notifee.onForegroundEvent((event) =>
+ this.handleOpened(fromNotifeeEvent(event))
+ )
+ notifee.onBackgroundEvent(async (event) =>
+ this.handleOpened(fromNotifeeEvent(event))
+ )
+ } else {
+ const messaging = getMessaging()
+ onNotificationOpenedApp(messaging, (message) =>
+ this.handleOpened(fromRemoteMessage(message))
+ )
+ onTokenRefresh(messaging, (token) => {
+ this.persistToken(token).catch(() => {})
+ })
+ }
try {
const token = await AsyncStorage.getItem(DEVICE_TOKEN)
@@ -175,40 +262,30 @@ class PushNotifications {
}
}
- async hasPermission(): Promise {
- return await Notifications.isRegisteredForRemoteNotifications()
- }
-
async requestPermission() {
- isRegistering = true
-
if (Platform.OS === MobileOS.ANDROID) {
// Android 13+ needs POST_NOTIFICATIONS. Use requestNotifications — PERMISSIONS.ANDROID
// does not expose POST_NOTIFICATIONS in react-native-permissions v5, so request(undefined) crashed native code.
await requestNotifications()
+ } else {
+ await requestPermission(getMessaging())
}
-
- Notifications.registerRemoteNotifications()
- }
-
- cancelNotif() {
- Notifications.cancelLocalNotification(this.lastId)
- }
-
- cancelAll() {
- Notifications.ios.cancelAllLocalNotifications()
}
setBadgeCount(count: number) {
- Notifications.ios.setBadgeCount(count)
+ if (Platform.OS === MobileOS.IOS) {
+ notifee.setBadgeCount(count)
+ }
}
async getToken() {
- // Wait until the device token and OS are persisted to async storage
- // isRegistering modified as global
- // eslint-disable-next-line no-unmodified-loop-condition
- while (isRegistering) {
- await new Promise((resolve) => setTimeout(resolve, 100))
+ try {
+ const deviceToken = await this.fetchDeviceToken()
+ if (deviceToken) {
+ return await this.persistToken(deviceToken)
+ }
+ } catch (e) {
+ console.error('Failed to fetch push token', e)
}
const token = await AsyncStorage.getItem(DEVICE_TOKEN)
if (token) {
diff --git a/packages/mobile/src/screens/audio-screen/AudioScreen.tsx b/packages/mobile/src/screens/audio-screen/AudioScreen.tsx
index 7a8b6cbdc95..95171a811e6 100644
--- a/packages/mobile/src/screens/audio-screen/AudioScreen.tsx
+++ b/packages/mobile/src/screens/audio-screen/AudioScreen.tsx
@@ -11,10 +11,10 @@ import { AUDIO } from '@audius/fixed-decimal'
import { useFocusEffect } from '@react-navigation/native'
import { Image, Linking } from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler'
-import LinearGradient from 'react-native-linear-gradient'
import { useDispatch, useSelector } from 'react-redux'
import {
+ LinearGradient,
Button,
Flex,
IconCrown,
diff --git a/packages/mobile/src/screens/coin-details-screen/EditCoinDetailsScreen.tsx b/packages/mobile/src/screens/coin-details-screen/EditCoinDetailsScreen.tsx
index 77ef64dd304..d8d56866260 100644
--- a/packages/mobile/src/screens/coin-details-screen/EditCoinDetailsScreen.tsx
+++ b/packages/mobile/src/screens/coin-details-screen/EditCoinDetailsScreen.tsx
@@ -19,9 +19,9 @@ import { useRoute, useNavigation } from '@react-navigation/native'
import { useFormikContext, Formik } from 'formik'
import { Image as RNImage, View, StyleSheet } from 'react-native'
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
-import LinearGradient from 'react-native-linear-gradient'
import {
+ LinearGradient,
IconInstagram,
IconLink,
IconPlus,
diff --git a/packages/mobile/src/screens/coin-details-screen/components/CoinInfoCard.tsx b/packages/mobile/src/screens/coin-details-screen/components/CoinInfoCard.tsx
index 23c1aa84e46..f487b53c9ec 100644
--- a/packages/mobile/src/screens/coin-details-screen/components/CoinInfoCard.tsx
+++ b/packages/mobile/src/screens/coin-details-screen/components/CoinInfoCard.tsx
@@ -7,9 +7,9 @@ import { WidthSizes } from '@audius/common/models'
import { shortenSPLAddress } from '@audius/common/utils'
import Clipboard from '@react-native-clipboard/clipboard'
import { Image, Linking, StyleSheet, View } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
import {
+ LinearGradient,
Divider,
Flex,
IconCopy,
diff --git a/packages/mobile/src/screens/coin-details-screen/components/GraduationProgressBar.tsx b/packages/mobile/src/screens/coin-details-screen/components/GraduationProgressBar.tsx
index 83d055a63d0..074dfe5de07 100644
--- a/packages/mobile/src/screens/coin-details-screen/components/GraduationProgressBar.tsx
+++ b/packages/mobile/src/screens/coin-details-screen/components/GraduationProgressBar.tsx
@@ -1,6 +1,4 @@
-import LinearGradient from 'react-native-linear-gradient'
-
-import { Box, spacing, useTheme } from '@audius/harmony-native'
+import { LinearGradient, Box, spacing, useTheme } from '@audius/harmony-native'
interface GraduationProgressBarProps {
value: number
diff --git a/packages/mobile/src/screens/explore-screen/components/ColorTile.tsx b/packages/mobile/src/screens/explore-screen/components/ColorTile.tsx
index 7a62aa68c1f..681309bdd88 100644
--- a/packages/mobile/src/screens/explore-screen/components/ColorTile.tsx
+++ b/packages/mobile/src/screens/explore-screen/components/ColorTile.tsx
@@ -7,9 +7,9 @@ import type {
ViewStyle
} from 'react-native'
import { Animated, Image, View } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
import type { SvgProps } from 'react-native-svg'
+import { LinearGradient } from '@audius/harmony-native'
import IconAudioRewardsPill from 'app/assets/images/iconAudioRewardsPill.svg'
import { Pressable } from 'app/components/core'
import Text from 'app/components/text'
@@ -142,7 +142,6 @@ export const ColorTile = ({
>
diff --git a/packages/mobile/src/screens/rewards-screen/RewardsScreen.tsx b/packages/mobile/src/screens/rewards-screen/RewardsScreen.tsx
index 9b8140b9f77..3a0c01eec13 100644
--- a/packages/mobile/src/screens/rewards-screen/RewardsScreen.tsx
+++ b/packages/mobile/src/screens/rewards-screen/RewardsScreen.tsx
@@ -5,10 +5,9 @@ import { useRemoteVar } from '@audius/common/hooks'
import { StringKeys } from '@audius/common/services'
import { tokenDashboardPageActions } from '@audius/common/store'
import { useFocusEffect } from '@react-navigation/native'
-import LinearGradient from 'react-native-linear-gradient'
import { useDispatch } from 'react-redux'
-import { IconCrown, Flex, Text } from '@audius/harmony-native'
+import { LinearGradient, IconCrown, Flex, Text } from '@audius/harmony-native'
import { ScrollView, Screen, Tile, ScreenContent } from 'app/components/core'
import { useToast } from 'app/hooks/useToast'
import { makeStyles } from 'app/styles'
diff --git a/packages/mobile/src/screens/rewards-screen/TiersTile.tsx b/packages/mobile/src/screens/rewards-screen/TiersTile.tsx
index ebca2560060..627a3de67f5 100644
--- a/packages/mobile/src/screens/rewards-screen/TiersTile.tsx
+++ b/packages/mobile/src/screens/rewards-screen/TiersTile.tsx
@@ -13,9 +13,9 @@ import { badgeTiers, getTierAndNumberForBalance } from '@audius/common/store'
import type { Nullable } from '@audius/common/utils'
import { formatNumberCommas } from '@audius/common/utils'
import { Linking } from 'react-native'
-import LinearGradient from 'react-native-linear-gradient'
import {
+ LinearGradient,
Button,
Flex,
IconDiscord,
diff --git a/packages/mobile/src/shims/react-native-linear-gradient.ts b/packages/mobile/src/shims/react-native-linear-gradient.ts
new file mode 100644
index 00000000000..6511eae8bf7
--- /dev/null
+++ b/packages/mobile/src/shims/react-native-linear-gradient.ts
@@ -0,0 +1,6 @@
+// Metro resolves `react-native-linear-gradient` here so dependencies that
+// still import it (react-native-gifted-charts) render our svg gradient.
+import { LinearGradient } from 'app/harmony-native/components/LinearGradient/LinearGradient'
+
+export { LinearGradient }
+export default LinearGradient
diff --git a/packages/mobile/src/store/offline-downloads/sagas/migrateOfflineDataPathSaga.ts b/packages/mobile/src/store/offline-downloads/sagas/migrateOfflineDataPathSaga.ts
index fb72c845b97..ecbbfcee866 100644
--- a/packages/mobile/src/store/offline-downloads/sagas/migrateOfflineDataPathSaga.ts
+++ b/packages/mobile/src/store/offline-downloads/sagas/migrateOfflineDataPathSaga.ts
@@ -1,8 +1,8 @@
import path from 'path'
+import * as RNFS from '@dr.pogodin/react-native-fs'
import { difference } from 'lodash'
import ReactNativeBlobUtil from 'react-native-blob-util'
-import RNFS from 'react-native-fs'
import { call, put, select } from 'typed-redux-saga'
import { downloadsRoot } from 'app/services/offline-downloader'