diff --git a/App.tsx b/App.tsx
index 81b05eb..e5b8d85 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,24 +1,11 @@
import React from 'react';
import { RecoilRoot } from 'recoil';
-import Layout from './src/components/NowPlayingLayout';
+import ArtistsList from './src/components/ArtistsList';
-const App = () => {
- return (
-
-
-
- );
-}
+const 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();
diff --git a/package-lock.json b/package-lock.json
index ed070ba..e68e36b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
+ "react-native-sqlite-storage": "^5.0.0",
"recoil": "^0.3.1",
"xmldom": "^0.5.0"
},
@@ -21,6 +22,7 @@
"@types/jest": "^26.0.23",
"@types/md5": "^2.3.0",
"@types/react-native": "^0.64.5",
+ "@types/react-native-sqlite-storage": "^5.0.0",
"@types/react-test-renderer": "^16.9.2",
"@types/xmldom": "^0.1.30",
"babel-jest": "^26.6.3",
@@ -3127,6 +3129,12 @@
"@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": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
@@ -10640,6 +10648,14 @@
"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": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
@@ -15673,6 +15689,12 @@
"@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": {
"version": "16.9.5",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-16.9.5.tgz",
@@ -21418,6 +21440,12 @@
"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": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
diff --git a/package.json b/package.json
index 7e05b6f..a0ea4b9 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
+ "react-native-sqlite-storage": "^5.0.0",
"recoil": "^0.3.1",
"xmldom": "^0.5.0"
},
@@ -23,6 +24,7 @@
"@types/jest": "^26.0.23",
"@types/md5": "^2.3.0",
"@types/react-native": "^0.64.5",
+ "@types/react-native-sqlite-storage": "^5.0.0",
"@types/react-test-renderer": "^16.9.2",
"@types/xmldom": "^0.1.30",
"babel-jest": "^26.6.3",
diff --git a/src/components/ArtistsList.tsx b/src/components/ArtistsList.tsx
new file mode 100644
index 0000000..577eb69
--- /dev/null
+++ b/src/components/ArtistsList.tsx
@@ -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 }) => (
+
+ {item.id}
+ {item.name}
+
+);
+
+const List = () => {
+ const artists = useRecoilValue(artistsState);
+
+ const renderItem: React.FC<{ item: ArtistState }> = ({ item }) => (
+
+ );
+
+ return (
+ item.id}
+ />
+ );
+}
+
+const ArtistsList = () => {
+ const resetArtists = useResetRecoilState(artistsState);
+ const updateArtists = useUpdateArtists();
+
+ return (
+
+
+
+ Loading...}>
+
+
+
+ );
+}
+
+export default ArtistsList;
diff --git a/src/db/client.ts b/src/db/client.ts
new file mode 100644
index 0000000..7b62493
--- /dev/null
+++ b/src/db/client.ts
@@ -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
+
+ private async openDb(): Promise {
+ return await SQLite.openDatabase({ ...this.dbParams });
+ }
+
+ async deleteDb(): Promise {
+ 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 {
+ 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 {
+ return;
+ }
+}
diff --git a/src/state/artists.ts b/src/state/artists.ts
new file mode 100644
index 0000000..1fdaf5d
--- /dev/null
+++ b/src/state/artists.ts
@@ -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({
+ 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]
+ );
+}
\ No newline at end of file