diff --git a/App.tsx b/App.tsx
index 8322053..5029abe 100644
--- a/App.tsx
+++ b/App.tsx
@@ -1,17 +1,17 @@
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
-import { RecoilRoot } from 'recoil';
import SplashPage from './src/components/SplashPage';
import RootNavigator from './src/components/navigation/RootNavigator';
+import { Provider } from 'jotai';
const App = () => (
-
+
-
+
);
export default App;
diff --git a/index.js b/index.js
index 5cc85db..a327443 100644
--- a/index.js
+++ b/index.js
@@ -1,11 +1,6 @@
import 'react-native-gesture-handler';
import 'react-native-get-random-values';
-import { AppRegistry, LogBox } from 'react-native';
-// ignore recoil's timer warning on android:
-// https://github.com/facebookexperimental/Recoil/issues/1030
-LogBox.ignoreLogs(["timer"]);
-
import { enableScreens } from 'react-native-screens';
enableScreens();
diff --git a/package-lock.json b/package-lock.json
index 0321e3b..0360d70 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@react-navigation/material-top-tabs": "^5.3.15",
"@react-navigation/native": "^5.9.4",
"@react-navigation/stack": "^5.14.5",
+ "jotai": "^1.1.0",
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
@@ -27,7 +28,6 @@
"react-native-screens": "^3.4.0",
"react-native-tab-view": "^2.16.0",
"react-native-track-player": "^1.2.7",
- "recoil": "^0.3.1",
"uuid": "^8.3.2",
"xmldom": "^0.5.0"
},
@@ -6116,11 +6116,6 @@
"dev": true,
"optional": true
},
- "node_modules/hamt_plus": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
- "integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE="
- },
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -8461,6 +8456,47 @@
"@sideway/pinpoint": "^2.0.0"
}
},
+ "node_modules/jotai": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/jotai/-/jotai-1.1.0.tgz",
+ "integrity": "sha512-+c/bMxS/c1LgsOYVfevOYLAu+WEgn85TJhmBoNSNHjMV+mTKZY0ACSyCrX1yz8KialK4+Ggm6d7Ek3sTXy8UgA==",
+ "engines": {
+ "node": ">=12"
+ },
+ "peerDependencies": {
+ "@urql/core": "*",
+ "immer": "*",
+ "optics-ts": "*",
+ "react": ">=16.8",
+ "react-query": "*",
+ "valtio": "*",
+ "wonka": "*",
+ "xstate": "*"
+ },
+ "peerDependenciesMeta": {
+ "@urql/core": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "optics-ts": {
+ "optional": true
+ },
+ "react-query": {
+ "optional": true
+ },
+ "valtio": {
+ "optional": true
+ },
+ "wonka": {
+ "optional": true
+ },
+ "xstate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -10772,25 +10808,6 @@
"node": ">= 0.10"
}
},
- "node_modules/recoil": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.3.1.tgz",
- "integrity": "sha512-KNA3DRqgxX4rRC8E7fc6uIw7BACmMPuraIYy+ejhE8tsw7w32CetMm8w7AMZa34wzanKKkev3vl3H7Z4s0QSiA==",
- "dependencies": {
- "hamt_plus": "1.0.2"
- },
- "peerDependencies": {
- "react": ">=16.13.1"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- },
- "react-native": {
- "optional": true
- }
- }
- },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -17936,11 +17953,6 @@
"dev": true,
"optional": true
},
- "hamt_plus": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
- "integrity": "sha1-4hwlKWjH4zsg9qGwlM2FeHomVgE="
- },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -19673,6 +19685,12 @@
"@sideway/pinpoint": "^2.0.0"
}
},
+ "jotai": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/jotai/-/jotai-1.1.0.tgz",
+ "integrity": "sha512-+c/bMxS/c1LgsOYVfevOYLAu+WEgn85TJhmBoNSNHjMV+mTKZY0ACSyCrX1yz8KialK4+Ggm6d7Ek3sTXy8UgA==",
+ "requires": {}
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -21513,14 +21531,6 @@
"resolve": "^1.1.6"
}
},
- "recoil": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.3.1.tgz",
- "integrity": "sha512-KNA3DRqgxX4rRC8E7fc6uIw7BACmMPuraIYy+ejhE8tsw7w32CetMm8w7AMZa34wzanKKkev3vl3H7Z4s0QSiA==",
- "requires": {
- "hamt_plus": "1.0.2"
- }
- },
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
diff --git a/package.json b/package.json
index 9fdb9dd..e6706cf 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@react-navigation/material-top-tabs": "^5.3.15",
"@react-navigation/native": "^5.9.4",
"@react-navigation/stack": "^5.14.5",
+ "jotai": "^1.1.0",
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
@@ -29,7 +30,6 @@
"react-native-screens": "^3.4.0",
"react-native-tab-view": "^2.16.0",
"react-native-track-player": "^1.2.7",
- "recoil": "^0.3.1",
"uuid": "^8.3.2",
"xmldom": "^0.5.0"
},
diff --git a/src/components/ArtistsList.tsx b/src/components/ArtistsList.tsx
index 22dea90..2d6eb52 100644
--- a/src/components/ArtistsList.tsx
+++ b/src/components/ArtistsList.tsx
@@ -1,8 +1,8 @@
import React from 'react';
-import { Button, FlatList, Text, View } from 'react-native';
-import { useRecoilValue, useResetRecoilState } from 'recoil';
+import { FlatList, Text, View } from 'react-native';
+import { useAtomValue } from 'jotai/utils';
import { Artist } from '../models/music';
-import { artistsState } from '../state/music';
+import { artistsAtom } from '../state/music';
const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
@@ -15,7 +15,7 @@ const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
);
const List = () => {
- const artists = useRecoilValue(artistsState);
+ const artists = useAtomValue(artistsAtom);
const renderItem: React.FC<{ item: Artist }> = ({ item }) => (
@@ -30,24 +30,10 @@ const List = () => {
);
}
-const ListPlusControls = () => {
- const resetArtists = useResetRecoilState(artistsState);
-
- return (
-
-
-
-
- );
-}
-
const ArtistsList = () => (
Loading...}>
-
+
)
diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx
index b7e576b..6d4248b 100644
--- a/src/components/Settings.tsx
+++ b/src/components/Settings.tsx
@@ -1,10 +1,10 @@
import { useNavigation } from '@react-navigation/core';
+import { useAtom } from 'jotai';
import md5 from 'md5';
import React from 'react';
import { Button, Text, View } from 'react-native';
-import { useRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
-import { appSettingsState } from '../state/settings';
+import { appSettingsAtom } from '../state/settings';
const TestControls = () => {
const navigation = useNavigation();
@@ -19,7 +19,7 @@ const TestControls = () => {
}
const ServerSettingsView = () => {
- const [appSettings, setAppSettings] = useRecoilState(appSettingsState);
+ const [appSettings, setAppSettings] = useAtom(appSettingsAtom);
const bootstrapServer = () => {
if (appSettings.servers.length !== 0) {
diff --git a/src/components/library/AlbumsTab.tsx b/src/components/library/AlbumsTab.tsx
index 4133ee8..436e504 100644
--- a/src/components/library/AlbumsTab.tsx
+++ b/src/components/library/AlbumsTab.tsx
@@ -1,10 +1,10 @@
+import { useAtomValue } from 'jotai/utils';
import React, { useEffect } from 'react';
import { FlatList, Text, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import LinearGradient from 'react-native-linear-gradient';
-import { useRecoilValue } from 'recoil';
import { Album } from '../../models/music';
-import { albumsState, albumsUpdatingState, useUpdateAlbums } from '../../state/music';
+import { albumsAtom, albumsUpdatingAtom, useUpdateAlbums } from '../../state/music';
import colors from '../../styles/colors';
import textStyles from '../../styles/text';
import TopTabContainer from '../common/TopTabContainer';
@@ -89,8 +89,8 @@ const AlbumListRenderItem: React.FC<{ item: Album }> = ({ item }) => (
);
const AlbumsList = () => {
- const albums = useRecoilValue(albumsState);
- const updating = useRecoilValue(albumsUpdatingState);
+ const albums = useAtomValue(albumsAtom);
+ const updating = useAtomValue(albumsUpdatingAtom);
const updateAlbums = useUpdateAlbums();
useEffect(() => {
diff --git a/src/components/library/ArtistsTab.tsx b/src/components/library/ArtistsTab.tsx
index 27628b1..5c71fc4 100644
--- a/src/components/library/ArtistsTab.tsx
+++ b/src/components/library/ArtistsTab.tsx
@@ -1,8 +1,8 @@
+import { useAtomValue } from 'jotai/utils';
import React, { useEffect } from 'react';
import { FlatList, Image, Text, View } from 'react-native';
-import { useRecoilValue } from 'recoil';
import { Artist } from '../../models/music';
-import { artistsState, artistsUpdatingState, useUpdateArtists } from '../../state/music';
+import { artistsAtom, artistsUpdatingAtom, useUpdateArtists } from '../../state/music';
import textStyles from '../../styles/text';
import TopTabContainer from '../common/TopTabContainer';
@@ -28,8 +28,8 @@ const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
);
const ArtistsList = () => {
- const artists = useRecoilValue(artistsState);
- const updating = useRecoilValue(artistsUpdatingState);
+ const artists = useAtomValue(artistsAtom);
+ const updating = useAtomValue(artistsUpdatingAtom);
const updateArtists = useUpdateArtists();
useEffect(() => {
diff --git a/src/state/music.ts b/src/state/music.ts
index 1d8248e..fd513d1 100644
--- a/src/state/music.ts
+++ b/src/state/music.ts
@@ -1,27 +1,21 @@
-import { atom, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
+import { atom, useAtom } from 'jotai';
+import { useAtomValue, useUpdateAtom } from 'jotai/utils';
import { Album, Artist } from '../models/music';
import { SubsonicApiClient } from '../subsonic/api';
-import { activeServer } from './settings';
+import { activeServerAtom } from './settings';
-export const artistsState = atom({
- key: 'artistsState',
- default: [],
-});
-
-export const artistsUpdatingState = atom({
- key: 'artistsUpdatingState',
- default: false,
-});
+export const artistsAtom = atom([]);
+export const artistsUpdatingAtom = atom(false);
export const useUpdateArtists = () => {
- const server = useRecoilValue(activeServer);
+ const server = useAtomValue(activeServerAtom);
+ const [updating, setUpdating] = useAtom(artistsUpdatingAtom);
+ const setArtists = useUpdateAtom(artistsAtom);
+
if (!server) {
return () => Promise.resolve();
}
- const [updating, setUpdating] = useRecoilState(artistsUpdatingState);
- const setArtists = useSetRecoilState(artistsState);
-
return async () => {
if (updating) {
return;
@@ -42,25 +36,18 @@ export const useUpdateArtists = () => {
}
}
-export const albumsState = atom({
- key: 'albumsState',
- default: [],
-});
-
-export const albumsUpdatingState = atom({
- key: 'albumsUpdatingState',
- default: false,
-});
+export const albumsAtom = atom([]);
+export const albumsUpdatingAtom = atom(false);
export const useUpdateAlbums = () => {
- const server = useRecoilValue(activeServer);
+ const server = useAtomValue(activeServerAtom);
+ const [updating, setUpdating] = useAtom(albumsUpdatingAtom);
+ const setAlbums = useUpdateAtom(albumsAtom);
+
if (!server) {
return () => Promise.resolve();
}
- const [updating, setUpdating] = useRecoilState(albumsUpdatingState);
- const setAlbums = useSetRecoilState(albumsState);
-
return async () => {
if (updating) {
return;
diff --git a/src/state/settings.ts b/src/state/settings.ts
index dd95ba2..ef823ec 100644
--- a/src/state/settings.ts
+++ b/src/state/settings.ts
@@ -1,28 +1,12 @@
-import { atom, DefaultValue, selector } from 'recoil';
-import { AppSettings, Server } from '../models/settings';
-import { getAppSettings, setAppSettings } from '../storage/settings';
+import { atom } from 'jotai';
+import { AppSettings } from '../models/settings';
+import atomWithAsyncStorage from '../storage/atomWithAsyncStorage';
-export const appSettingsState = atom({
- key: 'appSettingsState',
- default: selector({
- key: 'appSettingsState/default',
- get: () => getAppSettings(),
- }),
- effects_UNSTABLE: [
- ({ onSet }) => {
- onSet((newValue) => {
- if (!(newValue instanceof DefaultValue)) {
- setAppSettings(newValue);
- }
- });
- }
- ],
+export const appSettingsAtom = atomWithAsyncStorage('@appSettings', {
+ servers: [],
});
-export const activeServer = selector({
- key: 'activeServer',
- get: ({get}) => {
- const appSettings = get(appSettingsState);
- return appSettings.servers.find(x => x.id == appSettings.activeServer);
- }
+export const activeServerAtom = atom((get) => {
+ const appSettings = get(appSettingsAtom);
+ return appSettings.servers.find(x => x.id == appSettings.activeServer);
});
diff --git a/src/storage/asyncstorage.ts b/src/storage/asyncstorage.ts
index dead649..d138f02 100644
--- a/src/storage/asyncstorage.ts
+++ b/src/storage/asyncstorage.ts
@@ -1,26 +1,28 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
-export async function getItem(key: string): Promise {
+export async function getItem(key: string): Promise {
try {
- return await AsyncStorage.getItem(key);
+ const item = await AsyncStorage.getItem(key);
+ return item ? JSON.parse(item) : null;
} catch (e) {
console.error(`getItem error (key: ${key})`, e);
return null;
}
}
-export async function multiGet(keys: string[]): Promise<[string, string | null][]> {
+export async function multiGet(keys: string[]): Promise<[string, any | null][]> {
try {
- return await AsyncStorage.multiGet(keys);
+ const items = await AsyncStorage.multiGet(keys);
+ return items.map(x => [x[0], x[1] ? JSON.parse(x[1]) : null]);
} catch (e) {
console.error(`multiGet error`, e);
return [];
}
}
-export async function setItem(key: string, item: string): Promise {
+export async function setItem(key: string, item: any): Promise {
try {
- await AsyncStorage.setItem(key, item);
+ await AsyncStorage.setItem(key, JSON.stringify(item));
} catch (e) {
console.error(`setItem error (key: ${key})`, e);
}
@@ -28,7 +30,7 @@ export async function setItem(key: string, item: string): Promise {
export async function multiSet(items: string[][]): Promise {
try {
- await AsyncStorage.multiSet(items);
+ await AsyncStorage.multiSet(items.map(x => [x[0], JSON.stringify(x[1])]));
} catch (e) {
console.error(`multiSet error`, e);
}
diff --git a/src/storage/atomWithAsyncStorage.ts b/src/storage/atomWithAsyncStorage.ts
new file mode 100644
index 0000000..d44008e
--- /dev/null
+++ b/src/storage/atomWithAsyncStorage.ts
@@ -0,0 +1,10 @@
+import { atomWithStorage } from 'jotai/utils';
+import { getItem, setItem } from './asyncstorage';
+
+export default (key: string, defaultValue: T) => {
+ return atomWithStorage(key, defaultValue, {
+ getItem: async () => await getItem(key) || defaultValue,
+ setItem: setItem,
+ delayInit: true,
+ });
+}
diff --git a/src/storage/settings.ts b/src/storage/settings.ts
deleted file mode 100644
index 918ddf4..0000000
--- a/src/storage/settings.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { AppSettings } from '../models/settings';
-import { getItem, setItem } from './asyncstorage';
-
-const appSettingsKey = '@appSettings';
-
-export async function getAppSettings(): Promise {
- const item = await getItem(appSettingsKey);
- return item ? JSON.parse(item) : {
- servers: [],
- };
-}
-
-export async function setAppSettings(appSettings: AppSettings): Promise {
- await setItem(appSettingsKey, JSON.stringify(appSettings));
-}