mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
first interactions with db (state cache)
now we're cookin' with custom hooks
This commit is contained in:
parent
6fb4b30294
commit
2a6821c25d
25
App.tsx
25
App.tsx
@ -1,24 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RecoilRoot } from 'recoil';
|
import { RecoilRoot } from 'recoil';
|
||||||
import Layout from './src/components/NowPlayingLayout';
|
import ArtistsList from './src/components/ArtistsList';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => (
|
||||||
return (
|
<RecoilRoot>
|
||||||
<RecoilRoot>
|
<ArtistsList />
|
||||||
<Layout />
|
</RecoilRoot>
|
||||||
</RecoilRoot>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
import { SubsonicApiClient } from './src/subsonic/client';
|
|
||||||
import md5 from 'md5';
|
|
||||||
|
|
||||||
const password = 'test';
|
|
||||||
const salt = 'salty';
|
|
||||||
const token = md5(password + salt);
|
|
||||||
|
|
||||||
const client = new SubsonicApiClient('http://navidrome.home', 'austin', token, salt);
|
|
||||||
|
|
||||||
client.getIndexes();
|
|
||||||
|
|||||||
28
package-lock.json
generated
28
package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-native": "0.64.1",
|
"react-native": "0.64.1",
|
||||||
|
"react-native-sqlite-storage": "^5.0.0",
|
||||||
"recoil": "^0.3.1",
|
"recoil": "^0.3.1",
|
||||||
"xmldom": "^0.5.0"
|
"xmldom": "^0.5.0"
|
||||||
},
|
},
|
||||||
@ -21,6 +22,7 @@
|
|||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/md5": "^2.3.0",
|
"@types/md5": "^2.3.0",
|
||||||
"@types/react-native": "^0.64.5",
|
"@types/react-native": "^0.64.5",
|
||||||
|
"@types/react-native-sqlite-storage": "^5.0.0",
|
||||||
"@types/react-test-renderer": "^16.9.2",
|
"@types/react-test-renderer": "^16.9.2",
|
||||||
"@types/xmldom": "^0.1.30",
|
"@types/xmldom": "^0.1.30",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
@ -3127,6 +3129,12 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-native-sqlite-storage": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-kn5J+oLU//Jk9YshL9bcrm1Pxy4fp/7Pk8+yGZIeu/aEG8SmG75nxDzpfTopEluThDtjPDxFUWVDD0Ij/eCNJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/react-test-renderer": {
|
"node_modules/@types/react-test-renderer": {
|
||||||
"version": "16.9.5",
|
"version": "16.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
|
||||||
@ -10640,6 +10648,14 @@
|
|||||||
"nullthrows": "^1.1.1"
|
"nullthrows": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-sqlite-storage": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-c1Joq3/tO1nmIcP8SkRZNolPSbfvY8uZg5lXse0TmjIPC0qHVbk96IMvWGyly1TmYCIpxpuDRc0/xCffDbYIvg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-native": ">=0.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native/node_modules/ws": {
|
"node_modules/react-native/node_modules/ws": {
|
||||||
"version": "6.2.2",
|
"version": "6.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
|
||||||
@ -15673,6 +15689,12 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-native-sqlite-storage": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-kn5J+oLU//Jk9YshL9bcrm1Pxy4fp/7Pk8+yGZIeu/aEG8SmG75nxDzpfTopEluThDtjPDxFUWVDD0Ij/eCNJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/react-test-renderer": {
|
"@types/react-test-renderer": {
|
||||||
"version": "16.9.5",
|
"version": "16.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
|
||||||
@ -21418,6 +21440,12 @@
|
|||||||
"nullthrows": "^1.1.1"
|
"nullthrows": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-native-sqlite-storage": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-sqlite-storage/-/react-native-sqlite-storage-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-c1Joq3/tO1nmIcP8SkRZNolPSbfvY8uZg5lXse0TmjIPC0qHVbk96IMvWGyly1TmYCIpxpuDRc0/xCffDbYIvg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"react-refresh": {
|
"react-refresh": {
|
||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-native": "0.64.1",
|
"react-native": "0.64.1",
|
||||||
|
"react-native-sqlite-storage": "^5.0.0",
|
||||||
"recoil": "^0.3.1",
|
"recoil": "^0.3.1",
|
||||||
"xmldom": "^0.5.0"
|
"xmldom": "^0.5.0"
|
||||||
},
|
},
|
||||||
@ -23,6 +24,7 @@
|
|||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/md5": "^2.3.0",
|
"@types/md5": "^2.3.0",
|
||||||
"@types/react-native": "^0.64.5",
|
"@types/react-native": "^0.64.5",
|
||||||
|
"@types/react-native-sqlite-storage": "^5.0.0",
|
||||||
"@types/react-test-renderer": "^16.9.2",
|
"@types/react-test-renderer": "^16.9.2",
|
||||||
"@types/xmldom": "^0.1.30",
|
"@types/xmldom": "^0.1.30",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
|
|||||||
53
src/components/ArtistsList.tsx
Normal file
53
src/components/ArtistsList.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, FlatList, Text, View } from 'react-native';
|
||||||
|
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||||
|
import { artistsState, ArtistState, useUpdateArtists } from '../state/artists';
|
||||||
|
|
||||||
|
const ArtistItem: React.FC<{ item: ArtistState } > = ({ item }) => (
|
||||||
|
<View>
|
||||||
|
<Text>{item.id}</Text>
|
||||||
|
<Text style={{
|
||||||
|
fontSize: 60,
|
||||||
|
paddingBottom: 400,
|
||||||
|
}}>{item.name}</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
const List = () => {
|
||||||
|
const artists = useRecoilValue(artistsState);
|
||||||
|
|
||||||
|
const renderItem: React.FC<{ item: ArtistState }> = ({ item }) => (
|
||||||
|
<ArtistItem item={item} />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
data={artists}
|
||||||
|
renderItem={renderItem}
|
||||||
|
keyExtractor={item => item.id}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArtistsList = () => {
|
||||||
|
const resetArtists = useResetRecoilState(artistsState);
|
||||||
|
const updateArtists = useUpdateArtists();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Button
|
||||||
|
title='Reset to default'
|
||||||
|
onPress={resetArtists}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title='Update from API'
|
||||||
|
onPress={updateArtists}
|
||||||
|
/>
|
||||||
|
<React.Suspense fallback={<Text>Loading...</Text>}>
|
||||||
|
<List />
|
||||||
|
</React.Suspense>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArtistsList;
|
||||||
67
src/db/client.ts
Normal file
67
src/db/client.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import SQLite, { DatabaseParams, ResultSet, SQLiteDatabase } from 'react-native-sqlite-storage';
|
||||||
|
|
||||||
|
SQLite.enablePromise(true);
|
||||||
|
|
||||||
|
abstract class DbStorage {
|
||||||
|
private dbParams: DatabaseParams;
|
||||||
|
|
||||||
|
constructor(dbParams: DatabaseParams) {
|
||||||
|
this.dbParams = dbParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract createDb(): Promise<void>
|
||||||
|
|
||||||
|
private async openDb(): Promise<SQLiteDatabase> {
|
||||||
|
return await SQLite.openDatabase({ ...this.dbParams });
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteDb(): Promise<void> {
|
||||||
|
await SQLite.deleteDatabase({ ...this.dbParams });
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeSql(sql: string, params?: any[]): Promise<[ResultSet]> {
|
||||||
|
const db = await this.openDb();
|
||||||
|
try {
|
||||||
|
// https://github.com/andpor/react-native-sqlite-storage/issues/410
|
||||||
|
// if (params) {
|
||||||
|
// for (const p of params) {
|
||||||
|
// if (Array.isArray(p)) {
|
||||||
|
// throw new Error('param value cannot be an array');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return await db.executeSql(sql, params);
|
||||||
|
} catch (err) {
|
||||||
|
try { await db.close(); } catch {}
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
try { await db.close(); } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MusicDb extends DbStorage {
|
||||||
|
constructor() {
|
||||||
|
super({ name: 'music.db', location: 'default' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDb(): Promise<void> {
|
||||||
|
await this.executeSql(`
|
||||||
|
CREATE TABLE artists (
|
||||||
|
id PRIMARY KEY NOT NULL,
|
||||||
|
name NOT NULL,
|
||||||
|
starred
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsDb extends DbStorage {
|
||||||
|
constructor() {
|
||||||
|
super({ name: 'settings.db', location: 'Library' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDb(): Promise<void> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/state/artists.ts
Normal file
65
src/state/artists.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import md5 from 'md5';
|
||||||
|
import { atom, selector, useSetRecoilState } from 'recoil';
|
||||||
|
import { SubsonicApiClient } from '../subsonic/client';
|
||||||
|
import { MusicDb } from '../db/client';
|
||||||
|
|
||||||
|
const db = new MusicDb();
|
||||||
|
|
||||||
|
const password = 'test';
|
||||||
|
const salt = 'salty';
|
||||||
|
const token = md5(password + salt);
|
||||||
|
|
||||||
|
const client = new SubsonicApiClient('http://navidrome.home', 'austin', token, salt);
|
||||||
|
|
||||||
|
export interface ArtistState {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const artistsState = atom<ArtistState[]>({
|
||||||
|
key: 'artistsState',
|
||||||
|
default: selector({
|
||||||
|
key: 'artistsState/default',
|
||||||
|
get: async () => {
|
||||||
|
await prepopulate();
|
||||||
|
|
||||||
|
const results = await db.executeSql(`
|
||||||
|
SELECT * FROM artists;
|
||||||
|
`);
|
||||||
|
|
||||||
|
return results[0].rows.raw().map(i => ({
|
||||||
|
id: i.id,
|
||||||
|
name: i.name,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useUpdateArtists = () => {
|
||||||
|
const setArtists = useSetRecoilState(artistsState);
|
||||||
|
return async () => {
|
||||||
|
const response = await client.getArtists();
|
||||||
|
|
||||||
|
setArtists(response.data.artists.map(i => ({
|
||||||
|
id: i.id,
|
||||||
|
name: i.name,
|
||||||
|
})));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async function prepopulate() {
|
||||||
|
try { await db.deleteDb() } catch {}
|
||||||
|
await db.createDb();
|
||||||
|
await db.executeSql(`
|
||||||
|
INSERT INTO artists (id, name, starred)
|
||||||
|
VALUES (?, ?, ?);
|
||||||
|
`,
|
||||||
|
[1, 'good guy', true]
|
||||||
|
);
|
||||||
|
await db.executeSql(`
|
||||||
|
INSERT INTO artists (id, name, starred)
|
||||||
|
VALUES (?, ?, ?);
|
||||||
|
`,
|
||||||
|
[2, 'bad guy', false]
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user