mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-29 17:39:27 +01:00
switching to async storage
also switching to not storing music data from api unless downloaded
This commit is contained in:
parent
50be0a6f85
commit
71e34a6066
@ -7,6 +7,9 @@ buildscript {
|
|||||||
compileSdkVersion = 29
|
compileSdkVersion = 29
|
||||||
targetSdkVersion = 29
|
targetSdkVersion = 29
|
||||||
ndkVersion = "20.1.5948944"
|
ndkVersion = "20.1.5948944"
|
||||||
|
|
||||||
|
// react-native-async-storage next
|
||||||
|
kotlinVersion = '1.4.21'
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
@ -16,6 +19,9 @@ buildscript {
|
|||||||
classpath("com.android.tools.build:gradle:4.1.0")
|
classpath("com.android.tools.build:gradle:4.1.0")
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|
||||||
|
// react-native-async-storage next
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,3 +26,7 @@ android.enableJetifier=true
|
|||||||
|
|
||||||
# Version of flipper SDK to use with React Native
|
# Version of flipper SDK to use with React Native
|
||||||
FLIPPER_VERSION=0.75.1
|
FLIPPER_VERSION=0.75.1
|
||||||
|
|
||||||
|
# react-native-async-storage next
|
||||||
|
AsyncStorage_useNextStorage=true
|
||||||
|
AsyncStorage_kotlinVersion=1.4.21
|
||||||
|
|||||||
95
package-lock.json
generated
95
package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "subsonify",
|
"name": "subsonify",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-async-storage/async-storage": "^1.15.5",
|
||||||
"@react-native-community/masked-view": "^0.1.11",
|
"@react-native-community/masked-view": "^0.1.11",
|
||||||
"@react-navigation/bottom-tabs": "^5.11.11",
|
"@react-navigation/bottom-tabs": "^5.11.11",
|
||||||
"@react-navigation/material-top-tabs": "^5.3.15",
|
"@react-navigation/material-top-tabs": "^5.3.15",
|
||||||
@ -1768,6 +1769,17 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-native-async-storage/async-storage": {
|
||||||
|
"version": "1.15.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.15.5.tgz",
|
||||||
|
"integrity": "sha512-4AYehLH39B9a8UXCMf3ieOK+G61wGMP72ikx6/XSMA0DUnvx0PgaeaT2Wyt06kTrDTy8edewKnbrbeqwaM50TQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"deep-assign": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-native": "^0.60.6 || ^0.61.5 || ^0.62.2 || ^0.63.2 || ^0.64.0 || 1000.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@react-native-community/cli": {
|
"node_modules/@react-native-community/cli": {
|
||||||
"version": "5.0.1-alpha.2",
|
"version": "5.0.1-alpha.2",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-5.0.1-alpha.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-5.0.1-alpha.2.tgz",
|
||||||
@ -4491,6 +4503,18 @@
|
|||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deep-assign": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==",
|
||||||
|
"deprecated": "Check out `lodash.merge` or `merge-options` instead.",
|
||||||
|
"dependencies": {
|
||||||
|
"is-obj": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||||
@ -6664,6 +6688,14 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-obj": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-plain-object": {
|
"node_modules/is-plain-object": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||||
@ -14558,6 +14590,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@react-native-async-storage/async-storage": {
|
||||||
|
"version": "1.15.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.15.5.tgz",
|
||||||
|
"integrity": "sha512-4AYehLH39B9a8UXCMf3ieOK+G61wGMP72ikx6/XSMA0DUnvx0PgaeaT2Wyt06kTrDTy8edewKnbrbeqwaM50TQ==",
|
||||||
|
"requires": {
|
||||||
|
"deep-assign": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@react-native-community/cli": {
|
"@react-native-community/cli": {
|
||||||
"version": "5.0.1-alpha.2",
|
"version": "5.0.1-alpha.2",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-5.0.1-alpha.2.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-5.0.1-alpha.2.tgz",
|
||||||
@ -15132,7 +15172,8 @@
|
|||||||
"@react-native-community/masked-view": {
|
"@react-native-community/masked-view": {
|
||||||
"version": "0.1.11",
|
"version": "0.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.11.tgz",
|
||||||
"integrity": "sha512-rQfMIGSR/1r/SyN87+VD8xHHzDYeHaJq6elOSCAD+0iLagXkSI2pfA0LmSXP21uw5i3em7GkkRjfJ8wpqWXZNw=="
|
"integrity": "sha512-rQfMIGSR/1r/SyN87+VD8xHHzDYeHaJq6elOSCAD+0iLagXkSI2pfA0LmSXP21uw5i3em7GkkRjfJ8wpqWXZNw==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"@react-native/assets": {
|
"@react-native/assets": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -15621,7 +15662,8 @@
|
|||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
|
||||||
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
|
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"acorn-walk": {
|
"acorn-walk": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
@ -15840,7 +15882,8 @@
|
|||||||
"babel-core": {
|
"babel-core": {
|
||||||
"version": "7.0.0-bridge.0",
|
"version": "7.0.0-bridge.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
|
||||||
"integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg=="
|
"integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"babel-eslint": {
|
"babel-eslint": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
@ -16692,6 +16735,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
|
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
|
||||||
},
|
},
|
||||||
|
"deep-assign": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==",
|
||||||
|
"requires": {
|
||||||
|
"is-obj": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"deep-is": {
|
"deep-is": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||||
@ -17161,7 +17212,8 @@
|
|||||||
"version": "22.4.1",
|
"version": "22.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz",
|
||||||
"integrity": "sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg==",
|
"integrity": "sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"eslint-plugin-prettier": {
|
"eslint-plugin-prettier": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
@ -17217,7 +17269,8 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz",
|
||||||
"integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==",
|
"integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"eslint-plugin-react-native": {
|
"eslint-plugin-react-native": {
|
||||||
"version": "3.11.0",
|
"version": "3.11.0",
|
||||||
@ -18314,6 +18367,11 @@
|
|||||||
"integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==",
|
"integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"is-obj": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8="
|
||||||
|
},
|
||||||
"is-plain-object": {
|
"is-plain-object": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
|
||||||
@ -19064,7 +19122,8 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
|
||||||
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
|
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"jest-regex-util": {
|
"jest-regex-util": {
|
||||||
"version": "26.0.0",
|
"version": "26.0.0",
|
||||||
@ -21277,7 +21336,8 @@
|
|||||||
"react-native-fast-image": {
|
"react-native-fast-image": {
|
||||||
"version": "8.3.4",
|
"version": "8.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-8.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-8.3.4.tgz",
|
||||||
"integrity": "sha512-LpzAdjUphihUpVEBn5fEv5AILe55rHav0YiZroPZ1rumKDhAl4u2cG01ku2Pb7l8sayjTsNu7FuURAlXUUDsow=="
|
"integrity": "sha512-LpzAdjUphihUpVEBn5fEv5AILe55rHav0YiZroPZ1rumKDhAl4u2cG01ku2Pb7l8sayjTsNu7FuURAlXUUDsow==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"react-native-fs": {
|
"react-native-fs": {
|
||||||
"version": "2.18.0",
|
"version": "2.18.0",
|
||||||
@ -21311,12 +21371,14 @@
|
|||||||
"react-native-iphone-x-helper": {
|
"react-native-iphone-x-helper": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.3.1.tgz",
|
||||||
"integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg=="
|
"integrity": "sha512-HOf0jzRnq2/aFUcdCJ9w9JGzN3gdEg0zFE4FyYlp4jtidqU03D5X7ZegGKfT1EWteR0gPBGp9ye5T5FvSWi9Yg==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"react-native-linear-gradient": {
|
"react-native-linear-gradient": {
|
||||||
"version": "2.5.6",
|
"version": "2.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz",
|
||||||
"integrity": "sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg=="
|
"integrity": "sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"react-native-reanimated": {
|
"react-native-reanimated": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
@ -21332,7 +21394,8 @@
|
|||||||
"react-native-safe-area-context": {
|
"react-native-safe-area-context": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.2.0.tgz",
|
||||||
"integrity": "sha512-k2Nty4PwSnrg9HwrYeeE+EYqViYJoOFwEy9LxL5RIRfoqxAq/uQXNGwpUg2/u4gnKpBbEPa9eRh15KKMe/VHkA=="
|
"integrity": "sha512-k2Nty4PwSnrg9HwrYeeE+EYqViYJoOFwEy9LxL5RIRfoqxAq/uQXNGwpUg2/u4gnKpBbEPa9eRh15KKMe/VHkA==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"react-native-screens": {
|
"react-native-screens": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.0",
|
||||||
@ -21345,17 +21408,20 @@
|
|||||||
"react-native-sqlite-storage": {
|
"react-native-sqlite-storage": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
|
||||||
"integrity": "sha512-c1Joq3/tO1nmIcP8SkRZNolPSbfvY8uZg5lXse0TmjIPC0qHVbk96IMvWGyly1TmYCIpxpuDRc0/xCffDbYIvg=="
|
"integrity": "sha512-c1Joq3/tO1nmIcP8SkRZNolPSbfvY8uZg5lXse0TmjIPC0qHVbk96IMvWGyly1TmYCIpxpuDRc0/xCffDbYIvg==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"react-native-tab-view": {
|
"react-native-tab-view": {
|
||||||
"version": "2.16.0",
|
"version": "2.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-2.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-2.16.0.tgz",
|
||||||
"integrity": "sha512-ac2DmT7+l13wzIFqtbfXn4wwfgtPoKzWjjZyrK1t+T8sdemuUvD4zIt+UImg03fu3s3VD8Wh/fBrIdcqQyZJWg=="
|
"integrity": "sha512-ac2DmT7+l13wzIFqtbfXn4wwfgtPoKzWjjZyrK1t+T8sdemuUvD4zIt+UImg03fu3s3VD8Wh/fBrIdcqQyZJWg==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"react-native-track-player": {
|
"react-native-track-player": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-track-player/-/react-native-track-player-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-track-player/-/react-native-track-player-1.2.7.tgz",
|
||||||
"integrity": "sha512-U1kA25qm398/kY6BvTojGHt4S1tYuKrJNQpXW+dA+p0B+n4LlPyoidUXfu951YM7aoisg8EmQPsevUFmcXG+cg=="
|
"integrity": "sha512-U1kA25qm398/kY6BvTojGHt4S1tYuKrJNQpXW+dA+p0B+n4LlPyoidUXfu951YM7aoisg8EmQPsevUFmcXG+cg==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"react-refresh": {
|
"react-refresh": {
|
||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
@ -23302,7 +23368,8 @@
|
|||||||
"ws": {
|
"ws": {
|
||||||
"version": "7.4.6",
|
"version": "7.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A=="
|
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"xcode": {
|
"xcode": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@react-native-async-storage/async-storage": "^1.15.5",
|
||||||
"@react-native-community/masked-view": "^0.1.11",
|
"@react-native-community/masked-view": "^0.1.11",
|
||||||
"@react-navigation/bottom-tabs": "^5.11.11",
|
"@react-navigation/bottom-tabs": "^5.11.11",
|
||||||
"@react-navigation/material-top-tabs": "^5.3.15",
|
"@react-navigation/material-top-tabs": "^5.3.15",
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { MusicDb } from "./storage/music";
|
import { MusicDb } from "./storage/music";
|
||||||
import { SettingsDb } from "./storage/settings";
|
|
||||||
import { SubsonicApiClient } from "./subsonic/api";
|
import { SubsonicApiClient } from "./subsonic/api";
|
||||||
|
|
||||||
export const musicDb = new MusicDb();
|
export const musicDb = new MusicDb();
|
||||||
export const settingsDb = new SettingsDb();
|
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { Button, TextInput, View, Text } from 'react-native';
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import md5 from 'md5';
|
import md5 from 'md5';
|
||||||
import { musicDb, settingsDb } from '../clients';
|
import { musicDb } from '../clients';
|
||||||
import { appSettingsState, serversState } from '../state/settings';
|
import { appSettingsState } from '../state/settings';
|
||||||
import { DbStorage } from '../storage/db';
|
import { DbStorage } from '../storage/db';
|
||||||
import { StackScreenProps } from '@react-navigation/stack';
|
import { StackScreenProps } from '@react-navigation/stack';
|
||||||
import { useNavigation } from '@react-navigation/core';
|
import { useNavigation } from '@react-navigation/core';
|
||||||
@ -36,7 +36,6 @@ const DbControls = () => {
|
|||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<RecreateDbButton db={musicDb} title='Music' />
|
<RecreateDbButton db={musicDb} title='Music' />
|
||||||
<RecreateDbButton db={settingsDb} title='Settings' />
|
|
||||||
<Button
|
<Button
|
||||||
title='Now Playing'
|
title='Now Playing'
|
||||||
onPress={() => navigation.navigate('Now Playing')}
|
onPress={() => navigation.navigate('Now Playing')}
|
||||||
@ -46,11 +45,10 @@ const DbControls = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ServerSettingsView = () => {
|
const ServerSettingsView = () => {
|
||||||
const [servers, setServers] = useRecoilState(serversState);
|
|
||||||
const [appSettings, setAppSettings] = useRecoilState(appSettingsState);
|
const [appSettings, setAppSettings] = useRecoilState(appSettingsState);
|
||||||
|
|
||||||
const bootstrapServer = () => {
|
const bootstrapServer = () => {
|
||||||
if (servers.length !== 0) {
|
if (appSettings.servers.length !== 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,14 +56,17 @@ const ServerSettingsView = () => {
|
|||||||
const salt = uuidv4();
|
const salt = uuidv4();
|
||||||
const address = 'http://demo.subsonic.org';
|
const address = 'http://demo.subsonic.org';
|
||||||
|
|
||||||
setServers([{
|
setAppSettings({
|
||||||
|
...appSettings,
|
||||||
|
servers: [
|
||||||
|
...appSettings.servers,
|
||||||
|
{
|
||||||
id, salt, address,
|
id, salt, address,
|
||||||
username: 'guest',
|
username: 'guest',
|
||||||
token: md5('guest' + salt),
|
token: md5('guest' + salt),
|
||||||
}]);
|
},
|
||||||
|
],
|
||||||
setAppSettings({
|
activeServer: id,
|
||||||
server: id,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ const ServerSettingsView = () => {
|
|||||||
title='Add default server'
|
title='Add default server'
|
||||||
onPress={bootstrapServer}
|
onPress={bootstrapServer}
|
||||||
/>
|
/>
|
||||||
{servers.map(s => (
|
{appSettings.servers.map(s => (
|
||||||
<View key={s.id}>
|
<View key={s.id}>
|
||||||
<Text>{s.address}</Text>
|
<Text>{s.address}</Text>
|
||||||
<Text>{s.username}</Text>
|
<Text>{s.username}</Text>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
import TrackPlayer, { Track } from 'react-native-track-player';
|
import TrackPlayer, { Track } from 'react-native-track-player';
|
||||||
import { musicDb, settingsDb } from '../clients';
|
import { musicDb } from '../clients';
|
||||||
import paths from '../paths';
|
import paths from '../paths';
|
||||||
|
|
||||||
async function mkdir(path: string): Promise<void> {
|
async function mkdir(path: string): Promise<void> {
|
||||||
@ -30,14 +30,10 @@ const SplashPage: React.FC<{}> = ({ children }) => {
|
|||||||
await mkdir(paths.songs);
|
await mkdir(paths.songs);
|
||||||
|
|
||||||
await musicDb.openDb();
|
await musicDb.openDb();
|
||||||
await settingsDb.openDb();
|
|
||||||
|
|
||||||
if (!(await musicDb.dbExists())) {
|
if (!(await musicDb.dbExists())) {
|
||||||
await musicDb.createDb();
|
await musicDb.createDb();
|
||||||
}
|
}
|
||||||
if (!(await settingsDb.dbExists())) {
|
|
||||||
await settingsDb.createDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
await TrackPlayer.setupPlayer();
|
await TrackPlayer.setupPlayer();
|
||||||
TrackPlayer.updateOptions({
|
TrackPlayer.updateOptions({
|
||||||
|
|||||||
@ -4,14 +4,16 @@ import FastImage from 'react-native-fast-image';
|
|||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { Album } from '../../models/music';
|
import { Album } from '../../models/music';
|
||||||
import { albumsState, useCoverArtUri } from '../../state/music';
|
import { albumsState, albumsUpdatingState, useCoverArtUri, useUpdateAlbums } from '../../state/music';
|
||||||
import colors from '../../styles/colors';
|
import colors from '../../styles/colors';
|
||||||
import textStyles from '../../styles/text';
|
import textStyles from '../../styles/text';
|
||||||
import TopTabContainer from '../common/TopTabContainer';
|
import TopTabContainer from '../common/TopTabContainer';
|
||||||
|
|
||||||
const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ height, width, id }) => {
|
const AlbumArt: React.FC<{
|
||||||
const coverArtUri = useCoverArtUri(id);
|
height: number,
|
||||||
|
width: number,
|
||||||
|
coverArtUri?: string
|
||||||
|
}> = ({ height, width, coverArtUri }) => {
|
||||||
const Placeholder = (
|
const Placeholder = (
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[colors.accent, colors.accentLow]}
|
colors={[colors.accent, colors.accentLow]}
|
||||||
@ -39,27 +41,27 @@ const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ he
|
|||||||
}
|
}
|
||||||
const MemoAlbumArt = React.memo(AlbumArt);
|
const MemoAlbumArt = React.memo(AlbumArt);
|
||||||
|
|
||||||
const AlbumItem: React.FC<{ name: string, coverArt?: string } > = ({ name, coverArt }) => {
|
const AlbumItem: React.FC<{
|
||||||
|
name: string,
|
||||||
|
artist?: string,
|
||||||
|
coverArtUri?: string
|
||||||
|
} > = ({ name, artist, coverArtUri }) => {
|
||||||
const size = 125;
|
const size = 125;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View style={{
|
||||||
// flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginVertical: 8,
|
marginVertical: 8,
|
||||||
// marginLeft: 6,
|
|
||||||
// width: size,
|
|
||||||
flex: 1/3,
|
flex: 1/3,
|
||||||
}}>
|
}}>
|
||||||
<MemoAlbumArt
|
<MemoAlbumArt
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
id={coverArt}
|
coverArtUri={coverArtUri}
|
||||||
/>
|
/>
|
||||||
<View style={{
|
<View style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
width: size,
|
width: size,
|
||||||
// alignItems: 'baseline',
|
|
||||||
}}>
|
}}>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
@ -71,13 +73,10 @@ const AlbumItem: React.FC<{ name: string, coverArt?: string } > = ({ name, cover
|
|||||||
{name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{ ...textStyles.itemSubtitle }}
|
||||||
...textStyles.itemSubtitle,
|
|
||||||
// marginTop: 2,
|
|
||||||
}}
|
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
{name}
|
{artist}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@ -86,11 +85,19 @@ const AlbumItem: React.FC<{ name: string, coverArt?: string } > = ({ name, cover
|
|||||||
const MemoAlbumItem = React.memo(AlbumItem);
|
const MemoAlbumItem = React.memo(AlbumItem);
|
||||||
|
|
||||||
const AlbumListRenderItem: React.FC<{ item: Album }> = ({ item }) => (
|
const AlbumListRenderItem: React.FC<{ item: Album }> = ({ item }) => (
|
||||||
<MemoAlbumItem name={item.name} coverArt={item.coverArt} />
|
<MemoAlbumItem name={item.name} artist={item.artist} coverArtUri={item.coverArtThumbUri} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const AlbumsList = () => {
|
const AlbumsList = () => {
|
||||||
const albums = useRecoilValue(albumsState);
|
const albums = useRecoilValue(albumsState);
|
||||||
|
const updating = useRecoilValue(albumsUpdatingState);
|
||||||
|
const updateAlbums = useUpdateAlbums();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (albums.length === 0) {
|
||||||
|
updateAlbums();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
@ -100,6 +107,8 @@ const AlbumsList = () => {
|
|||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
numColumns={3}
|
numColumns={3}
|
||||||
removeClippedSubviews={true}
|
removeClippedSubviews={true}
|
||||||
|
refreshing={updating}
|
||||||
|
onRefresh={updateAlbums}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Text, View, Image, FlatList } from 'react-native';
|
import { Text, View, Image, FlatList } from 'react-native';
|
||||||
import { Artist } from '../../models/music';
|
import { Artist } from '../../models/music';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||||
import textStyles from '../../styles/text';
|
import textStyles from '../../styles/text';
|
||||||
import TopTabContainer from '../common/TopTabContainer';
|
import TopTabContainer from '../common/TopTabContainer';
|
||||||
import { artistsState } from '../../state/music';
|
import { artistsState, artistsUpdatingState, useUpdateArtists } from '../../state/music';
|
||||||
|
|
||||||
const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
|
const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
|
||||||
<View style={{
|
<View style={{
|
||||||
@ -29,6 +29,14 @@ const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
|
|||||||
|
|
||||||
const ArtistsList = () => {
|
const ArtistsList = () => {
|
||||||
const artists = useRecoilValue(artistsState);
|
const artists = useRecoilValue(artistsState);
|
||||||
|
const updating = useRecoilValue(artistsUpdatingState);
|
||||||
|
const updateArtists = useUpdateArtists();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (artists.length === 0) {
|
||||||
|
updateArtists();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const renderItem: React.FC<{ item: Artist }> = ({ item }) => (
|
const renderItem: React.FC<{ item: Artist }> = ({ item }) => (
|
||||||
<ArtistItem item={item} />
|
<ArtistItem item={item} />
|
||||||
@ -39,6 +47,8 @@ const ArtistsList = () => {
|
|||||||
data={artists}
|
data={artists}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={item => item.id}
|
keyExtractor={item => item.id}
|
||||||
|
onRefresh={updateArtists}
|
||||||
|
refreshing={updating}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,18 @@ export interface Artist {
|
|||||||
name: string;
|
name: string;
|
||||||
starred?: Date;
|
starred?: Date;
|
||||||
coverArt?: string;
|
coverArt?: string;
|
||||||
|
coverArtUri?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Album {
|
export interface Album {
|
||||||
id: string;
|
id: string;
|
||||||
artistId: string;
|
artistId?: string;
|
||||||
|
artist?: string;
|
||||||
name: string;
|
name: string;
|
||||||
starred?: Date;
|
starred?: Date;
|
||||||
coverArt?: string;
|
coverArt?: string;
|
||||||
|
coverArtUri?: string,
|
||||||
|
coverArtThumbUri?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Song {
|
export interface Song {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export interface ServerSettings {
|
export interface Server {
|
||||||
id: string;
|
id: string;
|
||||||
address: string;
|
address: string;
|
||||||
username: string;
|
username: string;
|
||||||
@ -7,5 +7,6 @@ export interface ServerSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AppSettings {
|
export interface AppSettings {
|
||||||
server?: string;
|
servers: Server[],
|
||||||
|
activeServer?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { atom, DefaultValue, selector, selectorFamily, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
import { atom, DefaultValue, selector, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||||
import { musicDb } from '../clients';
|
import { musicDb } from '../clients';
|
||||||
import { Album, Artist, Song } from '../models/music';
|
import { Album, Artist, Song } from '../models/music';
|
||||||
import paths from '../paths';
|
import paths from '../paths';
|
||||||
@ -9,38 +9,85 @@ import RNFS from 'react-native-fs';
|
|||||||
|
|
||||||
export const artistsState = atom<Artist[]>({
|
export const artistsState = atom<Artist[]>({
|
||||||
key: 'artistsState',
|
key: 'artistsState',
|
||||||
default: selector({
|
default: [],
|
||||||
key: 'artistsState/default',
|
|
||||||
get: () => musicDb.getArtists(),
|
|
||||||
}),
|
|
||||||
effects_UNSTABLE: [
|
|
||||||
({ onSet }) => {
|
|
||||||
onSet((newValue) => {
|
|
||||||
if (!(newValue instanceof DefaultValue)) {
|
|
||||||
musicDb.updateArtists(newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const artistsUpdatingState = atom<boolean>({
|
||||||
|
key: 'artistsUpdatingState',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useUpdateArtists = () => {
|
||||||
|
const server = useRecoilValue(activeServer);
|
||||||
|
if (!server) {
|
||||||
|
return () => Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updating, setUpdating] = useRecoilState(artistsUpdatingState);
|
||||||
|
const setArtists = useSetRecoilState(artistsState);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
if (updating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUpdating(true);
|
||||||
|
|
||||||
|
const client = new SubsonicApiClient(server);
|
||||||
|
const response = await client.getArtists();
|
||||||
|
|
||||||
|
setArtists(response.data.artists.map(x => ({
|
||||||
|
id: x.id,
|
||||||
|
name: x.name,
|
||||||
|
starred: x.starred,
|
||||||
|
coverArt: x.coverArt,
|
||||||
|
coverArtUri: x.coverArt ? client.getCoverArtUri({ id: x.coverArt }) : undefined,
|
||||||
|
})));
|
||||||
|
setUpdating(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const albumsState = atom<Album[]>({
|
export const albumsState = atom<Album[]>({
|
||||||
key: 'albumsState',
|
key: 'albumsState',
|
||||||
default: selector({
|
default: [],
|
||||||
key: 'albumsState/default',
|
|
||||||
get: () => musicDb.getAlbums(),
|
|
||||||
}),
|
|
||||||
effects_UNSTABLE: [
|
|
||||||
({ onSet }) => {
|
|
||||||
onSet((newValue) => {
|
|
||||||
if (!(newValue instanceof DefaultValue)) {
|
|
||||||
musicDb.updateAlbums(newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const albumsUpdatingState = atom<boolean>({
|
||||||
|
key: 'albumsUpdatingState',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useUpdateAlbums = () => {
|
||||||
|
const server = useRecoilValue(activeServer);
|
||||||
|
if (!server) {
|
||||||
|
return () => Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updating, setUpdating] = useRecoilState(albumsUpdatingState);
|
||||||
|
const setAlbums = useSetRecoilState(albumsState);
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
if (updating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setUpdating(true);
|
||||||
|
|
||||||
|
const client = new SubsonicApiClient(server);
|
||||||
|
const response = await client.getAlbumList2({ type: 'alphabeticalByArtist', size: 500 });
|
||||||
|
|
||||||
|
setAlbums(response.data.albums.map(x => ({
|
||||||
|
id: x.id,
|
||||||
|
artistId: x.artistId,
|
||||||
|
artist: x.artist,
|
||||||
|
name: x.name,
|
||||||
|
starred: x.starred,
|
||||||
|
coverArt: x.coverArt,
|
||||||
|
coverArtUri: x.coverArt ? client.getCoverArtUri({ id: x.coverArt }) : undefined,
|
||||||
|
coverArtThumbUri: x.coverArt ? client.getCoverArtUri({ id: x.coverArt, size: '128' }) : undefined,
|
||||||
|
})));
|
||||||
|
setUpdating(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const songsState = atom<Song[]>({
|
export const songsState = atom<Song[]>({
|
||||||
key: 'songsState',
|
key: 'songsState',
|
||||||
default: selector({
|
default: selector({
|
||||||
|
|||||||
@ -1,47 +1,28 @@
|
|||||||
import { atom, DefaultValue, selector } from 'recoil';
|
import { atom, DefaultValue, selector } from 'recoil';
|
||||||
import { settingsDb } from '../clients';
|
import { AppSettings, Server } from '../models/settings';
|
||||||
import { AppSettings, ServerSettings } from '../models/settings';
|
import { getAppSettings, setAppSettings } from '../storage/settings';
|
||||||
|
|
||||||
export const serversState = atom<ServerSettings[]>({
|
|
||||||
key: 'serverState',
|
|
||||||
default: selector({
|
|
||||||
key: 'serversState/default',
|
|
||||||
get: () => settingsDb.getServers(),
|
|
||||||
}),
|
|
||||||
effects_UNSTABLE: [
|
|
||||||
({ onSet }) => {
|
|
||||||
onSet((newValue) => {
|
|
||||||
if (!(newValue instanceof DefaultValue)) {
|
|
||||||
settingsDb.updateServers(newValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const appSettingsState = atom<AppSettings>({
|
export const appSettingsState = atom<AppSettings>({
|
||||||
key: 'appSettingsState',
|
key: 'appSettingsState',
|
||||||
default: selector({
|
default: selector({
|
||||||
key: 'appSettingsState/default',
|
key: 'appSettingsState/default',
|
||||||
get: () => settingsDb.getApp(),
|
get: () => getAppSettings(),
|
||||||
}),
|
}),
|
||||||
effects_UNSTABLE: [
|
effects_UNSTABLE: [
|
||||||
({ onSet }) => {
|
({ onSet }) => {
|
||||||
onSet((newValue, oldValue) => {
|
onSet((newValue, oldValue) => {
|
||||||
if (!(newValue instanceof DefaultValue)) {
|
if (!(newValue instanceof DefaultValue)) {
|
||||||
settingsDb.updateApp(newValue);
|
setAppSettings(newValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const activeServer = selector<ServerSettings | undefined>({
|
export const activeServer = selector<Server | undefined>({
|
||||||
key: 'activeServer',
|
key: 'activeServer',
|
||||||
get: ({get}) => {
|
get: ({get}) => {
|
||||||
const appSettings = get(appSettingsState);
|
const appSettings = get(appSettingsState);
|
||||||
const servers = get(serversState);
|
return appSettings.servers.find(x => x.id == appSettings.activeServer);
|
||||||
|
|
||||||
return servers.find(x => x.id == appSettings.server);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
98
src/storage/asyncstorage.ts
Normal file
98
src/storage/asyncstorage.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
|
|
||||||
|
const key = {
|
||||||
|
downloadedSongKeys: '@downloadedSongKeys',
|
||||||
|
downloadedAlbumKeys: '@downloadedAlbumKeys',
|
||||||
|
downloadedArtistKeys: '@downloadedArtistKeys',
|
||||||
|
downloadedPlaylistKeys: '@downloadedPlaylistKeys',
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getItem(key: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
return await AsyncStorage.getItem(key);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`getItem error (key: ${key})`, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function multiGet(keys: string[]): Promise<[string, string | null][]> {
|
||||||
|
try {
|
||||||
|
return await AsyncStorage.multiGet(keys);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`multiGet error`, e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setItem(key: string, item: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.setItem(key, item);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`setItem error (key: ${key})`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function multiSet(items: string[][]): Promise<void> {
|
||||||
|
try {
|
||||||
|
await AsyncStorage.multiSet(items);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`multiSet error`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownloadedSong = {
|
||||||
|
id: string;
|
||||||
|
type: 'song';
|
||||||
|
name: string;
|
||||||
|
album: string;
|
||||||
|
artist: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DownloadedAlbum = {
|
||||||
|
id: string;
|
||||||
|
type: 'album';
|
||||||
|
songs: string[];
|
||||||
|
name: string;
|
||||||
|
artist: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DownloadedArtist = {
|
||||||
|
id: string;
|
||||||
|
type: 'artist';
|
||||||
|
songs: string[];
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DownloadedPlaylist = {
|
||||||
|
id: string;
|
||||||
|
type: 'playlist';
|
||||||
|
songs: string[];
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getDownloadedSongs(): Promise<DownloadedSong[]> {
|
||||||
|
const keysItem = await getItem(key.downloadedSongKeys);
|
||||||
|
const keys: string[] = keysItem ? JSON.parse(keysItem) : [];
|
||||||
|
|
||||||
|
const items = await multiGet(keys);
|
||||||
|
return items.map(x => {
|
||||||
|
const parsed = JSON.parse(x[1] as string);
|
||||||
|
return {
|
||||||
|
id: x[0],
|
||||||
|
type: 'song',
|
||||||
|
...parsed,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setDownloadedSongs(items: DownloadedSong[]): Promise<void> {
|
||||||
|
await multiSet([
|
||||||
|
[key.downloadedSongKeys, JSON.stringify(items.map(x => x.id))],
|
||||||
|
...items.map(x => [x.id, JSON.stringify({
|
||||||
|
name: x.name,
|
||||||
|
album: x.album,
|
||||||
|
artist: x.artist,
|
||||||
|
})]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
@ -1,92 +1,15 @@
|
|||||||
import { AppSettings, ServerSettings } from '../models/settings';
|
import { AppSettings } from '../models/settings';
|
||||||
import { DbStorage } from './db';
|
import { getItem, setItem } from './asyncstorage';
|
||||||
|
|
||||||
export class SettingsDb extends DbStorage {
|
const appSettingsKey = '@appSettings';
|
||||||
constructor() {
|
|
||||||
super({ name: 'settings.db', location: 'Library' });
|
|
||||||
}
|
|
||||||
|
|
||||||
async createDb(): Promise<void> {
|
export async function getAppSettings(): Promise<AppSettings> {
|
||||||
await this.initDb(tx => {
|
const item = await getItem(appSettingsKey);
|
||||||
tx.executeSql(`
|
return item ? JSON.parse(item) : {
|
||||||
CREATE TABLE servers (
|
servers: [],
|
||||||
id TEXT PRIMARY KEY NOT NULL,
|
};
|
||||||
address TEXT NOT NULL,
|
}
|
||||||
username TEXT NOT NULL,
|
|
||||||
token TEXT NOT NULL,
|
export async function setAppSettings(appSettings: AppSettings): Promise<void> {
|
||||||
salt TEXT NOT NULL
|
await setItem(appSettingsKey, JSON.stringify(appSettings));
|
||||||
);
|
|
||||||
`);
|
|
||||||
tx.executeSql(`
|
|
||||||
CREATE TABLE app (
|
|
||||||
server TEXT
|
|
||||||
);
|
|
||||||
`);
|
|
||||||
tx.executeSql(`
|
|
||||||
INSERT INTO app (server)
|
|
||||||
VALUES (NULL);
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getServers(): Promise<ServerSettings[]> {
|
|
||||||
return (await this.executeSql(`
|
|
||||||
SELECT * FROM servers;
|
|
||||||
`))[0].rows.raw().map(x => ({
|
|
||||||
id: x.id,
|
|
||||||
address: x.address,
|
|
||||||
username: x.username,
|
|
||||||
token: x.token,
|
|
||||||
salt: x.salt,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async getServer(id: string): Promise<ServerSettings | undefined> {
|
|
||||||
return (await this.getServers()).find(x => x.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// async addServer(server: ServerSettings): Promise<void> {
|
|
||||||
// await this.executeSql(`
|
|
||||||
// INSERT INTO servers (id, address, username, token, salt)
|
|
||||||
// VALUES (?, ?, ?, ?, ?);
|
|
||||||
// `, [server.id, server.address, server.username, server.token, server.salt]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// async removeServer(id: string): Promise<void> {
|
|
||||||
// await this.executeSql(`
|
|
||||||
// DELETE FROM servers
|
|
||||||
// WHERE id = ?;
|
|
||||||
// `, [id]);
|
|
||||||
// }
|
|
||||||
|
|
||||||
async updateServers(servers: ServerSettings[]): Promise<void> {
|
|
||||||
await this.transaction((tx) => {
|
|
||||||
tx.executeSql(`
|
|
||||||
DELETE FROM servers
|
|
||||||
`);
|
|
||||||
for (const s of servers) {
|
|
||||||
tx.executeSql(`
|
|
||||||
INSERT INTO servers (id, address, username, token, salt)
|
|
||||||
VALUES (?, ?, ?, ?, ?);
|
|
||||||
`, [s.id, s.address, s.username, s.token, s.salt]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getApp(): Promise<AppSettings> {
|
|
||||||
return (await this.executeSql(`
|
|
||||||
SELECT * FROM app;
|
|
||||||
`))[0].rows.raw().map(x => ({
|
|
||||||
server: x.server || undefined,
|
|
||||||
}))[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateApp(app: AppSettings): Promise<void> {
|
|
||||||
await this.executeSql(`
|
|
||||||
UPDATE app SET
|
|
||||||
server = ?;
|
|
||||||
`, [
|
|
||||||
app.server || null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { DOMParser } from 'xmldom';
|
|||||||
import RNFS from 'react-native-fs';
|
import RNFS from 'react-native-fs';
|
||||||
import { GetAlbumList2Params, GetAlbumListParams, GetAlbumParams, GetArtistInfo2Params, GetArtistInfoParams, GetCoverArtParams, GetIndexesParams, GetMusicDirectoryParams } from './params';
|
import { GetAlbumList2Params, GetAlbumListParams, GetAlbumParams, GetArtistInfo2Params, GetArtistInfoParams, GetCoverArtParams, GetIndexesParams, GetMusicDirectoryParams } from './params';
|
||||||
import { GetAlbumList2Response, GetAlbumListResponse, GetAlbumResponse, GetArtistInfo2Response, GetArtistInfoResponse, GetArtistsResponse, GetIndexesResponse, GetMusicDirectoryResponse, SubsonicResponse } from './responses';
|
import { GetAlbumList2Response, GetAlbumListResponse, GetAlbumResponse, GetArtistInfo2Response, GetArtistInfoResponse, GetArtistsResponse, GetIndexesResponse, GetMusicDirectoryResponse, SubsonicResponse } from './responses';
|
||||||
import { ServerSettings } from '../models/settings';
|
import { Server } from '../models/settings';
|
||||||
import paths from '../paths';
|
import paths from '../paths';
|
||||||
|
|
||||||
export class SubsonicApiError extends Error {
|
export class SubsonicApiError extends Error {
|
||||||
@ -58,7 +58,7 @@ export class SubsonicApiClient {
|
|||||||
|
|
||||||
private params: URLSearchParams
|
private params: URLSearchParams
|
||||||
|
|
||||||
constructor(server: ServerSettings) {
|
constructor(server: Server) {
|
||||||
this.address = server.address;
|
this.address = server.address;
|
||||||
this.username = server.username;
|
this.username = server.username;
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export class SubsonicApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = `${this.address}/rest/${method}?${query}`;
|
const url = `${this.address}/rest/${method}?${query}`;
|
||||||
console.log(url);
|
// console.log(url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,4 +189,8 @@ export class SubsonicApiClient {
|
|||||||
const path = `${paths.songCache}/${params.id}`;
|
const path = `${paths.songCache}/${params.id}`;
|
||||||
return await this.apiDownload('getCoverArt', path, params);
|
return await this.apiDownload('getCoverArt', path, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCoverArtUri(params: GetCoverArtParams): string {
|
||||||
|
return this.buildUrl('getCoverArt', params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user