mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
half decent album list with art
This commit is contained in:
parent
ff94889644
commit
b4fee0aff4
3
App.tsx
3
App.tsx
@ -1,13 +1,16 @@
|
||||
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';
|
||||
|
||||
const App = () => (
|
||||
<RecoilRoot>
|
||||
<SplashPage>
|
||||
<NavigationContainer>
|
||||
<RootNavigator />
|
||||
</NavigationContainer>
|
||||
</SplashPage>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import com.facebook.react.PackageList;
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.rnfs.RNFSPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
rootProject.name = 'SubSonify'
|
||||
include ':react-native-fs'
|
||||
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
|
||||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
include ':app'
|
||||
|
||||
@ -12,6 +12,8 @@ target 'SubSonify' do
|
||||
:hermes_enabled => false
|
||||
)
|
||||
|
||||
pod 'RNFS', :path => '../node_modules/react-native-fs'
|
||||
|
||||
target 'SubSonifyTests' do
|
||||
inherit! :complete
|
||||
# Pods for testing
|
||||
|
||||
1576
package-lock.json
generated
1576
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@
|
||||
"md5": "^2.3.0",
|
||||
"react": "17.0.1",
|
||||
"react-native": "0.64.1",
|
||||
"react-native-fs": "^2.18.0",
|
||||
"react-native-gesture-handler": "^1.10.3",
|
||||
"react-native-get-random-values": "^1.7.0",
|
||||
"react-native-linear-gradient": "^2.5.6",
|
||||
|
||||
BIN
res/record-l.png
Normal file
BIN
res/record-l.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
res/record-m.png
Normal file
BIN
res/record-m.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
61
src/components/SplashPage.tsx
Normal file
61
src/components/SplashPage.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import RNFS from 'react-native-fs';
|
||||
import { musicDb, settingsDb } from '../clients';
|
||||
|
||||
async function mkdir(path: string): Promise<void> {
|
||||
const exists = await RNFS.exists(path);
|
||||
if (exists) {
|
||||
const isDir = (await RNFS.stat(path)).isDirectory();
|
||||
if (!isDir) {
|
||||
throw new Error(`path exists and is not a directory: ${path}`);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return await RNFS.mkdir(path);
|
||||
}
|
||||
|
||||
const SplashPage: React.FC<{}> = ({ children }) => {
|
||||
const [ready, setReady] = useState(false);
|
||||
|
||||
const minSplashTime = new Promise(resolve => setTimeout(resolve, 1));
|
||||
|
||||
const prepare = async () => {
|
||||
const filesPath = RNFS.DocumentDirectoryPath;
|
||||
|
||||
await mkdir(`${filesPath}/image_cache`);
|
||||
await mkdir(`${filesPath}/song_cache`);
|
||||
await mkdir(`${filesPath}/songs`);
|
||||
|
||||
await musicDb.openDb();
|
||||
await settingsDb.openDb();
|
||||
|
||||
if (!(await musicDb.dbExists())) {
|
||||
await musicDb.createDb();
|
||||
}
|
||||
if (!(await settingsDb.dbExists())) {
|
||||
await settingsDb.createDb();
|
||||
}
|
||||
}
|
||||
|
||||
const promise = Promise.all([
|
||||
prepare(), minSplashTime,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
promise.then(() => {
|
||||
setReady(true);
|
||||
});
|
||||
})
|
||||
|
||||
if (!ready) {
|
||||
return <Text>Loading THE GOOD SHIT...</Text>
|
||||
}
|
||||
return (
|
||||
<View style={{ flex: 1 }}>{children}</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default SplashPage;
|
||||
@ -1,8 +1,117 @@
|
||||
import React from 'react';
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import { View, Image, Text, FlatList, Button, ListRenderItem } from 'react-native';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Album } from '../../models/music';
|
||||
import { albumsState, albumState, useUpdateAlbums, albumIdsState, useCoverArtUri } from '../../state/albums';
|
||||
import TopTabContainer from '../common/TopTabContainer';
|
||||
import textStyles from '../../styles/text';
|
||||
import { ScrollView } from 'react-native-gesture-handler';
|
||||
import colors from '../../styles/colors';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
|
||||
const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ height, width, id }) => {
|
||||
const coverArtSource = useCoverArtUri(id);
|
||||
|
||||
return (
|
||||
<LinearGradient
|
||||
colors={[colors.accent, colors.accentLow]}
|
||||
style={{
|
||||
height, width,
|
||||
backgroundColor: 'white',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={coverArtSource ? { uri: coverArtSource } : require('../../../res/record-m.png')}
|
||||
style={{
|
||||
height, width,
|
||||
}}
|
||||
/>
|
||||
</LinearGradient>
|
||||
)
|
||||
}
|
||||
|
||||
const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
||||
const album = useRecoilValue(albumState(id));
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log(album.name);
|
||||
// });
|
||||
|
||||
return (
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 6,
|
||||
marginLeft: 6,
|
||||
// height: 200,
|
||||
}}>
|
||||
<AlbumArt
|
||||
width={56}
|
||||
height={56}
|
||||
id={album.coverArt}
|
||||
/>
|
||||
<Text style={{
|
||||
...textStyles.paragraph,
|
||||
marginLeft: 12,
|
||||
}}>{album.name}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const MemoAlbumItem = memo(AlbumItem, (prev, next) => {
|
||||
// console.log('prev: ' + JSON.stringify(prev) + ' next: ' + JSON.stringify(next))
|
||||
return prev.id == next.id;
|
||||
});
|
||||
|
||||
const AlbumsList = () => {
|
||||
const albumIds = useRecoilValue(albumIdsState);
|
||||
const updateAlbums = useUpdateAlbums();
|
||||
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const renderItem: React.FC<{ item: string }> = ({ item }) => (
|
||||
<MemoAlbumItem id={item} />
|
||||
);
|
||||
|
||||
const refresh = async () => {
|
||||
setRefreshing(true);
|
||||
await updateAlbums();
|
||||
setRefreshing(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!refreshing && albumIds.length === 0) {
|
||||
refresh();
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
{/* <Button
|
||||
title='Update'
|
||||
onPress={updateAlbums}
|
||||
/> */}
|
||||
<FlatList
|
||||
data={albumIds}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item}
|
||||
onRefresh={refresh}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
{/* <ScrollView>
|
||||
{Object.values(albums).map(item => (
|
||||
<AlbumItem item={item} key={item.id} />
|
||||
))}
|
||||
</ScrollView> */}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const AlbumsTab = () => (
|
||||
<TopTabContainer>
|
||||
<React.Suspense fallback={<Text>Loading...</Text>}>
|
||||
<AlbumsList />
|
||||
</React.Suspense>
|
||||
</TopTabContainer>
|
||||
);
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
|
||||
</View>
|
||||
);
|
||||
|
||||
const ArtistsTab = () => {
|
||||
const ArtistsList = () => {
|
||||
const artists = useRecoilValue(artistsState);
|
||||
|
||||
const renderItem: React.FC<{ item: Artist }> = ({ item }) => (
|
||||
@ -35,14 +35,18 @@ const ArtistsTab = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<TopTabContainer>
|
||||
<FlatList
|
||||
data={artists}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.id}
|
||||
/>
|
||||
</TopTabContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const ArtistsTab = () => (
|
||||
<TopTabContainer>
|
||||
<ArtistsList />
|
||||
</TopTabContainer>
|
||||
);
|
||||
|
||||
export default ArtistsTab;
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
export interface Artist {
|
||||
id: string;
|
||||
name: string;
|
||||
starred?: Date;
|
||||
coverArt?: string;
|
||||
}
|
||||
|
||||
export interface Album {
|
||||
id: string;
|
||||
name: string;
|
||||
starred?: Date;
|
||||
coverArt?: string;
|
||||
coverArtPath?: string;
|
||||
coverArtModified?: Date;
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { atom, DefaultValue, selector, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { atom, DefaultValue, selector, selectorFamily, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { SubsonicApiClient } from '../subsonic/api';
|
||||
import { activeServer } from './settings'
|
||||
import { Artist } from '../models/music';
|
||||
import { Album } from '../models/music';
|
||||
import { musicDb } from '../clients';
|
||||
import { useEffect, useState } from 'react';
|
||||
import RNFS from 'react-native-fs';
|
||||
|
||||
export const albumsState = atom<Artist[]>({
|
||||
export const albumsState = atom<{ [id: string]: Album }>({
|
||||
key: 'albumsState',
|
||||
default: selector({
|
||||
key: 'albumsState/default',
|
||||
@ -14,28 +16,99 @@ export const albumsState = atom<Artist[]>({
|
||||
({ onSet }) => {
|
||||
onSet((newValue) => {
|
||||
if (!(newValue instanceof DefaultValue)) {
|
||||
musicDb.updateAlbums(newValue);
|
||||
musicDb.updateAlbums(Object.values(newValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// export const useUpdateAlbums = () => {
|
||||
// const setAlbums = useSetRecoilState(albumsState);
|
||||
// const server = useRecoilValue(activeServer);
|
||||
export const albumIdsState = selector<string[]>({
|
||||
key: 'albumIdsState',
|
||||
get: ({get}) => Object.keys(get(albumsState)),
|
||||
});
|
||||
|
||||
// return async () => {
|
||||
// if (!server) {
|
||||
// return;
|
||||
export const albumState = selectorFamily<Album, string>({
|
||||
key: 'albumState',
|
||||
get: id => ({ get }) => {
|
||||
return get(albumsState)[id];
|
||||
},
|
||||
// set: id => ({ set, get }, newValue) => {
|
||||
// if (!(newValue instanceof DefaultValue)) {
|
||||
// set(albumsState, prevState => ({ ...prevState, [id]: newValue }));
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
// const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
|
||||
// const response = await client.getAlbums();
|
||||
export const useUpdateAlbums = () => {
|
||||
const setAlbums = useSetRecoilState(albumsState);
|
||||
const server = useRecoilValue(activeServer);
|
||||
|
||||
// setAlbums(response.data.albums.map(i => ({
|
||||
// id: i.id,
|
||||
// name: i.name,
|
||||
// })));
|
||||
// };
|
||||
// };
|
||||
return async () => {
|
||||
if (!server) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
|
||||
const response = await client.getAlbumList2({ type: 'alphabeticalByArtist', size: 50 });
|
||||
|
||||
const albums = response.data.albums.reduce((acc, x) => {
|
||||
acc[x.id] = {
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
coverArt: x.coverArt,
|
||||
};
|
||||
return acc;
|
||||
}, {} as { [id: string]: Album });
|
||||
|
||||
setAlbums(albums);
|
||||
};
|
||||
};
|
||||
|
||||
export function useCoverArtUri(id: string | undefined): string | undefined {
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const server = useRecoilValue(activeServer);
|
||||
|
||||
const [downloadAttempted, setdownloadAttempted] = useState(false);
|
||||
const [coverArtSource, setCoverArtSource] = useState<string | undefined>(undefined);
|
||||
|
||||
const getCoverArt = async () => {
|
||||
if (coverArtSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const filePath = `${RNFS.DocumentDirectoryPath}/image_cache/${id}`;
|
||||
const fileUri = `file://${filePath}`;
|
||||
|
||||
if (await RNFS.exists(filePath)) {
|
||||
// file already in cache, return the file
|
||||
setCoverArtSource(fileUri);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!server) {
|
||||
// can't download without server set
|
||||
return;
|
||||
}
|
||||
|
||||
setdownloadAttempted(true);
|
||||
if (downloadAttempted) {
|
||||
// don't try to download more than once using this hook
|
||||
return;
|
||||
}
|
||||
|
||||
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
|
||||
await client.getCoverArt({ id });
|
||||
|
||||
setCoverArtSource(fileUri);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getCoverArt();
|
||||
});
|
||||
|
||||
return coverArtSource;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ SQLite.enablePromise(true);
|
||||
|
||||
export abstract class DbStorage {
|
||||
private dbParams: DatabaseParams;
|
||||
private db: SQLiteDatabase | undefined;
|
||||
|
||||
constructor(dbParams: DatabaseParams) {
|
||||
this.dbParams = dbParams;
|
||||
@ -36,36 +37,23 @@ export abstract class DbStorage {
|
||||
return results[0].rows.length > 0;
|
||||
}
|
||||
|
||||
private async openDb(): Promise<SQLiteDatabase> {
|
||||
return await SQLite.openDatabase({ ...this.dbParams });
|
||||
async openDb(): Promise<void> {
|
||||
this.db = await SQLite.openDatabase({ ...this.dbParams });
|
||||
}
|
||||
|
||||
async deleteDb(): Promise<void> {
|
||||
if (this.db) {
|
||||
await this.db.close();
|
||||
}
|
||||
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
|
||||
return await db.executeSql(sql, params);
|
||||
} catch (err) {
|
||||
try { await db.close(); } catch {}
|
||||
throw err;
|
||||
} finally {
|
||||
try { await db.close(); } catch {}
|
||||
}
|
||||
return await (this.db as SQLiteDatabase).executeSql(sql, params);
|
||||
}
|
||||
|
||||
async transaction(scope: (tx: Transaction) => void): Promise<void> {
|
||||
const db = await this.openDb();
|
||||
try {
|
||||
await db.transaction(scope);
|
||||
} catch (err) {
|
||||
try { await db.close(); } catch {}
|
||||
throw err;
|
||||
} finally {
|
||||
try { await db.close(); } catch {}
|
||||
}
|
||||
await (this.db as SQLiteDatabase).transaction(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,8 @@ export class MusicDb extends DbStorage {
|
||||
CREATE TABLE albums (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
starred TEXT
|
||||
starred TEXT,
|
||||
coverArt TEXT
|
||||
);
|
||||
`);
|
||||
});
|
||||
@ -32,6 +33,7 @@ export class MusicDb extends DbStorage {
|
||||
`))[0].rows.raw().map(x => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
starred: x.starred ? new Date(x.starred) : undefined,
|
||||
coverArt: x.coverArt || undefined,
|
||||
}));
|
||||
}
|
||||
@ -43,20 +45,56 @@ export class MusicDb extends DbStorage {
|
||||
`);
|
||||
for (const a of artists) {
|
||||
tx.executeSql(`
|
||||
INSERT INTO artists (id, name, starred, coverArt)
|
||||
INSERT INTO artists (
|
||||
id,
|
||||
name,
|
||||
starred,
|
||||
coverArt
|
||||
)
|
||||
VALUES (?, ?, ?, ?);
|
||||
`, [a.id, a.name, null, a.coverArt || null]);
|
||||
`, [
|
||||
a.id,
|
||||
a.name,
|
||||
a.starred ? a.starred.toISOString() : null,
|
||||
a.coverArt || null
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getAlbums(): Promise<Album[]> {
|
||||
return (await this.executeSql(`
|
||||
SELECT * FROM albums;
|
||||
`))[0].rows.raw().map(x => ({
|
||||
async getAlbum(id: string): Promise<Album> {
|
||||
const results = await this.executeSql(`
|
||||
SELECT * FROM albums
|
||||
WHERE id = ?;
|
||||
`, [id]);
|
||||
|
||||
const rows = results[0].rows.raw();
|
||||
return rows.map(x => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
}));
|
||||
starred: x.starred ? new Date(x.starred) : undefined,
|
||||
coverArt: x.coverArt || undefined,
|
||||
}))[0];
|
||||
}
|
||||
|
||||
async getAlbumIds(): Promise<string[]> {
|
||||
return (await this.executeSql(`
|
||||
SELECT id FROM albums;
|
||||
`))[0].rows.raw().map(x => x.id);
|
||||
}
|
||||
|
||||
async getAlbums(): Promise<{[id: string]: Album}> {
|
||||
return (await this.executeSql(`
|
||||
SELECT * FROM albums;
|
||||
`))[0].rows.raw().reduce((acc, x) => {
|
||||
acc[x.id] = {
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
starred: x.starred ? new Date(x.starred) : undefined,
|
||||
coverArt: x.coverArt || undefined,
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
async updateAlbums(albums: Album[]): Promise<void> {
|
||||
@ -66,9 +104,19 @@ export class MusicDb extends DbStorage {
|
||||
`);
|
||||
for (const a of albums) {
|
||||
tx.executeSql(`
|
||||
INSERT INTO albums (id, name, starred)
|
||||
VALUES (?, ?, ?);
|
||||
`, [a.id, a.name, null]);
|
||||
INSERT INTO albums (
|
||||
id,
|
||||
name,
|
||||
starred,
|
||||
coverArt
|
||||
)
|
||||
VALUES (?, ?, ?, ?);
|
||||
`, [
|
||||
a.id,
|
||||
a.name,
|
||||
a.starred ? a.starred.toISOString() : null,
|
||||
a.coverArt || null
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -9,4 +9,5 @@ export default {
|
||||
low: '#000000',
|
||||
},
|
||||
accent: '#c260e5',
|
||||
accentLow: '#50285e',
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { DOMParser } from 'xmldom';
|
||||
import { GetAlbumList2Params, GetAlbumListParams, GetArtistInfo2Params, GetArtistInfoParams, GetIndexesParams } from './params';
|
||||
import RNFS from 'react-native-fs';
|
||||
import { GetAlbumList2Params, GetAlbumListParams, GetArtistInfo2Params, GetArtistInfoParams, GetCoverArtParams, GetIndexesParams } from './params';
|
||||
import { GetAlbumList2Response, GetAlbumListResponse, GetArtistInfo2Response, GetArtistInfoResponse, GetArtistsResponse, GetIndexesResponse, SubsonicResponse } from './responses';
|
||||
|
||||
export class SubsonicApiError extends Error {
|
||||
@ -35,7 +36,7 @@ export class SubsonicApiClient {
|
||||
this.params.append('c', 'subsonify-cool-unique-app-string')
|
||||
}
|
||||
|
||||
private async apiRequest(method: string, params?: {[key: string]: any}): Promise<Document> {
|
||||
private buildUrl(method: string, params?: {[key: string]: any}): string {
|
||||
let query = this.params.toString();
|
||||
if (params) {
|
||||
const urlParams = this.obj2Params(params);
|
||||
@ -45,10 +46,21 @@ export class SubsonicApiClient {
|
||||
}
|
||||
|
||||
const url = `${this.address}/rest/${method}?${query}`;
|
||||
|
||||
console.log(url);
|
||||
return url;
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
private async apiDownload(method: string, path: string, params?: {[key: string]: any}): Promise<string> {
|
||||
await RNFS.downloadFile({
|
||||
fromUrl: this.buildUrl(method, params),
|
||||
toFile: path,
|
||||
}).promise;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private async apiGetXml(method: string, params?: {[key: string]: any}): Promise<Document> {
|
||||
const response = await fetch(this.buildUrl(method, params));
|
||||
const text = await response.text();
|
||||
|
||||
console.log(text);
|
||||
@ -80,7 +92,7 @@ export class SubsonicApiClient {
|
||||
//
|
||||
|
||||
async ping(): Promise<SubsonicResponse<null>> {
|
||||
const xml = await this.apiRequest('ping');
|
||||
const xml = await this.apiGetXml('ping');
|
||||
return new SubsonicResponse<null>(xml, null);
|
||||
}
|
||||
|
||||
@ -89,22 +101,22 @@ export class SubsonicApiClient {
|
||||
//
|
||||
|
||||
async getArtists(): Promise<SubsonicResponse<GetArtistsResponse>> {
|
||||
const xml = await this.apiRequest('getArtists');
|
||||
const xml = await this.apiGetXml('getArtists');
|
||||
return new SubsonicResponse<GetArtistsResponse>(xml, new GetArtistsResponse(xml));
|
||||
}
|
||||
|
||||
async getIndexes(params?: GetIndexesParams): Promise<SubsonicResponse<GetIndexesResponse>> {
|
||||
const xml = await this.apiRequest('getIndexes', params);
|
||||
const xml = await this.apiGetXml('getIndexes', params);
|
||||
return new SubsonicResponse<GetIndexesResponse>(xml, new GetIndexesResponse(xml));
|
||||
}
|
||||
|
||||
async getArtistInfo(params: GetArtistInfoParams): Promise<SubsonicResponse<GetArtistInfoResponse>> {
|
||||
const xml = await this.apiRequest('getArtistInfo', params);
|
||||
const xml = await this.apiGetXml('getArtistInfo', params);
|
||||
return new SubsonicResponse<GetArtistInfoResponse>(xml, new GetArtistInfoResponse(xml));
|
||||
}
|
||||
|
||||
async getArtistInfo2(params: GetArtistInfo2Params): Promise<SubsonicResponse<GetArtistInfo2Response>> {
|
||||
const xml = await this.apiRequest('getArtistInfo2', params);
|
||||
const xml = await this.apiGetXml('getArtistInfo2', params);
|
||||
return new SubsonicResponse<GetArtistInfo2Response>(xml, new GetArtistInfo2Response(xml));
|
||||
}
|
||||
|
||||
@ -113,12 +125,21 @@ export class SubsonicApiClient {
|
||||
//
|
||||
|
||||
async getAlbumList(params: GetAlbumListParams): Promise<SubsonicResponse<GetAlbumListResponse>> {
|
||||
const xml = await this.apiRequest('getAlbumList', params);
|
||||
const xml = await this.apiGetXml('getAlbumList', params);
|
||||
return new SubsonicResponse<GetAlbumListResponse>(xml, new GetAlbumListResponse(xml));
|
||||
}
|
||||
|
||||
async getAlbumList2(params: GetAlbumList2Params): Promise<SubsonicResponse<GetAlbumList2Response>> {
|
||||
const xml = await this.apiRequest('getAlbumList2', params);
|
||||
const xml = await this.apiGetXml('getAlbumList2', params);
|
||||
return new SubsonicResponse<GetAlbumList2Response>(xml, new GetAlbumList2Response(xml));
|
||||
}
|
||||
|
||||
//
|
||||
// Media retrieval
|
||||
//
|
||||
|
||||
async getCoverArt(params: GetCoverArtParams): Promise<string> {
|
||||
const path = `${RNFS.DocumentDirectoryPath}/image_cache/${params.id}`;
|
||||
return await this.apiDownload('getCoverArt', path, params);
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,3 +45,12 @@ export type GetAlbumList2Params = {
|
||||
} | GetAlbumList2TypeByYear | GetAlbumList2TypeByGenre;
|
||||
|
||||
export type GetAlbumListParams = GetAlbumList2Params;
|
||||
|
||||
//
|
||||
// Media retrieval
|
||||
//
|
||||
|
||||
export type GetCoverArtParams = {
|
||||
id: string;
|
||||
size?: string;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user