mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +01:00
switched to navigation-react-native
seems much simpler, but now i may need some other deps...
This commit is contained in:
parent
c7d65e0a58
commit
17fe1b9850
9
App.tsx
9
App.tsx
@ -1,18 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
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 BottomTabNavigator from './src/components/navigation/BottomTabNavigator';
|
||||||
import { enableScreens } from 'react-native-screens';
|
|
||||||
enableScreens();
|
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<SplashPage>
|
<SplashPage>
|
||||||
<NavigationContainer>
|
<BottomTabNavigator />
|
||||||
<RootNavigator />
|
|
||||||
</NavigationContainer>
|
|
||||||
</SplashPage>
|
</SplashPage>
|
||||||
</RecoilRoot>
|
</RecoilRoot>
|
||||||
);
|
);
|
||||||
|
|||||||
9
android/app/proguard-rules.pro
vendored
9
android/app/proguard-rules.pro
vendored
@ -9,4 +9,11 @@
|
|||||||
|
|
||||||
# Add any project specific keep options here:
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
-keep class com.facebook.react.turbomodule.** { *; }
|
-keep public class com.dylanvann.fastimage.* {*;}
|
||||||
|
-keep public class com.dylanvann.fastimage.** {*;}
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
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 {
|
||||||
|
|
||||||
@ -13,9 +12,4 @@ public class MainActivity extends ReactActivity {
|
|||||||
protected String getMainComponentName() {
|
protected String getMainComponentName() {
|
||||||
return "SubSonify";
|
return "SubSonify";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,10 +12,6 @@ import com.facebook.soloader.SoLoader;
|
|||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// for reanimated
|
|
||||||
import com.facebook.react.bridge.JSIModulePackage;
|
|
||||||
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
|
|
||||||
|
|
||||||
public class MainApplication extends Application implements ReactApplication {
|
public class MainApplication extends Application implements ReactApplication {
|
||||||
|
|
||||||
private final ReactNativeHost mReactNativeHost =
|
private final ReactNativeHost mReactNativeHost =
|
||||||
@ -38,12 +34,6 @@ public class MainApplication extends Application implements ReactApplication {
|
|||||||
protected String getJSMainModuleName() {
|
protected String getJSMainModuleName() {
|
||||||
return "index";
|
return "index";
|
||||||
}
|
}
|
||||||
|
|
||||||
// for reanimated
|
|
||||||
@Override
|
|
||||||
protected JSIModulePackage getJSIModulePackage() {
|
|
||||||
return new ReanimatedJSIModulePackage();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
7
android/app/src/main/res/anim/fade_in.xml
Normal file
7
android/app/src/main/res/anim/fade_in.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="0"
|
||||||
|
android:toAlpha="1" />
|
||||||
|
</set>
|
||||||
7
android/app/src/main/res/anim/fade_out.xml
Normal file
7
android/app/src/main/res/anim/fade_out.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<alpha
|
||||||
|
android:duration="300"
|
||||||
|
android:fromAlpha="1"
|
||||||
|
android:toAlpha="0" />
|
||||||
|
</set>
|
||||||
7
android/app/src/main/res/anim/slide_in.xml
Normal file
7
android/app/src/main/res/anim/slide_in.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="100%"
|
||||||
|
android:toXDelta="0" />
|
||||||
|
</set>
|
||||||
7
android/app/src/main/res/anim/slide_out.xml
Normal file
7
android/app/src/main/res/anim/slide_out.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<translate
|
||||||
|
android:duration="300"
|
||||||
|
android:fromXDelta="0"
|
||||||
|
android:toXDelta="100%" />
|
||||||
|
</set>
|
||||||
@ -1,7 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: ['module:metro-react-native-babel-preset'],
|
presets: ['module:metro-react-native-babel-preset'],
|
||||||
plugins: [
|
|
||||||
// reanimated has to be listed last in plugins
|
|
||||||
'react-native-reanimated/plugin',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|||||||
1
index.js
1
index.js
@ -1,4 +1,3 @@
|
|||||||
import 'react-native-gesture-handler';
|
|
||||||
import 'react-native-get-random-values';
|
import 'react-native-get-random-values';
|
||||||
|
|
||||||
import { AppRegistry, LogBox } from 'react-native';
|
import { AppRegistry, LogBox } from 'react-native';
|
||||||
|
|||||||
3118
package-lock.json
generated
3118
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -10,23 +10,17 @@
|
|||||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-native-community/masked-view": "^0.1.11",
|
|
||||||
"@react-navigation/bottom-tabs": "^5.11.11",
|
|
||||||
"@react-navigation/material-top-tabs": "^5.3.15",
|
|
||||||
"@react-navigation/native": "^5.9.4",
|
|
||||||
"@react-navigation/stack": "^5.14.5",
|
|
||||||
"md5": "^2.3.0",
|
"md5": "^2.3.0",
|
||||||
|
"navigation": "^5.5.0",
|
||||||
|
"navigation-react": "^4.3.0",
|
||||||
|
"navigation-react-native": "^7.4.2",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-native": "0.64.1",
|
"react-native": "0.64.1",
|
||||||
|
"react-native-fast-image": "^8.3.4",
|
||||||
"react-native-fs": "^2.18.0",
|
"react-native-fs": "^2.18.0",
|
||||||
"react-native-gesture-handler": "^1.10.3",
|
|
||||||
"react-native-get-random-values": "^1.7.0",
|
"react-native-get-random-values": "^1.7.0",
|
||||||
"react-native-linear-gradient": "^2.5.6",
|
"react-native-linear-gradient": "^2.5.6",
|
||||||
"react-native-reanimated": "^2.0.0",
|
|
||||||
"react-native-safe-area-context": "^3.2.0",
|
|
||||||
"react-native-screens": "^3.4.0",
|
|
||||||
"react-native-sqlite-storage": "^5.0.0",
|
"react-native-sqlite-storage": "^5.0.0",
|
||||||
"react-native-tab-view": "^2.16.0",
|
|
||||||
"react-native-track-player": "^1.2.7",
|
"react-native-track-player": "^1.2.7",
|
||||||
"recoil": "^0.3.1",
|
"recoil": "^0.3.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Button, FlatList, Text, View } from 'react-native';
|
import { Button, FlatList, Text, View } from 'react-native';
|
||||||
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
import { useRecoilValue, useResetRecoilState } from 'recoil';
|
||||||
import { artistsState, useUpdateArtists } from '../state/artists';
|
import { artistsState, useUpdateArtists } from '../state/artists';
|
||||||
@ -21,6 +21,12 @@ const List = () => {
|
|||||||
<ArtistItem item={item} />
|
<ArtistItem item={item} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('rendering artists');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('mounting artists');
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={artists}
|
data={artists}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import { Button, TextInput, View, Text } from 'react-native';
|
import { Button, TextInput, View, Text } from 'react-native';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
@ -6,8 +6,7 @@ import md5 from 'md5';
|
|||||||
import { musicDb, settingsDb } from '../clients';
|
import { musicDb, settingsDb } from '../clients';
|
||||||
import { appSettingsState, serversState } from '../state/settings';
|
import { appSettingsState, serversState } from '../state/settings';
|
||||||
import { DbStorage } from '../storage/db';
|
import { DbStorage } from '../storage/db';
|
||||||
import { StackScreenProps } from '@react-navigation/stack';
|
import { NavigationContext } from 'navigation-react';
|
||||||
import { useNavigation } from '@react-navigation/core';
|
|
||||||
|
|
||||||
const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, title }) => {
|
const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, title }) => {
|
||||||
const [inProgress, setInProgress] = useState(false);
|
const [inProgress, setInProgress] = useState(false);
|
||||||
@ -32,14 +31,14 @@ const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, titl
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DbControls = () => {
|
const DbControls = () => {
|
||||||
const navigation = useNavigation();
|
const { stateNavigator } = useContext(NavigationContext);
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<RecreateDbButton db={musicDb} title='Music' />
|
<RecreateDbButton db={musicDb} title='Music' />
|
||||||
<RecreateDbButton db={settingsDb} title='Settings' />
|
<RecreateDbButton db={settingsDb} title='Settings' />
|
||||||
<Button
|
<Button
|
||||||
title='Now Playing'
|
title='Now Playing'
|
||||||
onPress={() => navigation.navigate('Now Playing')}
|
onPress={() => stateNavigator.navigate('nowplaying')}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, View, Image, Pressable } from 'react-native';
|
import { Text, View, Image, Pressable } from 'react-native';
|
||||||
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
|
|
||||||
import textStyles from '../../styles/text';
|
import textStyles from '../../styles/text';
|
||||||
import colors from '../../styles/colors';
|
import colors from '../../styles/colors';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { NavigationContext } from 'navigation-react';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
const icons: {[key: string]: any} = {
|
const icons: {[key: string]: any} = {
|
||||||
home: {
|
home: {
|
||||||
@ -23,7 +25,43 @@ const icons: {[key: string]: any} = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigation }) => {
|
const BottomTabButton: React.FC<{
|
||||||
|
route: string,
|
||||||
|
title: string,
|
||||||
|
onPress: () => void,
|
||||||
|
isFocused: boolean,
|
||||||
|
}> = ({ route, title, onPress, isFocused }) => {
|
||||||
|
const img = icons[route];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={onPress}
|
||||||
|
style={{
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FastImage
|
||||||
|
source={isFocused ? img.fill : img.regular}
|
||||||
|
style={{
|
||||||
|
height: 26,
|
||||||
|
width: 26,
|
||||||
|
}}
|
||||||
|
tintColor={isFocused ? colors.text.primary : colors.text.secondary}
|
||||||
|
/>
|
||||||
|
<Text style={{
|
||||||
|
...textStyles.xsmall,
|
||||||
|
color: isFocused ? colors.text.primary : colors.text.secondary,
|
||||||
|
}}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const BottomTabBar = () => {
|
||||||
|
const { stateNavigator } = useContext(NavigationContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View style={{
|
||||||
height: 54,
|
height: 54,
|
||||||
@ -33,56 +71,19 @@ const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigat
|
|||||||
justifyContent: 'space-around',
|
justifyContent: 'space-around',
|
||||||
paddingHorizontal: 28,
|
paddingHorizontal: 28,
|
||||||
}}>
|
}}>
|
||||||
{state.routes.map((route, index) => {
|
{Object.values(stateNavigator.states).map(state => (
|
||||||
const { options } = descriptors[route.key] as any;
|
<BottomTabButton
|
||||||
const label =
|
key={state.key}
|
||||||
options.tabBarLabel !== undefined
|
route={state.key}
|
||||||
? options.tabBarLabel as string
|
title={state.title}
|
||||||
: options.title !== undefined
|
onPress={() => {
|
||||||
? options.title
|
if (stateNavigator.stateContext.state.key !== state.key) {
|
||||||
: route.name;
|
stateNavigator.navigate(state.key);
|
||||||
|
}
|
||||||
const isFocused = state.index === index;
|
}}
|
||||||
const img = icons[options.icon];
|
isFocused={stateNavigator.stateContext.state.key === state.key}
|
||||||
|
/>
|
||||||
const onPress = () => {
|
))}
|
||||||
const event = navigation.emit({
|
|
||||||
type: 'tabPress',
|
|
||||||
target: route.key,
|
|
||||||
canPreventDefault: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isFocused && !event.defaultPrevented) {
|
|
||||||
navigation.navigate(route.name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
key={route.key}
|
|
||||||
onPress={onPress}
|
|
||||||
style={{
|
|
||||||
alignItems: 'center',
|
|
||||||
flex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
source={isFocused ? img.fill : img.regular}
|
|
||||||
style={{
|
|
||||||
height: 26,
|
|
||||||
width: 26,
|
|
||||||
tintColor: isFocused ? colors.text.primary : colors.text.secondary,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Text style={{
|
|
||||||
...textStyles.xsmall,
|
|
||||||
color: isFocused ? colors.text.primary : colors.text.secondary,
|
|
||||||
}}>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
</Pressable>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,46 +1,56 @@
|
|||||||
import React from 'react';
|
import { NavigationContext } from 'navigation-react';
|
||||||
import { Text, View } from 'react-native';
|
import React, { useContext } from 'react';
|
||||||
import { MaterialTopTabBarProps } from '@react-navigation/material-top-tabs';
|
import { Pressable, Text, View } from 'react-native';
|
||||||
import colors from '../../styles/colors';
|
import colors from '../../styles/colors';
|
||||||
import textStyles from '../../styles/text';
|
import textStyles from '../../styles/text';
|
||||||
|
|
||||||
const TopTabBar: React.FC<MaterialTopTabBarProps> = ({ state, descriptors }) => {
|
const TopTabButton: React.FC<{
|
||||||
|
title: string,
|
||||||
|
onPress: () => void,
|
||||||
|
isFocused: boolean,
|
||||||
|
}> = ({ title, onPress, isFocused }) => {
|
||||||
|
const color = isFocused ? colors.text.primary : colors.text.secondary;
|
||||||
|
const borderBottomColor = isFocused ? colors.accent : colors.gradient.high;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={onPress}
|
||||||
|
style={{
|
||||||
|
borderBottomColor,
|
||||||
|
borderBottomWidth: 1.5,
|
||||||
|
width: 94,
|
||||||
|
height: 44,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}>
|
||||||
|
<Text style={{
|
||||||
|
...textStyles.header, color,
|
||||||
|
}}>{title}</Text>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopTabBar = () => {
|
||||||
|
const { stateNavigator } = useContext(NavigationContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View style={{
|
||||||
backgroundColor: colors.gradient.high,
|
backgroundColor: colors.gradient.high,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'flex-start',
|
justifyContent: 'space-evenly',
|
||||||
justifyContent: 'flex-start',
|
|
||||||
}}>
|
}}>
|
||||||
{state.routes.map((route, index) => {
|
{Object.values(stateNavigator.states).map(state => (
|
||||||
const { options } = descriptors[route.key];
|
<TopTabButton
|
||||||
const label =
|
key={state.key}
|
||||||
options.tabBarLabel !== undefined
|
title={state.title}
|
||||||
? options.tabBarLabel
|
onPress={() => {
|
||||||
: options.title !== undefined
|
if (stateNavigator.stateContext.state.key !== state.key) {
|
||||||
? options.title
|
stateNavigator.navigate(state.key);
|
||||||
: route.name;
|
}
|
||||||
|
}}
|
||||||
const isFocused = state.index === index;
|
isFocused={stateNavigator.stateContext.state.key === state.key}
|
||||||
const color = isFocused ? colors.text.primary : colors.text.secondary;
|
/>
|
||||||
const borderBottomColor = isFocused ? colors.accent : colors.gradient.high;
|
))}
|
||||||
|
|
||||||
return (
|
|
||||||
<View key={route.key} style={{
|
|
||||||
borderBottomColor,
|
|
||||||
borderBottomWidth: 1.5,
|
|
||||||
// paddingVertical: 8,
|
|
||||||
width: 94,
|
|
||||||
height: 44,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}>
|
|
||||||
<Text style={{
|
|
||||||
...textStyles.header, color,
|
|
||||||
}}>{label}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,16 @@
|
|||||||
import React, { memo, useEffect, useState } from 'react';
|
import React, { memo, useEffect, useState } from 'react';
|
||||||
import { View, Image, Text, FlatList, Button, ListRenderItem } from 'react-native';
|
import { View, Image, Text, FlatList, Button, ListRenderItem, ScrollView } from 'react-native';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { Album } from '../../models/music';
|
import { Album } from '../../models/music';
|
||||||
import { albumsState, albumState, useUpdateAlbums, albumIdsState, useCoverArtUri } from '../../state/albums';
|
import { albumsState, albumState, useUpdateAlbums, albumIdsState, useCoverArtUri } from '../../state/albums';
|
||||||
import TopTabContainer from '../common/TopTabContainer';
|
import TopTabContainer from '../common/TopTabContainer';
|
||||||
import textStyles from '../../styles/text';
|
import textStyles from '../../styles/text';
|
||||||
import { ScrollView } from 'react-native-gesture-handler';
|
|
||||||
import colors from '../../styles/colors';
|
import colors from '../../styles/colors';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import RNFS from 'react-native-fs';
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// console.log(id);
|
// console.log(id);
|
||||||
// });
|
// });
|
||||||
@ -19,16 +18,12 @@ const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ he
|
|||||||
const Placeholder = (
|
const Placeholder = (
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
colors={[colors.accent, colors.accentLow]}
|
colors={[colors.accent, colors.accentLow]}
|
||||||
style={{
|
style={{ height, width }}
|
||||||
height, width,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Image
|
<FastImage
|
||||||
source={require('../../../res/record-m.png')}
|
source={require('../../../res/record-m.png')}
|
||||||
style={{
|
style={{ height, width }}
|
||||||
height, width,
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
resizeMode: 'contain',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
);
|
);
|
||||||
@ -37,24 +32,23 @@ const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ he
|
|||||||
<View style={{
|
<View style={{
|
||||||
height, width,
|
height, width,
|
||||||
}}>
|
}}>
|
||||||
<Image
|
<FastImage
|
||||||
source={{ uri: coverArtSource }}
|
source={{ uri: `file://${RNFS.DocumentDirectoryPath}/image_cache/${id}` }}
|
||||||
style={{
|
style={{ height, width }}
|
||||||
height, width,
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
resizeMode: 'contain',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
return coverArtSource ? CoverArt : Placeholder;
|
return id ? CoverArt : Placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
const AlbumItem: React.FC<{
|
||||||
const album = useRecoilValue(albumState(id));
|
name: string,
|
||||||
|
coverArt?: string,
|
||||||
|
} > = ({ name, coverArt }) => {
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// console.log(album.name);
|
// console.log(name);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const size = 125;
|
const size = 125;
|
||||||
@ -66,12 +60,13 @@ const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
|||||||
marginVertical: 8,
|
marginVertical: 8,
|
||||||
// marginLeft: 6,
|
// marginLeft: 6,
|
||||||
// width: size,
|
// width: size,
|
||||||
|
height: 180,
|
||||||
flex: 1/3,
|
flex: 1/3,
|
||||||
}}>
|
}}>
|
||||||
<AlbumArt
|
<AlbumArt
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
id={album.coverArt}
|
id={coverArt}
|
||||||
/>
|
/>
|
||||||
<View style={{
|
<View style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@ -85,7 +80,7 @@ const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
|||||||
}}
|
}}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
>
|
>
|
||||||
{album.name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
@ -94,36 +89,34 @@ const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
|
|||||||
}}
|
}}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
{album.name}
|
{name}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const MemoAlbumItem = memo(AlbumItem, (prev, next) => {
|
function renderItem(props: { item: Album }) {
|
||||||
// console.log('prev: ' + JSON.stringify(prev) + ' next: ' + JSON.stringify(next))
|
return <AlbumItem name={props.item.name} coverArt={props.item.coverArt} />;
|
||||||
return prev.id == next.id;
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const AlbumsList = () => {
|
const AlbumsList = () => {
|
||||||
const albumIds = useRecoilValue(albumIdsState);
|
const albums = useRecoilValue(albumsState);
|
||||||
const updateAlbums = useUpdateAlbums();
|
const updateAlbums = useUpdateAlbums();
|
||||||
|
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
const renderItem: React.FC<{ item: string }> = ({ item }) => (
|
|
||||||
<MemoAlbumItem id={item} />
|
|
||||||
);
|
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
await updateAlbums();
|
await updateAlbums();
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('rendering albums');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!refreshing && albumIds.length === 0) {
|
console.log('mounting albums');
|
||||||
|
if (!refreshing && Object.keys(albums).length === 0) {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -131,22 +124,37 @@ const AlbumsList = () => {
|
|||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={albumIds}
|
data={Object.values(albums)}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={item => item}
|
keyExtractor={item => item.id}
|
||||||
onRefresh={refresh}
|
onRefresh={refresh}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
numColumns={3}
|
numColumns={3}
|
||||||
removeClippedSubviews={false}
|
removeClippedSubviews={true}
|
||||||
|
// initialNumToRender={3}
|
||||||
|
// maxToRenderPerBatch={2}
|
||||||
|
// updateCellsBatchingPeriod={1000}
|
||||||
|
getItemLayout={(data, index) => ({
|
||||||
|
length: 180,
|
||||||
|
offset: 180 * Math.floor(index / 3),
|
||||||
|
index
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
|
{/* <ScrollView>
|
||||||
|
{Object.values(albums).map(album => (
|
||||||
|
<AlbumItem name={album.name} coverArt={album.coverArt} key={album.id} />
|
||||||
|
))}
|
||||||
|
</ScrollView> */}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MemoAlbumsList = React.memo(AlbumsList);
|
||||||
|
|
||||||
const AlbumsTab = () => (
|
const AlbumsTab = () => (
|
||||||
<TopTabContainer>
|
<TopTabContainer>
|
||||||
<React.Suspense fallback={<Text>Loading...</Text>}>
|
<React.Suspense fallback={<Text>Loading...</Text>}>
|
||||||
<AlbumsList />
|
<MemoAlbumsList />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
</TopTabContainer>
|
</TopTabContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,40 +1,36 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
|
||||||
import SettingsView from '../Settings';
|
import SettingsView from '../Settings';
|
||||||
import NowPlayingLayout from '../NowPlayingLayout';
|
import NowPlayingLayout from '../NowPlayingLayout';
|
||||||
import ArtistsList from '../ArtistsList';
|
import ArtistsList from '../ArtistsList';
|
||||||
import LibraryTopTabNavigator from './LibraryTopTabNavigator';
|
import LibraryTopTabNavigator from './LibraryTopTabNavigator';
|
||||||
import BottomTabBar from '../common/BottomTabBar';
|
import BottomTabBar from '../common/BottomTabBar';
|
||||||
|
import { NavigationHandler } from 'navigation-react';
|
||||||
|
import { NavigationStack } from 'navigation-react-native';
|
||||||
|
import { StateNavigator } from 'navigation';
|
||||||
|
|
||||||
const Tab = createBottomTabNavigator();
|
const stateNavigator = new StateNavigator([
|
||||||
|
{ key: 'home', title: 'Home' },
|
||||||
|
{ key: 'library', title: 'Library', trackCrumbTrail: true },
|
||||||
|
{ key: 'search', title: 'Search', trackCrumbTrail: true },
|
||||||
|
{ key: 'settings', title: 'Settings', trackCrumbTrail: true },
|
||||||
|
]);
|
||||||
|
|
||||||
const BottomTabNavigator = () => {
|
const { home, library, search, settings } = stateNavigator.states;
|
||||||
return (
|
home.renderScene = ArtistsList;
|
||||||
<Tab.Navigator
|
library.renderScene = LibraryTopTabNavigator;
|
||||||
tabBar={BottomTabBar}
|
search.renderScene = NowPlayingLayout;
|
||||||
>
|
settings.renderScene = SettingsView;
|
||||||
<Tab.Screen
|
|
||||||
name='Home'
|
const BottomTabNavigator = () => (
|
||||||
component={ArtistsList}
|
<NavigationHandler stateNavigator={stateNavigator}>
|
||||||
options={{ icon: 'home' } as any}
|
<NavigationStack
|
||||||
/>
|
unmountStyle={from => from ? 'fade_in' : 'fade_out'}
|
||||||
<Tab.Screen
|
crumbStyle={from => from ? 'fade_in' : 'fade_out'}
|
||||||
name='Library'
|
/>
|
||||||
component={LibraryTopTabNavigator}
|
<BottomTabBar />
|
||||||
options={{ icon: 'library' } as any}
|
</NavigationHandler>
|
||||||
/>
|
);
|
||||||
<Tab.Screen
|
|
||||||
name='Search'
|
stateNavigator.navigate('home');
|
||||||
component={NowPlayingLayout}
|
|
||||||
options={{ icon: 'search' } as any}
|
|
||||||
/>
|
|
||||||
<Tab.Screen
|
|
||||||
name='Settings'
|
|
||||||
component={SettingsView}
|
|
||||||
options={{ icon: 'settings' } as any}
|
|
||||||
/>
|
|
||||||
</Tab.Navigator>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BottomTabNavigator;
|
export default BottomTabNavigator;
|
||||||
|
|||||||
@ -1,32 +1,34 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
|
||||||
import AlbumsTab from '../library/AlbumsTab';
|
import AlbumsTab from '../library/AlbumsTab';
|
||||||
import ArtistsTab from '../library/ArtistsTab';
|
import ArtistsTab from '../library/ArtistsTab';
|
||||||
import PlaylistsTab from '../library/PlaylistsTab';
|
import PlaylistsTab from '../library/PlaylistsTab';
|
||||||
import TopTabBar from '../common/TopTabBar';
|
import TopTabBar from '../common/TopTabBar';
|
||||||
|
import { StateNavigator } from 'navigation';
|
||||||
|
import { NavigationHandler } from 'navigation-react';
|
||||||
|
import { NavigationStack } from 'navigation-react-native';
|
||||||
|
|
||||||
const Tab = createMaterialTopTabNavigator();
|
const stateNavigator = new StateNavigator([
|
||||||
|
{ key: 'albums', title: 'Albums' },
|
||||||
|
{ key: 'artists', title: 'Artists', trackCrumbTrail: true, },
|
||||||
|
{ key: 'playlists', title: 'Playlists', trackCrumbTrail: true, },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { albums, artists, playlists } = stateNavigator.states;
|
||||||
|
albums.renderScene = AlbumsTab;
|
||||||
|
artists.renderScene = ArtistsTab;
|
||||||
|
playlists.renderScene = PlaylistsTab;
|
||||||
|
|
||||||
const LibraryTopTabNavigator = () => (
|
const LibraryTopTabNavigator = () => (
|
||||||
<View style={{
|
<NavigationHandler stateNavigator={stateNavigator}>
|
||||||
flex: 1,
|
<TopTabBar />
|
||||||
}}>
|
<NavigationStack
|
||||||
<Tab.Navigator tabBar={TopTabBar}>
|
unmountStyle={from => from ? 'slide_in' : 'slide_out'}
|
||||||
<Tab.Screen
|
crumbStyle={from => from ? 'slide_in' : 'slide_out'}
|
||||||
name='Albums'
|
/>
|
||||||
component={AlbumsTab}
|
</NavigationHandler>
|
||||||
/>
|
|
||||||
<Tab.Screen
|
|
||||||
name='Artists'
|
|
||||||
component={ArtistsTab}
|
|
||||||
/>
|
|
||||||
<Tab.Screen
|
|
||||||
name='Playlists'
|
|
||||||
component={PlaylistsTab}
|
|
||||||
/>
|
|
||||||
</Tab.Navigator>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
stateNavigator.navigate('albums');
|
||||||
|
|
||||||
export default LibraryTopTabNavigator;
|
export default LibraryTopTabNavigator;
|
||||||
|
|||||||
@ -1,23 +1,26 @@
|
|||||||
|
import { StateNavigator } from 'navigation';
|
||||||
|
import { NavigationHandler } from 'navigation-react';
|
||||||
|
import { NavigationStack } from 'navigation-react-native';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createStackNavigator } from '@react-navigation/stack';
|
import { View } from 'react-native';
|
||||||
import NowPlayingLayout from '../NowPlayingLayout';
|
import NowPlayingLayout from '../NowPlayingLayout';
|
||||||
import BottomTabNavigator from './BottomTabNavigator';
|
import BottomTabNavigator from './BottomTabNavigator';
|
||||||
|
|
||||||
const RootStack = createStackNavigator();
|
const stateNavigator = new StateNavigator([
|
||||||
|
{ key: 'main' },
|
||||||
|
{ key: 'nowplaying', trackCrumbTrail: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { main, nowplaying } = stateNavigator.states;
|
||||||
|
main.renderScene = () => <BottomTabNavigator />;
|
||||||
|
nowplaying.renderScene = () => <NowPlayingLayout />;
|
||||||
|
|
||||||
const RootNavigator = () => (
|
const RootNavigator = () => (
|
||||||
<RootStack.Navigator>
|
<NavigationHandler stateNavigator={stateNavigator}>
|
||||||
<RootStack.Screen
|
<NavigationStack />
|
||||||
name='Main'
|
</NavigationHandler>
|
||||||
component={BottomTabNavigator}
|
|
||||||
options={{ headerShown: false }}
|
|
||||||
/>
|
|
||||||
<RootStack.Screen
|
|
||||||
name='Now Playing'
|
|
||||||
component={NowPlayingLayout}
|
|
||||||
options={{ headerShown: false }}
|
|
||||||
/>
|
|
||||||
</RootStack.Navigator>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
stateNavigator.navigate('main');
|
||||||
|
|
||||||
export default RootNavigator;
|
export default RootNavigator;
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export function useCoverArtUri(id: string | undefined): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
|
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
|
||||||
await client.getCoverArt({ id });
|
await client.getCoverArt({ id, size: '32' });
|
||||||
|
|
||||||
setCoverArtSource(fileUri);
|
setCoverArtSource(fileUri);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,7 +78,7 @@ export class SubsonicApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const url = `${this.address}/rest/${method}?${query}`;
|
const url = `${this.address}/rest/${method}?${query}`;
|
||||||
console.log(url);
|
// console.log(url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ export class SubsonicApiClient {
|
|||||||
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();
|
||||||
|
|
||||||
console.log(text);
|
// console.log(text);
|
||||||
|
|
||||||
const xml = new DOMParser().parseFromString(text);
|
const xml = new DOMParser().parseFromString(text);
|
||||||
if (xml.documentElement.getAttribute('status') !== 'ok') {
|
if (xml.documentElement.getAttribute('status') !== 'ok') {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user