Revert "switched to navigation-react-native"

This reverts commit a8d0211ab7739b71fc5e32206ff3e85dbf7f051c.
This commit is contained in:
austinried 2021-06-25 09:23:19 +09:00
parent 17fe1b9850
commit 4152ff6cfb
22 changed files with 3268 additions and 356 deletions

View File

@ -1,13 +1,18 @@
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 BottomTabNavigator from './src/components/navigation/BottomTabNavigator';
import { enableScreens } from 'react-native-screens';
enableScreens();
const App = () => (
<RecoilRoot>
<SplashPage>
<BottomTabNavigator />
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
</SplashPage>
</RecoilRoot>
);

View File

@ -9,11 +9,4 @@
# Add any project specific keep options here:
-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 *;
}
-keep class com.facebook.react.turbomodule.** { *; }

View File

@ -1,6 +1,7 @@
package com.subsonify;
import com.facebook.react.ReactActivity;
import android.os.Bundle;
public class MainActivity extends ReactActivity {
@ -12,4 +13,9 @@ public class MainActivity extends ReactActivity {
protected String getMainComponentName() {
return "SubSonify";
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
}

View File

@ -12,6 +12,10 @@ import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
// for reanimated
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
@ -34,6 +38,12 @@ public class MainApplication extends Application implements ReactApplication {
protected String getJSMainModuleName() {
return "index";
}
// for reanimated
@Override
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
}
};
@Override

View File

@ -1,7 +0,0 @@
<?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>

View File

@ -1,7 +0,0 @@
<?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>

View File

@ -1,7 +0,0 @@
<?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>

View File

@ -1,7 +0,0 @@
<?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>

View File

@ -1,3 +1,7 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
// reanimated has to be listed last in plugins
'react-native-reanimated/plugin',
],
};

View File

@ -1,3 +1,4 @@
import 'react-native-gesture-handler';
import 'react-native-get-random-values';
import { AppRegistry, LogBox } from 'react-native';

3118
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,23 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
},
"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",
"navigation": "^5.5.0",
"navigation-react": "^4.3.0",
"navigation-react-native": "^7.4.2",
"react": "17.0.1",
"react-native": "0.64.1",
"react-native-fast-image": "^8.3.4",
"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",
"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-tab-view": "^2.16.0",
"react-native-track-player": "^1.2.7",
"recoil": "^0.3.1",
"uuid": "^8.3.2",

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React from 'react';
import { Button, FlatList, Text, View } from 'react-native';
import { useRecoilValue, useResetRecoilState } from 'recoil';
import { artistsState, useUpdateArtists } from '../state/artists';
@ -21,12 +21,6 @@ const List = () => {
<ArtistItem item={item} />
);
console.log('rendering artists');
useEffect(() => {
console.log('mounting artists');
});
return (
<FlatList
data={artists}

View File

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { Button, TextInput, View, Text } from 'react-native';
import { useRecoilState } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
@ -6,7 +6,8 @@ import md5 from 'md5';
import { musicDb, settingsDb } from '../clients';
import { appSettingsState, serversState } from '../state/settings';
import { DbStorage } from '../storage/db';
import { NavigationContext } from 'navigation-react';
import { StackScreenProps } from '@react-navigation/stack';
import { useNavigation } from '@react-navigation/core';
const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, title }) => {
const [inProgress, setInProgress] = useState(false);
@ -31,14 +32,14 @@ const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, titl
}
const DbControls = () => {
const { stateNavigator } = useContext(NavigationContext);
const navigation = useNavigation();
return (
<View>
<RecreateDbButton db={musicDb} title='Music' />
<RecreateDbButton db={settingsDb} title='Settings' />
<Button
title='Now Playing'
onPress={() => stateNavigator.navigate('nowplaying')}
onPress={() => navigation.navigate('Now Playing')}
/>
</View>
);

View File

@ -1,10 +1,8 @@
import React from 'react';
import { Text, View, Image, Pressable } from 'react-native';
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
import textStyles from '../../styles/text';
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} = {
home: {
@ -25,43 +23,7 @@ const icons: {[key: string]: any} = {
},
}
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);
const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigation }) => {
return (
<View style={{
height: 54,
@ -71,19 +33,56 @@ const BottomTabBar = () => {
justifyContent: 'space-around',
paddingHorizontal: 28,
}}>
{Object.values(stateNavigator.states).map(state => (
<BottomTabButton
key={state.key}
route={state.key}
title={state.title}
onPress={() => {
if (stateNavigator.stateContext.state.key !== state.key) {
stateNavigator.navigate(state.key);
}
}}
isFocused={stateNavigator.stateContext.state.key === state.key}
/>
))}
{state.routes.map((route, index) => {
const { options } = descriptors[route.key] as any;
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel as string
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
const img = icons[options.icon];
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 File

@ -1,56 +1,46 @@
import { NavigationContext } from 'navigation-react';
import React, { useContext } from 'react';
import { Pressable, Text, View } from 'react-native';
import React from 'react';
import { Text, View } from 'react-native';
import { MaterialTopTabBarProps } from '@react-navigation/material-top-tabs';
import colors from '../../styles/colors';
import textStyles from '../../styles/text';
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);
const TopTabBar: React.FC<MaterialTopTabBarProps> = ({ state, descriptors }) => {
return (
<View style={{
backgroundColor: colors.gradient.high,
flexDirection: 'row',
justifyContent: 'space-evenly',
alignItems: 'flex-start',
justifyContent: 'flex-start',
}}>
{Object.values(stateNavigator.states).map(state => (
<TopTabButton
key={state.key}
title={state.title}
onPress={() => {
if (stateNavigator.stateContext.state.key !== state.key) {
stateNavigator.navigate(state.key);
}
}}
isFocused={stateNavigator.stateContext.state.key === state.key}
/>
))}
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
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 File

@ -1,16 +1,17 @@
import React, { memo, useEffect, useState } from 'react';
import { View, Image, Text, FlatList, Button, ListRenderItem, ScrollView } from 'react-native';
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';
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 coverArtSource = useCoverArtUri(id);
// useEffect(() => {
// console.log(id);
// });
@ -18,12 +19,16 @@ const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ he
const Placeholder = (
<LinearGradient
colors={[colors.accent, colors.accentLow]}
style={{ height, width }}
style={{
height, width,
}}
>
<FastImage
<Image
source={require('../../../res/record-m.png')}
style={{ height, width }}
resizeMode={FastImage.resizeMode.contain}
style={{
height, width,
resizeMode: 'contain',
}}
/>
</LinearGradient>
);
@ -32,23 +37,24 @@ const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ he
<View style={{
height, width,
}}>
<FastImage
source={{ uri: `file://${RNFS.DocumentDirectoryPath}/image_cache/${id}` }}
style={{ height, width }}
resizeMode={FastImage.resizeMode.contain}
<Image
source={{ uri: coverArtSource }}
style={{
height, width,
resizeMode: 'contain',
}}
/>
</View>
);
return id ? CoverArt : Placeholder;
return coverArtSource ? CoverArt : Placeholder;
}
const AlbumItem: React.FC<{
name: string,
coverArt?: string,
} > = ({ name, coverArt }) => {
const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
const album = useRecoilValue(albumState(id));
// useEffect(() => {
// console.log(name);
// console.log(album.name);
// });
const size = 125;
@ -60,13 +66,12 @@ const AlbumItem: React.FC<{
marginVertical: 8,
// marginLeft: 6,
// width: size,
height: 180,
flex: 1/3,
}}>
<AlbumArt
width={size}
height={size}
id={coverArt}
id={album.coverArt}
/>
<View style={{
flex: 1,
@ -80,7 +85,7 @@ const AlbumItem: React.FC<{
}}
numberOfLines={2}
>
{name}
{album.name}
</Text>
<Text
style={{
@ -89,34 +94,36 @@ const AlbumItem: React.FC<{
}}
numberOfLines={1}
>
{name}
{album.name}
</Text>
</View>
</View>
);
}
function renderItem(props: { item: Album }) {
return <AlbumItem name={props.item.name} coverArt={props.item.coverArt} />;
}
const MemoAlbumItem = memo(AlbumItem, (prev, next) => {
// console.log('prev: ' + JSON.stringify(prev) + ' next: ' + JSON.stringify(next))
return prev.id == next.id;
});
const AlbumsList = () => {
const albums = useRecoilValue(albumsState);
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);
}
console.log('rendering albums');
useEffect(() => {
console.log('mounting albums');
if (!refreshing && Object.keys(albums).length === 0) {
if (!refreshing && albumIds.length === 0) {
refresh();
}
});
@ -124,37 +131,22 @@ const AlbumsList = () => {
return (
<View style={{ flex: 1 }}>
<FlatList
data={Object.values(albums)}
data={albumIds}
renderItem={renderItem}
keyExtractor={item => item.id}
keyExtractor={item => item}
onRefresh={refresh}
refreshing={refreshing}
numColumns={3}
removeClippedSubviews={true}
// initialNumToRender={3}
// maxToRenderPerBatch={2}
// updateCellsBatchingPeriod={1000}
getItemLayout={(data, index) => ({
length: 180,
offset: 180 * Math.floor(index / 3),
index
})}
removeClippedSubviews={false}
/>
{/* <ScrollView>
{Object.values(albums).map(album => (
<AlbumItem name={album.name} coverArt={album.coverArt} key={album.id} />
))}
</ScrollView> */}
</View>
);
}
const MemoAlbumsList = React.memo(AlbumsList);
const AlbumsTab = () => (
<TopTabContainer>
<React.Suspense fallback={<Text>Loading...</Text>}>
<MemoAlbumsList />
<AlbumsList />
</React.Suspense>
</TopTabContainer>
);

View File

@ -1,36 +1,40 @@
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import SettingsView from '../Settings';
import NowPlayingLayout from '../NowPlayingLayout';
import ArtistsList from '../ArtistsList';
import LibraryTopTabNavigator from './LibraryTopTabNavigator';
import BottomTabBar from '../common/BottomTabBar';
import { NavigationHandler } from 'navigation-react';
import { NavigationStack } from 'navigation-react-native';
import { StateNavigator } from 'navigation';
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 Tab = createBottomTabNavigator();
const { home, library, search, settings } = stateNavigator.states;
home.renderScene = ArtistsList;
library.renderScene = LibraryTopTabNavigator;
search.renderScene = NowPlayingLayout;
settings.renderScene = SettingsView;
const BottomTabNavigator = () => (
<NavigationHandler stateNavigator={stateNavigator}>
<NavigationStack
unmountStyle={from => from ? 'fade_in' : 'fade_out'}
crumbStyle={from => from ? 'fade_in' : 'fade_out'}
/>
<BottomTabBar />
</NavigationHandler>
);
stateNavigator.navigate('home');
const BottomTabNavigator = () => {
return (
<Tab.Navigator
tabBar={BottomTabBar}
>
<Tab.Screen
name='Home'
component={ArtistsList}
options={{ icon: 'home' } as any}
/>
<Tab.Screen
name='Library'
component={LibraryTopTabNavigator}
options={{ icon: 'library' } as any}
/>
<Tab.Screen
name='Search'
component={NowPlayingLayout}
options={{ icon: 'search' } as any}
/>
<Tab.Screen
name='Settings'
component={SettingsView}
options={{ icon: 'settings' } as any}
/>
</Tab.Navigator>
);
}
export default BottomTabNavigator;

View File

@ -1,34 +1,32 @@
import React from 'react';
import { View } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import AlbumsTab from '../library/AlbumsTab';
import ArtistsTab from '../library/ArtistsTab';
import PlaylistsTab from '../library/PlaylistsTab';
import TopTabBar from '../common/TopTabBar';
import { StateNavigator } from 'navigation';
import { NavigationHandler } from 'navigation-react';
import { NavigationStack } from 'navigation-react-native';
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 Tab = createMaterialTopTabNavigator();
const LibraryTopTabNavigator = () => (
<NavigationHandler stateNavigator={stateNavigator}>
<TopTabBar />
<NavigationStack
unmountStyle={from => from ? 'slide_in' : 'slide_out'}
crumbStyle={from => from ? 'slide_in' : 'slide_out'}
/>
</NavigationHandler>
<View style={{
flex: 1,
}}>
<Tab.Navigator tabBar={TopTabBar}>
<Tab.Screen
name='Albums'
component={AlbumsTab}
/>
<Tab.Screen
name='Artists'
component={ArtistsTab}
/>
<Tab.Screen
name='Playlists'
component={PlaylistsTab}
/>
</Tab.Navigator>
</View>
);
stateNavigator.navigate('albums');
export default LibraryTopTabNavigator;

View File

@ -1,26 +1,23 @@
import { StateNavigator } from 'navigation';
import { NavigationHandler } from 'navigation-react';
import { NavigationStack } from 'navigation-react-native';
import React from 'react';
import { View } from 'react-native';
import { createStackNavigator } from '@react-navigation/stack';
import NowPlayingLayout from '../NowPlayingLayout';
import BottomTabNavigator from './BottomTabNavigator';
const stateNavigator = new StateNavigator([
{ key: 'main' },
{ key: 'nowplaying', trackCrumbTrail: true },
]);
const { main, nowplaying } = stateNavigator.states;
main.renderScene = () => <BottomTabNavigator />;
nowplaying.renderScene = () => <NowPlayingLayout />;
const RootStack = createStackNavigator();
const RootNavigator = () => (
<NavigationHandler stateNavigator={stateNavigator}>
<NavigationStack />
</NavigationHandler>
<RootStack.Navigator>
<RootStack.Screen
name='Main'
component={BottomTabNavigator}
options={{ headerShown: false }}
/>
<RootStack.Screen
name='Now Playing'
component={NowPlayingLayout}
options={{ headerShown: false }}
/>
</RootStack.Navigator>
);
stateNavigator.navigate('main');
export default RootNavigator;

View File

@ -101,7 +101,7 @@ export function useCoverArtUri(id: string | undefined): string | undefined {
}
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
await client.getCoverArt({ id, size: '32' });
await client.getCoverArt({ id });
setCoverArtSource(fileUri);
}

View File

@ -78,7 +78,7 @@ export class SubsonicApiClient {
}
const url = `${this.address}/rest/${method}?${query}`;
// console.log(url);
console.log(url);
return url;
}
@ -98,7 +98,7 @@ export class SubsonicApiClient {
const response = await fetch(this.buildUrl(method, params));
const text = await response.text();
// console.log(text);
console.log(text);
const xml = new DOMParser().parseFromString(text);
if (xml.documentElement.getAttribute('status') !== 'ok') {