mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 09:09:29 +01:00
started album view
switched to native material top tabs (w/style)
This commit is contained in:
parent
84cff58741
commit
ef69e9d517
4
App.tsx
4
App.tsx
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { DarkTheme, NavigationContainer } from '@react-navigation/native';
|
||||||
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 { Provider } from 'jotai';
|
import { Provider } from 'jotai';
|
||||||
@ -7,7 +7,7 @@ import { Provider } from 'jotai';
|
|||||||
const App = () => (
|
const App = () => (
|
||||||
<Provider>
|
<Provider>
|
||||||
<SplashPage>
|
<SplashPage>
|
||||||
<NavigationContainer>
|
<NavigationContainer theme={DarkTheme}>
|
||||||
<RootNavigator />
|
<RootNavigator />
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
</SplashPage>
|
</SplashPage>
|
||||||
|
|||||||
BIN
res/arrow_left-fill.png
Normal file
BIN
res/arrow_left-fill.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
40
src/components/common/AlbumView.tsx
Normal file
40
src/components/common/AlbumView.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { useAtomValue } from 'jotai/utils';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
import { albumAtomFamily } from '../../state/music';
|
||||||
|
import TopTabContainer from './TopTabContainer';
|
||||||
|
|
||||||
|
const AlbumDetails: React.FC<{
|
||||||
|
id: string,
|
||||||
|
}> = ({ id }) => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const album = useAtomValue(albumAtomFamily(id));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!album) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigation.setOptions({ title: album.name });
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text>Name: {album?.name}</Text>
|
||||||
|
<Text>Artist: {album?.artist}</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AlbumView: React.FC<{
|
||||||
|
id: string,
|
||||||
|
}> = ({ id }) => (
|
||||||
|
<TopTabContainer>
|
||||||
|
<Text>{id}</Text>
|
||||||
|
<React.Suspense fallback={<Text>Loading...</Text>}>
|
||||||
|
<AlbumDetails id={id} />
|
||||||
|
</React.Suspense>
|
||||||
|
</TopTabContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default React.memo(AlbumView);
|
||||||
@ -1,48 +0,0 @@
|
|||||||
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 TopTabBar: React.FC<MaterialTopTabBarProps> = ({ state, descriptors }) => {
|
|
||||||
return (
|
|
||||||
<View style={{
|
|
||||||
backgroundColor: colors.gradient.high,
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
}}>
|
|
||||||
{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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TopTabBar;
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { useAtomValue } from 'jotai/utils';
|
import { useAtomValue } from 'jotai/utils';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { FlatList, Text, View } from 'react-native';
|
import { FlatList, Pressable, Text, View } from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import { Album } from '../../models/music';
|
import { Album } from '../../models/music';
|
||||||
@ -42,18 +43,24 @@ const AlbumArt: React.FC<{
|
|||||||
const MemoAlbumArt = React.memo(AlbumArt);
|
const MemoAlbumArt = React.memo(AlbumArt);
|
||||||
|
|
||||||
const AlbumItem: React.FC<{
|
const AlbumItem: React.FC<{
|
||||||
|
id: string;
|
||||||
name: string,
|
name: string,
|
||||||
artist?: string,
|
artist?: string,
|
||||||
coverArtUri?: string
|
coverArtUri?: string
|
||||||
} > = ({ name, artist, coverArtUri }) => {
|
} > = ({ id, name, artist, coverArtUri }) => {
|
||||||
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const size = 125;
|
const size = 125;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<Pressable
|
||||||
|
style={{
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginVertical: 8,
|
marginVertical: 8,
|
||||||
flex: 1/3,
|
flex: 1/3,
|
||||||
}}>
|
}}
|
||||||
|
onPress={() => navigation.navigate('AlbumView', { id })}
|
||||||
|
>
|
||||||
<MemoAlbumArt
|
<MemoAlbumArt
|
||||||
width={size}
|
width={size}
|
||||||
height={size}
|
height={size}
|
||||||
@ -79,13 +86,13 @@ const AlbumItem: React.FC<{
|
|||||||
{artist}
|
{artist}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</Pressable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const MemoAlbumItem = React.memo(AlbumItem);
|
const MemoAlbumItem = React.memo(AlbumItem);
|
||||||
|
|
||||||
const AlbumListRenderItem: React.FC<{ item: Album }> = ({ item }) => (
|
const AlbumListRenderItem: React.FC<{ item: Album }> = ({ item }) => (
|
||||||
<MemoAlbumItem name={item.name} artist={item.artist} coverArtUri={item.coverArtThumbUri} />
|
<MemoAlbumItem id={item.id} name={item.name} artist={item.artist} coverArtUri={item.coverArtThumbUri} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const AlbumsList = () => {
|
const AlbumsList = () => {
|
||||||
|
|||||||
@ -4,15 +4,37 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-ta
|
|||||||
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 { createStackNavigator, StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import AlbumView from '../common/AlbumView';
|
||||||
|
import { RouteProp } from '@react-navigation/native';
|
||||||
|
import text from '../../styles/text';
|
||||||
|
import colors from '../../styles/colors';
|
||||||
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
const Tab = createMaterialTopTabNavigator();
|
const Tab = createMaterialTopTabNavigator();
|
||||||
|
|
||||||
const LibraryTopTabNavigator = () => (
|
const LibraryTopTabNavigator = () => (
|
||||||
<View style={{
|
<Tab.Navigator tabBarOptions={{
|
||||||
flex: 1,
|
// scrollEnabled: true,
|
||||||
|
tabStyle: {
|
||||||
|
// width: 100,
|
||||||
|
// paddingHorizontal: 0,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
height: 48,
|
||||||
|
backgroundColor: colors.gradient.high,
|
||||||
|
elevation: 0,
|
||||||
|
},
|
||||||
|
labelStyle: {
|
||||||
|
...text.header,
|
||||||
|
textTransform: null as any,
|
||||||
|
marginTop: 0,
|
||||||
|
marginHorizontal: 2,
|
||||||
|
},
|
||||||
|
indicatorStyle: {
|
||||||
|
backgroundColor: colors.accent,
|
||||||
|
},
|
||||||
}}>
|
}}>
|
||||||
<Tab.Navigator tabBar={TopTabBar}>
|
|
||||||
<Tab.Screen
|
<Tab.Screen
|
||||||
name='Albums'
|
name='Albums'
|
||||||
component={AlbumsTab}
|
component={AlbumsTab}
|
||||||
@ -26,7 +48,60 @@ const LibraryTopTabNavigator = () => (
|
|||||||
component={PlaylistsTab}
|
component={PlaylistsTab}
|
||||||
/>
|
/>
|
||||||
</Tab.Navigator>
|
</Tab.Navigator>
|
||||||
|
);
|
||||||
|
|
||||||
|
type LibraryStackParamList = {
|
||||||
|
LibraryTopTabs: undefined,
|
||||||
|
AlbumView: { id: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlbumScreenNavigationProp = StackNavigationProp<LibraryStackParamList, 'AlbumView'>;
|
||||||
|
type AlbumScreenRouteProp = RouteProp<LibraryStackParamList, 'AlbumView'>;
|
||||||
|
type AlbumScreenProps = {
|
||||||
|
route: AlbumScreenRouteProp,
|
||||||
|
navigation: AlbumScreenNavigationProp,
|
||||||
|
};
|
||||||
|
|
||||||
|
const AlbumScreen: React.FC<AlbumScreenProps> = ({ route }) => (
|
||||||
|
<AlbumView id={route.params.id} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const Stack = createStackNavigator<LibraryStackParamList>();
|
||||||
|
|
||||||
|
const LibraryStackNavigator = () => (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Stack.Navigator>
|
||||||
|
<Stack.Screen
|
||||||
|
name='LibraryTopTabs'
|
||||||
|
component={LibraryTopTabNavigator}
|
||||||
|
options={{ headerShown: false }}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name='AlbumView'
|
||||||
|
component={AlbumScreen}
|
||||||
|
options={{ title: 'Album',
|
||||||
|
headerStyle: {
|
||||||
|
height: 50,
|
||||||
|
backgroundColor: colors.gradient.high,
|
||||||
|
},
|
||||||
|
headerTitleContainerStyle: {
|
||||||
|
marginLeft: -14,
|
||||||
|
},
|
||||||
|
headerLeftContainerStyle: {
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
headerTitleStyle: {
|
||||||
|
...text.header,
|
||||||
|
},
|
||||||
|
headerBackImage: () => <FastImage
|
||||||
|
source={require('../../../res/arrow_left-fill.png')}
|
||||||
|
tintColor={colors.text.primary}
|
||||||
|
style={{ height: 22, width: 22 }}
|
||||||
|
/>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack.Navigator>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default LibraryTopTabNavigator;
|
export default LibraryStackNavigator;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { atom, useAtom } from 'jotai';
|
import { atom, useAtom } from 'jotai';
|
||||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils';
|
import { atomFamily, useAtomValue, useUpdateAtom } from 'jotai/utils';
|
||||||
import { Album, Artist } from '../models/music';
|
import { Album, Artist } from '../models/music';
|
||||||
import { SubsonicApiClient } from '../subsonic/api';
|
import { SubsonicApiClient } from '../subsonic/api';
|
||||||
import { activeServerAtom } from './settings';
|
import { activeServerAtom } from './settings';
|
||||||
@ -70,3 +70,19 @@ export const useUpdateAlbums = () => {
|
|||||||
setUpdating(false);
|
setUpdating(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const albumAtomFamily = atomFamily((id: string) => atom<Album | undefined>(async (get) => {
|
||||||
|
const server = get(activeServerAtom);
|
||||||
|
if (!server) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new SubsonicApiClient(server);
|
||||||
|
const response = await client.getAlbum({ id });
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name: response.data.album.name,
|
||||||
|
artist: response.data.album.artist,
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const paragraph: TextStyle = {
|
|||||||
|
|
||||||
const header: TextStyle = {
|
const header: TextStyle = {
|
||||||
...paragraph,
|
...paragraph,
|
||||||
fontSize: 20,
|
fontSize: 19,
|
||||||
fontFamily: fontSemiBold,
|
fontFamily: fontSemiBold,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user