mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 09:09:29 +01:00
reworked placeholder
also fixed downloads finishing early and reading corrupted files
This commit is contained in:
parent
b4fee0aff4
commit
41d78f0d2f
3
App.tsx
3
App.tsx
@ -4,6 +4,9 @@ import { RecoilRoot } from 'recoil';
|
|||||||
import SplashPage from './src/components/SplashPage';
|
import SplashPage from './src/components/SplashPage';
|
||||||
import RootNavigator from './src/components/navigation/RootNavigator';
|
import RootNavigator from './src/components/navigation/RootNavigator';
|
||||||
|
|
||||||
|
import { enableScreens } from 'react-native-screens';
|
||||||
|
enableScreens();
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<SplashPage>
|
<SplashPage>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.subsonify;
|
package com.subsonify;
|
||||||
|
|
||||||
import com.facebook.react.ReactActivity;
|
import com.facebook.react.ReactActivity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
public class MainActivity extends ReactActivity {
|
public class MainActivity extends ReactActivity {
|
||||||
|
|
||||||
@ -12,4 +13,9 @@ public class MainActivity extends ReactActivity {
|
|||||||
protected String getMainComponentName() {
|
protected String getMainComponentName() {
|
||||||
return "SubSonify";
|
return "SubSonify";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,7 +75,7 @@ const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigat
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text style={{
|
<Text style={{
|
||||||
...textStyles.small,
|
...textStyles.xsmall,
|
||||||
color: isFocused ? colors.text.primary : colors.text.secondary,
|
color: isFocused ? colors.text.primary : colors.text.secondary,
|
||||||
}}>
|
}}>
|
||||||
{label}
|
{label}
|
||||||
|
|||||||
@ -12,22 +12,42 @@ import LinearGradient from 'react-native-linear-gradient';
|
|||||||
const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ height, width, id }) => {
|
const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ height, width, id }) => {
|
||||||
const coverArtSource = useCoverArtUri(id);
|
const coverArtSource = useCoverArtUri(id);
|
||||||
|
|
||||||
return (
|
// useEffect(() => {
|
||||||
|
// console.log(id);
|
||||||
|
// });
|
||||||
|
|
||||||
|
const Placeholder = (
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[colors.accent, colors.accentLow]}
|
colors={[colors.accent, colors.accentLow]}
|
||||||
style={{
|
style={{
|
||||||
height, width,
|
height, width,
|
||||||
backgroundColor: 'white',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
source={coverArtSource ? { uri: coverArtSource } : require('../../../res/record-m.png')}
|
source={require('../../../res/record-m.png')}
|
||||||
style={{
|
style={{
|
||||||
height, width,
|
height, width,
|
||||||
|
resizeMode: 'contain',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
)
|
);
|
||||||
|
|
||||||
|
const CoverArt = (
|
||||||
|
<View style={{
|
||||||
|
height, width,
|
||||||
|
}}>
|
||||||
|
<Image
|
||||||
|
source={{ uri: coverArtSource }}
|
||||||
|
style={{
|
||||||
|
height, width,
|
||||||
|
resizeMode: 'contain',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
return coverArtSource ? CoverArt : Placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
||||||
@ -37,23 +57,46 @@ const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
|||||||
// console.log(album.name);
|
// console.log(album.name);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
const size = 180;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View style={{
|
||||||
flexDirection: 'row',
|
// flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginVertical: 6,
|
marginVertical: 10,
|
||||||
marginLeft: 6,
|
// marginLeft: 6,
|
||||||
// height: 200,
|
// width: size,
|
||||||
|
flex: 1/2,
|
||||||
}}>
|
}}>
|
||||||
<AlbumArt
|
<AlbumArt
|
||||||
width={56}
|
width={size}
|
||||||
height={56}
|
height={size}
|
||||||
id={album.coverArt}
|
id={album.coverArt}
|
||||||
/>
|
/>
|
||||||
<Text style={{
|
<View style={{
|
||||||
...textStyles.paragraph,
|
flex: 1,
|
||||||
marginLeft: 12,
|
width: size,
|
||||||
}}>{album.name}</Text>
|
// alignItems: 'baseline',
|
||||||
|
}}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
...textStyles.itemTitle,
|
||||||
|
marginTop: 5,
|
||||||
|
}}
|
||||||
|
numberOfLines={2}
|
||||||
|
>
|
||||||
|
{album.name}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
...textStyles.itemSubtitle,
|
||||||
|
marginTop: 3,
|
||||||
|
}}
|
||||||
|
numberOfLines={1}
|
||||||
|
>
|
||||||
|
{album.name}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -83,26 +126,19 @@ const AlbumsList = () => {
|
|||||||
if (!refreshing && albumIds.length === 0) {
|
if (!refreshing && albumIds.length === 0) {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
{/* <Button
|
|
||||||
title='Update'
|
|
||||||
onPress={updateAlbums}
|
|
||||||
/> */}
|
|
||||||
<FlatList
|
<FlatList
|
||||||
data={albumIds}
|
data={albumIds}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={item => item}
|
keyExtractor={item => item}
|
||||||
onRefresh={refresh}
|
onRefresh={refresh}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
|
numColumns={2}
|
||||||
|
removeClippedSubviews={false}
|
||||||
/>
|
/>
|
||||||
{/* <ScrollView>
|
|
||||||
{Object.values(albums).map(item => (
|
|
||||||
<AlbumItem item={item} key={item.id} />
|
|
||||||
))}
|
|
||||||
</ScrollView> */}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,19 @@ const header: TextStyle = {
|
|||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
};
|
};
|
||||||
|
|
||||||
const small: TextStyle = {
|
const itemTitle: TextStyle = {
|
||||||
|
...paragraph,
|
||||||
|
fontSize: 14,
|
||||||
|
// fontFamily: 'Ubuntu-Light',
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemSubtitle: TextStyle = {
|
||||||
|
...paragraph,
|
||||||
|
fontSize: 12,
|
||||||
|
color: colors.text.secondary,
|
||||||
|
};
|
||||||
|
|
||||||
|
const xsmall: TextStyle = {
|
||||||
...paragraph,
|
...paragraph,
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
};
|
};
|
||||||
@ -20,5 +32,7 @@ const small: TextStyle = {
|
|||||||
export default {
|
export default {
|
||||||
paragraph,
|
paragraph,
|
||||||
header,
|
header,
|
||||||
small,
|
itemTitle,
|
||||||
|
itemSubtitle,
|
||||||
|
xsmall
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,6 +18,38 @@ export class SubsonicApiError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QueuePromise = () => Promise<any>;
|
||||||
|
|
||||||
|
class Queue {
|
||||||
|
maxSimultaneously: number;
|
||||||
|
|
||||||
|
private active = 0;
|
||||||
|
private queue: QueuePromise[] = [];
|
||||||
|
|
||||||
|
constructor(maxSimultaneously = 1) {
|
||||||
|
this.maxSimultaneously = maxSimultaneously;
|
||||||
|
}
|
||||||
|
|
||||||
|
async enqueue(func: QueuePromise) {
|
||||||
|
if (++this.active > this.maxSimultaneously) {
|
||||||
|
await new Promise(resolve => this.queue.push(resolve as QueuePromise));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await func();
|
||||||
|
} catch (err) {
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
this.active--;
|
||||||
|
if (this.queue.length) {
|
||||||
|
(this.queue.shift() as QueuePromise)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadQueue = new Queue(1);
|
||||||
|
|
||||||
export class SubsonicApiClient {
|
export class SubsonicApiClient {
|
||||||
address: string;
|
address: string;
|
||||||
username: string;
|
username: string;
|
||||||
@ -36,7 +68,7 @@ export class SubsonicApiClient {
|
|||||||
this.params.append('c', 'subsonify-cool-unique-app-string')
|
this.params.append('c', 'subsonify-cool-unique-app-string')
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildUrl(method: string, params?: {[key: string]: any}): string {
|
private buildUrl(method: string, params?: { [key: string]: any }): string {
|
||||||
let query = this.params.toString();
|
let query = this.params.toString();
|
||||||
if (params) {
|
if (params) {
|
||||||
const urlParams = this.obj2Params(params);
|
const urlParams = this.obj2Params(params);
|
||||||
@ -50,16 +82,19 @@ export class SubsonicApiClient {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async apiDownload(method: string, path: string, params?: {[key: string]: any}): Promise<string> {
|
private async apiDownload(method: string, path: string, params?: { [key: string]: any }): Promise<string> {
|
||||||
await RNFS.downloadFile({
|
const download = RNFS.downloadFile({
|
||||||
fromUrl: this.buildUrl(method, params),
|
fromUrl: this.buildUrl(method, params),
|
||||||
toFile: path,
|
toFile: path,
|
||||||
}).promise;
|
}).promise;
|
||||||
|
|
||||||
|
await downloadQueue.enqueue(() => download);
|
||||||
|
await downloadQueue.enqueue(() => new Promise(resolve => setTimeout(resolve, 100)));
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async apiGetXml(method: string, params?: {[key: string]: any}): Promise<Document> {
|
private async apiGetXml(method: string, params?: { [key: string]: any }): Promise<Document> {
|
||||||
const response = await fetch(this.buildUrl(method, params));
|
const response = await fetch(this.buildUrl(method, params));
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
|
|
||||||
@ -73,7 +108,7 @@ export class SubsonicApiClient {
|
|||||||
return xml;
|
return xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
private obj2Params(obj: {[key: string]: any}): URLSearchParams | undefined {
|
private obj2Params(obj: { [key: string]: any }): URLSearchParams | undefined {
|
||||||
const keys = Object.keys(obj);
|
const keys = Object.keys(obj);
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user