mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 00:59:28 +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 { NavigationContainer } from '@react-navigation/native';
|
||||
import { DarkTheme, NavigationContainer } from '@react-navigation/native';
|
||||
import SplashPage from './src/components/SplashPage';
|
||||
import RootNavigator from './src/components/navigation/RootNavigator';
|
||||
import { Provider } from 'jotai';
|
||||
@ -7,7 +7,7 @@ import { Provider } from 'jotai';
|
||||
const App = () => (
|
||||
<Provider>
|
||||
<SplashPage>
|
||||
<NavigationContainer>
|
||||
<NavigationContainer theme={DarkTheme}>
|
||||
<RootNavigator />
|
||||
</NavigationContainer>
|
||||
</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 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 LinearGradient from 'react-native-linear-gradient';
|
||||
import { Album } from '../../models/music';
|
||||
@ -42,18 +43,24 @@ const AlbumArt: React.FC<{
|
||||
const MemoAlbumArt = React.memo(AlbumArt);
|
||||
|
||||
const AlbumItem: React.FC<{
|
||||
id: string;
|
||||
name: string,
|
||||
artist?: string,
|
||||
coverArtUri?: string
|
||||
} > = ({ name, artist, coverArtUri }) => {
|
||||
} > = ({ id, name, artist, coverArtUri }) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const size = 125;
|
||||
|
||||
return (
|
||||
<View style={{
|
||||
<Pressable
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
marginVertical: 8,
|
||||
flex: 1/3,
|
||||
}}>
|
||||
}}
|
||||
onPress={() => navigation.navigate('AlbumView', { id })}
|
||||
>
|
||||
<MemoAlbumArt
|
||||
width={size}
|
||||
height={size}
|
||||
@ -79,13 +86,13 @@ const AlbumItem: React.FC<{
|
||||
{artist}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
const MemoAlbumItem = React.memo(AlbumItem);
|
||||
|
||||
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 = () => {
|
||||
|
||||
@ -4,15 +4,37 @@ import { createMaterialTopTabNavigator } from '@react-navigation/material-top-ta
|
||||
import AlbumsTab from '../library/AlbumsTab';
|
||||
import ArtistsTab from '../library/ArtistsTab';
|
||||
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 LibraryTopTabNavigator = () => (
|
||||
<View style={{
|
||||
flex: 1,
|
||||
<Tab.Navigator tabBarOptions={{
|
||||
// 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
|
||||
name='Albums'
|
||||
component={AlbumsTab}
|
||||
@ -26,7 +48,60 @@ const LibraryTopTabNavigator = () => (
|
||||
component={PlaylistsTab}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
|
||||
export default LibraryTopTabNavigator;
|
||||
export default LibraryStackNavigator;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 { SubsonicApiClient } from '../subsonic/api';
|
||||
import { activeServerAtom } from './settings';
|
||||
@ -70,3 +70,19 @@ export const useUpdateAlbums = () => {
|
||||
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 = {
|
||||
...paragraph,
|
||||
fontSize: 20,
|
||||
fontSize: 19,
|
||||
fontFamily: fontSemiBold,
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user