refactored text styles, not enough shared

This commit is contained in:
austinried 2021-07-08 17:56:05 +09:00
parent 3460b4014f
commit fe92c63a60
16 changed files with 358 additions and 346 deletions

View File

@ -9,8 +9,8 @@ import CoverArt from '@app/components/CoverArt'
interface AlbumArtProps { interface AlbumArtProps {
id: string id: string
height: number height: string | number
width: number width: string | number
} }
const AlbumArt: React.FC<AlbumArtProps> = ({ id, height, width }) => { const AlbumArt: React.FC<AlbumArtProps> = ({ id, height, width }) => {

View File

@ -1,31 +1,33 @@
import React, { useState } from 'react'
import { GestureResponderEvent, Pressable, Text } from 'react-native'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import text from '@app/styles/text' import font from '@app/styles/font'
import React from 'react'
import { GestureResponderEvent, StyleSheet, Text } from 'react-native'
import PressableOpacity from './PressableOpacity'
const Button: React.FC<{ const Button: React.FC<{
title: string title: string
onPress: (event: GestureResponderEvent) => void onPress: (event: GestureResponderEvent) => void
}> = ({ title, onPress }) => { }> = ({ title, onPress }) => {
const [opacity, setOpacity] = useState(1)
return ( return (
<Pressable <PressableOpacity onPress={onPress} style={styles.container}>
onPress={onPress} <Text style={styles.text}>{title}</Text>
onPressIn={() => setOpacity(0.6)} </PressableOpacity>
onPressOut={() => setOpacity(1)}
onLongPress={() => setOpacity(1)}
style={{
backgroundColor: colors.accent,
paddingHorizontal: 24,
minHeight: 42,
justifyContent: 'center',
borderRadius: 1000,
opacity,
}}>
<Text style={{ ...text.button }}>{title}</Text>
</Pressable>
) )
} }
export default Button const styles = StyleSheet.create({
container: {
backgroundColor: colors.accent,
paddingHorizontal: 24,
minHeight: 42,
justifyContent: 'center',
borderRadius: 1000,
},
text: {
fontSize: 15,
fontFamily: font.bold,
color: colors.text.primary,
},
})
export default React.memo(Button)

View File

@ -1,27 +1,26 @@
import React, { useState } from 'react'
import { LayoutRectangle, ScrollView, ScrollViewProps } from 'react-native'
import colors from '@app/styles/colors'
import ImageGradientBackground from '@app/components/ImageGradientBackground' import ImageGradientBackground from '@app/components/ImageGradientBackground'
import colors from '@app/styles/colors'
import dimensions from '@app/styles/dimensions'
import React from 'react'
import { ScrollView, ScrollViewProps, useWindowDimensions } from 'react-native'
const ImageGradientScrollView: React.FC<ScrollViewProps & { imageUri?: string; imageKey?: string }> = props => { const ImageGradientScrollView: React.FC<ScrollViewProps & { imageUri?: string; imageKey?: string }> = props => {
const [layout, setLayout] = useState<LayoutRectangle | undefined>(undefined) const layout = useWindowDimensions()
props.style = props.style || {} const minHeight = layout.height - (dimensions.top() + dimensions.bottom())
if (typeof props.style === 'object' && props.style !== null) {
props.style = {
...props.style,
backgroundColor: colors.gradient.low,
}
}
return ( return (
<ScrollView <ScrollView
overScrollMode="never" overScrollMode="never"
{...props} {...props}
onLayout={event => { style={[
setLayout(event.nativeEvent.layout) props.style,
}}> {
<ImageGradientBackground height={layout?.height} imageUri={props.imageUri} imageKey={props.imageKey} /> backgroundColor: colors.gradient.low,
},
]}
contentContainerStyle={[{ minHeight }, props.contentContainerStyle]}>
<ImageGradientBackground height={minHeight} imageUri={props.imageUri} imageKey={props.imageKey} />
{props.children} {props.children}
</ScrollView> </ScrollView>
) )

View File

@ -5,7 +5,7 @@ import { useAtomValue } from 'jotai/utils'
import { currentTrackAtom, playerStateAtom, usePause, usePlay, useProgress } from '@app/state/trackplayer' import { currentTrackAtom, playerStateAtom, usePause, usePlay, useProgress } from '@app/state/trackplayer'
import CoverArt from '@app/components/CoverArt' import CoverArt from '@app/components/CoverArt'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import { Font } from '@app/styles/text' import font from '@app/styles/font'
import { State } from 'react-native-track-player' import { State } from 'react-native-track-player'
import PressableOpacity from '@app/components/PressableOpacity' import PressableOpacity from '@app/components/PressableOpacity'
import IconFA5 from 'react-native-vector-icons/FontAwesome5' import IconFA5 from 'react-native-vector-icons/FontAwesome5'
@ -110,12 +110,12 @@ const styles = StyleSheet.create({
marginLeft: 10, marginLeft: 10,
}, },
detailsTitle: { detailsTitle: {
fontFamily: Font.semiBold, fontFamily: font.semiBold,
fontSize: 13, fontSize: 13,
color: colors.text.primary, color: colors.text.primary,
}, },
detailsAlbum: { detailsAlbum: {
fontFamily: Font.regular, fontFamily: font.regular,
fontSize: 13, fontSize: 13,
color: colors.text.secondary, color: colors.text.secondary,
}, },

View File

@ -3,6 +3,7 @@ import { LayoutRectangle, Pressable, PressableProps } from 'react-native'
type PressableOpacityProps = PressableProps & { type PressableOpacityProps = PressableProps & {
ripple?: boolean ripple?: boolean
rippleColor?: string
} }
const PressableOpacity: React.FC<PressableOpacityProps> = props => { const PressableOpacity: React.FC<PressableOpacityProps> = props => {
@ -20,8 +21,9 @@ const PressableOpacity: React.FC<PressableOpacityProps> = props => {
android_ripple={ android_ripple={
props.ripple props.ripple
? { ? {
color: 'rgba(255,255,255,0.26)', color: props.rippleColor || 'rgba(255,255,255,0.26)',
radius: dimensions ? dimensions.width / 2 : undefined, radius: dimensions ? dimensions.width / 2 : undefined,
borderless: true,
} }
: undefined : undefined
} }

View File

@ -1,12 +1,19 @@
import React, { useState } from 'react' import React from 'react'
import { Text, View, Pressable } from 'react-native' import { Text, View, StyleSheet } from 'react-native'
import { BottomTabBarProps } from '@react-navigation/bottom-tabs' import { BottomTabBarProps } from '@react-navigation/bottom-tabs'
import textStyles from '@app/styles/text'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import FastImage from 'react-native-fast-image' import FastImage from 'react-native-fast-image'
import NowPlayingBar from '@app/components/NowPlayingBar' import NowPlayingBar from '@app/components/NowPlayingBar'
import PressableOpacity from '@app/components/PressableOpacity'
import font from '@app/styles/font'
import dimensions from '@app/styles/dimensions'
const icons: { [key: string]: any } = { type TabButtonImage = {
regular: number
fill: number
}
const icons: { [key: string]: TabButtonImage } = {
home: { home: {
regular: require('@res/icons/home.png'), regular: require('@res/icons/home.png'),
fill: require('@res/icons/home-fill.png'), fill: require('@res/icons/home-fill.png'),
@ -30,11 +37,9 @@ const BottomTabButton: React.FC<{
label: string label: string
name: string name: string
isFocused: boolean isFocused: boolean
img: { regular: number; fill: number } img: TabButtonImage
navigation: any navigation: any
}> = ({ routeKey, label, name, isFocused, img, navigation }) => { }> = ({ routeKey, label, name, isFocused, img, navigation }) => {
const [opacity, setOpacity] = useState(1)
const onPress = () => { const onPress = () => {
const event = navigation.emit({ const event = navigation.emit({
type: 'tabPress', type: 'tabPress',
@ -48,47 +53,30 @@ const BottomTabButton: React.FC<{
} }
return ( return (
<Pressable // <PressableOpacity onPress={onPress} style={styles.button} ripple={true} rippleColor="rgba(100,100,100,0.18)">
onPress={onPress} <PressableOpacity onPress={onPress} style={styles.button}>
onPressIn={() => setOpacity(0.6)}
onPressOut={() => setOpacity(1)}
style={{
alignItems: 'center',
flex: 1,
opacity,
}}>
<FastImage <FastImage
source={isFocused ? img.fill : img.regular} source={isFocused ? img.fill : img.regular}
style={{ style={styles.image}
height: 26,
width: 26,
}}
tintColor={isFocused ? colors.text.primary : colors.text.secondary} tintColor={isFocused ? colors.text.primary : colors.text.secondary}
/> />
<Text <Text
style={{ style={{
...textStyles.xsmall, ...styles.text,
color: isFocused ? colors.text.primary : colors.text.secondary, color: isFocused ? colors.text.primary : colors.text.secondary,
}}> }}>
{label} {label}
</Text> </Text>
</Pressable> </PressableOpacity>
) )
} }
const MemoBottomTabButton = React.memo(BottomTabButton)
const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigation }) => { const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigation }) => {
return ( return (
<View> <View>
<NowPlayingBar /> <NowPlayingBar />
<View <View style={styles.container}>
style={{
height: 54,
backgroundColor: colors.gradient.high,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingHorizontal: 28,
}}>
{state.routes.map((route, index) => { {state.routes.map((route, index) => {
const { options } = descriptors[route.key] as any const { options } = descriptors[route.key] as any
const label = const label =
@ -99,7 +87,7 @@ const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigat
: route.name : route.name
return ( return (
<BottomTabButton <MemoBottomTabButton
key={route.key} key={route.key}
routeKey={route.key} routeKey={route.key}
label={label} label={label}
@ -115,4 +103,28 @@ const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigat
) )
} }
const styles = StyleSheet.create({
container: {
height: dimensions.tabBar,
backgroundColor: colors.gradient.high,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingHorizontal: 28,
},
button: {
alignItems: 'center',
flex: 1,
height: '100%',
},
image: {
height: 26,
width: 26,
},
text: {
fontSize: 10,
fontFamily: font.regular,
},
})
export default BottomTabBar export default BottomTabBar

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { StatusBar, View } from 'react-native' import { StatusBar, StyleSheet, View } from 'react-native'
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs' import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs'
import AlbumsTab from '@app/screens/LibraryAlbums' import AlbumsTab from '@app/screens/LibraryAlbums'
import ArtistsTab from '@app/screens/LibraryArtists' import ArtistsTab from '@app/screens/LibraryArtists'
@ -7,36 +7,10 @@ import PlaylistsTab from '@app/screens/LibraryPlaylists'
import { createNativeStackNavigator, NativeStackNavigationProp } from 'react-native-screens/native-stack' import { createNativeStackNavigator, NativeStackNavigationProp } from 'react-native-screens/native-stack'
import AlbumView from '@app/screens/AlbumView' import AlbumView from '@app/screens/AlbumView'
import { RouteProp } from '@react-navigation/native' import { RouteProp } from '@react-navigation/native'
import text from '@app/styles/text' import font from '@app/styles/font'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import ArtistView from '@app/screens/ArtistView' import ArtistView from '@app/screens/ArtistView'
import dimensions from '@app/styles/dimensions'
const Tab = createMaterialTopTabNavigator()
const LibraryTopTabNavigator = () => (
<Tab.Navigator
tabBarOptions={{
style: {
height: 48,
backgroundColor: colors.gradient.high,
elevation: 0,
marginTop: StatusBar.currentHeight,
},
labelStyle: {
...text.header,
textTransform: null as any,
marginTop: 0,
marginHorizontal: 2,
},
indicatorStyle: {
backgroundColor: colors.text.primary,
},
}}>
<Tab.Screen name="Albums" component={AlbumsTab} />
<Tab.Screen name="Artists" component={ArtistsTab} />
<Tab.Screen name="Playlists" component={PlaylistsTab} />
</Tab.Navigator>
)
type LibraryStackParamList = { type LibraryStackParamList = {
LibraryTopTabs: undefined LibraryTopTabs: undefined
@ -66,28 +40,73 @@ const ArtistScreen: React.FC<ArtistScreenProps> = ({ route }) => (
<ArtistView id={route.params.id} title={route.params.title} /> <ArtistView id={route.params.id} title={route.params.title} />
) )
const Stack = createNativeStackNavigator<LibraryStackParamList>() const Tab = createMaterialTopTabNavigator()
const itemScreenOptions = { const LibraryTopTabNavigator = () => (
title: '', <Tab.Navigator
headerStyle: { tabBarOptions={{
backgroundColor: colors.gradient.high, style: styles.tabBar,
}, labelStyle: styles.tablabelStyle,
headerHideShadow: true, indicatorStyle: styles.tabindicatorStyle,
headerTintColor: 'white', }}>
headerTitleStyle: { <Tab.Screen name="Albums" component={AlbumsTab} />
...text.header, <Tab.Screen name="Artists" component={ArtistsTab} />
} as any, <Tab.Screen name="Playlists" component={PlaylistsTab} />
} </Tab.Navigator>
const LibraryStackNavigator = () => (
<View style={{ flex: 1 }}>
<Stack.Navigator>
<Stack.Screen name="LibraryTopTabs" component={LibraryTopTabNavigator} options={{ headerShown: false }} />
<Stack.Screen name="AlbumView" component={AlbumScreen} options={itemScreenOptions} />
<Stack.Screen name="ArtistView" component={ArtistScreen} options={itemScreenOptions} />
</Stack.Navigator>
</View>
) )
const Stack = createNativeStackNavigator<LibraryStackParamList>()
const LibraryStackNavigator = () => {
const itemScreenOptions = {
title: '',
headerStyle: styles.stackheaderStyle,
headerHideShadow: true,
headerTintColor: 'white',
headerTitleStyle: styles.stackheaderTitleStyle,
}
return (
<View style={styles.stackContainer}>
<Stack.Navigator>
<Stack.Screen name="LibraryTopTabs" component={LibraryTopTabNavigator} options={{ headerShown: false }} />
<Stack.Screen name="AlbumView" component={AlbumScreen} options={itemScreenOptions} />
<Stack.Screen name="ArtistView" component={ArtistScreen} options={itemScreenOptions} />
</Stack.Navigator>
</View>
)
}
const styles = StyleSheet.create({
stackContainer: {
flex: 1,
},
stackheaderStyle: {
backgroundColor: colors.gradient.high,
},
stackheaderTitleStyle: {
fontSize: 18,
fontFamily: font.semiBold,
color: colors.text.primary,
},
tabBar: {
height: dimensions.header,
marginTop: StatusBar.currentHeight,
backgroundColor: colors.gradient.high,
elevation: 0,
justifyContent: 'center',
},
tablabelStyle: {
fontSize: 18,
fontFamily: font.semiBold,
color: colors.text.primary,
textTransform: null as any,
marginTop: 0,
marginHorizontal: 2,
},
tabindicatorStyle: {
backgroundColor: colors.text.primary,
},
})
export default LibraryStackNavigator export default LibraryStackNavigator

View File

@ -1,13 +1,13 @@
import { useNavigation } from '@react-navigation/native' 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 { ActivityIndicator, GestureResponderEvent, StyleSheet, Text, useWindowDimensions, View } from 'react-native' import { ActivityIndicator, GestureResponderEvent, StyleSheet, Text, View } from 'react-native'
import IconFA from 'react-native-vector-icons/FontAwesome' import IconFA from 'react-native-vector-icons/FontAwesome'
import IconMat from 'react-native-vector-icons/MaterialIcons' import IconMat from 'react-native-vector-icons/MaterialIcons'
import { albumAtomFamily } from '@app/state/music' import { albumAtomFamily } from '@app/state/music'
import { currentTrackAtom, useSetQueue } from '@app/state/trackplayer' import { currentTrackAtom, useSetQueue } from '@app/state/trackplayer'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import text, { Font } from '@app/styles/text' import font from '@app/styles/font'
import AlbumArt from '@app/components/AlbumArt' import AlbumArt from '@app/components/AlbumArt'
import Button from '@app/components/Button' import Button from '@app/components/Button'
import GradientBackground from '@app/components/GradientBackground' import GradientBackground from '@app/components/GradientBackground'
@ -46,7 +46,6 @@ const SongItem: React.FC<{
const songStyles = StyleSheet.create({ const songStyles = StyleSheet.create({
container: { container: {
marginTop: 20, marginTop: 20,
marginLeft: 10,
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
@ -54,14 +53,15 @@ const songStyles = StyleSheet.create({
text: { text: {
flex: 1, flex: 1,
alignItems: 'flex-start', alignItems: 'flex-start',
width: 100,
}, },
title: { title: {
fontSize: 16, fontSize: 16,
fontFamily: Font.semiBold, fontFamily: font.semiBold,
}, },
subtitle: { subtitle: {
fontSize: 14, fontSize: 14,
fontFamily: Font.regular, fontFamily: font.regular,
color: colors.text.secondary, color: colors.text.secondary,
}, },
controls: { controls: {
@ -78,101 +78,59 @@ const AlbumDetails: React.FC<{
id: string id: string
}> = ({ id }) => { }> = ({ id }) => {
const album = useAtomValue(albumAtomFamily(id)) const album = useAtomValue(albumAtomFamily(id))
const layout = useWindowDimensions()
const setQueue = useSetQueue() const setQueue = useSetQueue()
const coverSize = layout.width - layout.width / 2.5
if (!album) { if (!album) {
return <Text style={text.paragraph}>No Album</Text> return <></>
} }
return ( return (
<ImageGradientScrollView <ImageGradientScrollView
imageUri={album.coverArtThumbUri} imageUri={album.coverArtThumbUri}
imageKey={`${album.name}${album.artist}`} imageKey={`${album.name}${album.artist}`}
style={{ style={styles.container}>
flex: 1, <View style={styles.content}>
}} <View style={styles.cover}>
contentContainerStyle={{ <AlbumArt id={album.id} height="100%" width="100%" />
alignItems: 'center', </View>
paddingTop: coverSize / 8, <Text style={styles.title}>{album.name}</Text>
}}> <Text style={styles.subtitle}>
<AlbumArt id={album.id} height={coverSize} width={coverSize} /> {album.artist}
<Text {album.year ? `${album.year}` : ''}
style={{ </Text>
...text.title, <View style={styles.controls}>
marginTop: 12, <Button title="Play Album" onPress={() => setQueue(album.songs, album.name, album.songs[0].id)} />
width: layout.width - layout.width / 8, </View>
textAlign: 'center', <View style={styles.songs}>
}}> {album.songs
{album.name} .sort((a, b) => {
</Text> if (b.track && a.track) {
return a.track - b.track
<Text } else {
style={{ return a.title.localeCompare(b.title)
...text.itemSubtitle, }
fontSize: 14, })
marginTop: 4, .map(s => (
marginBottom: 20, <SongItem
width: layout.width - layout.width / 8, key={s.id}
textAlign: 'center', id={s.id}
}}> title={s.title}
{album.artist} artist={s.artist}
{album.year ? `${album.year}` : ''} track={s.track}
</Text> onPress={() => setQueue(album.songs, album.name, s.id)}
/>
<View ))}
style={{ </View>
flexDirection: 'row',
}}>
<Button title="Play Album" onPress={() => setQueue(album.songs, album.name, album.songs[0].id)} />
</View>
<View
style={{
width: layout.width - layout.width / 20,
marginTop: 20,
marginBottom: 30,
}}>
{album.songs
.sort((a, b) => {
if (b.track && a.track) {
return a.track - b.track
} else {
return a.title.localeCompare(b.title)
}
})
.map(s => (
<SongItem
key={s.id}
id={s.id}
title={s.title}
artist={s.artist}
track={s.track}
onPress={() => setQueue(album.songs, album.name, s.id)}
/>
))}
</View> </View>
</ImageGradientScrollView> </ImageGradientScrollView>
) )
} }
const AlbumViewFallback = () => { const AlbumViewFallback = () => (
const layout = useWindowDimensions() <GradientBackground style={styles.fallback}>
<ActivityIndicator size="large" color={colors.accent} />
const coverSize = layout.width - layout.width / 2.5 </GradientBackground>
)
return (
<GradientBackground
style={{
alignItems: 'center',
paddingTop: coverSize / 8 + coverSize / 2 - 18,
}}>
<ActivityIndicator size="large" color={colors.accent} />
</GradientBackground>
)
}
const AlbumView: React.FC<{ const AlbumView: React.FC<{
id: string id: string
@ -191,4 +149,46 @@ const AlbumView: React.FC<{
) )
} }
const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
alignItems: 'center',
paddingTop: 10,
paddingHorizontal: 20,
},
title: {
fontSize: 24,
fontFamily: font.bold,
color: colors.text.primary,
marginTop: 12,
textAlign: 'center',
},
subtitle: {
fontFamily: font.regular,
color: colors.text.secondary,
fontSize: 14,
marginTop: 4,
marginBottom: 20,
textAlign: 'center',
},
cover: {
height: 220,
width: 220,
},
controls: {
flexDirection: 'row',
},
songs: {
marginTop: 10,
marginBottom: 30,
width: '100%',
},
fallback: {
alignItems: 'center',
paddingTop: 100,
},
})
export default React.memo(AlbumView) export default React.memo(AlbumView)

View File

@ -1,11 +1,12 @@
import { useNavigation } from '@react-navigation/native' 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 { Text } from 'react-native' import { StyleSheet, Text } from 'react-native'
import { artistInfoAtomFamily } from '@app/state/music' import { artistInfoAtomFamily } from '@app/state/music'
import text from '@app/styles/text'
import ArtistArt from '@app/components/ArtistArt' import ArtistArt from '@app/components/ArtistArt'
import GradientScrollView from '@app/components/GradientScrollView' import GradientScrollView from '@app/components/GradientScrollView'
import font from '@app/styles/font'
import colors from '@app/styles/colors'
const ArtistDetails: React.FC<{ id: string }> = ({ id }) => { const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
const artist = useAtomValue(artistInfoAtomFamily(id)) const artist = useAtomValue(artistInfoAtomFamily(id))
@ -15,15 +16,8 @@ const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
} }
return ( return (
<GradientScrollView <GradientScrollView style={styles.scroll} contentContainerStyle={styles.scrollContent}>
style={{ <Text style={styles.title}>{artist.name}</Text>
flex: 1,
}}
contentContainerStyle={{
alignItems: 'center',
// paddingTop: coverSize / 8,
}}>
<Text style={text.paragraph}>{artist.name}</Text>
<ArtistArt id={artist.id} height={200} width={200} /> <ArtistArt id={artist.id} height={200} width={200} />
</GradientScrollView> </GradientScrollView>
) )
@ -46,4 +40,18 @@ const ArtistView: React.FC<{
) )
} }
const styles = StyleSheet.create({
scroll: {
flex: 1,
},
scrollContent: {
alignItems: 'center',
},
title: {
fontFamily: font.regular,
fontSize: 16,
color: colors.text.primary,
},
})
export default React.memo(ArtistView) export default React.memo(ArtistView)

View File

@ -1,12 +1,13 @@
import { useNavigation } from '@react-navigation/native' 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 { Pressable, Text, View } from 'react-native' import { Pressable, StyleSheet, Text, View } from 'react-native'
import { Album } from '@app/models/music' import { Album } from '@app/models/music'
import { albumsAtom, albumsUpdatingAtom, useUpdateAlbums } from '@app/state/music' import { albumsAtom, albumsUpdatingAtom, useUpdateAlbums } from '@app/state/music'
import textStyles from '@app/styles/text' import font from '@app/styles/font'
import AlbumArt from '@app/components/AlbumArt' import AlbumArt from '@app/components/AlbumArt'
import GradientFlatList from '@app/components/GradientFlatList' import GradientFlatList from '@app/components/GradientFlatList'
import colors from '@app/styles/colors'
const AlbumItem: React.FC<{ const AlbumItem: React.FC<{
id: string id: string
@ -15,31 +16,14 @@ const AlbumItem: React.FC<{
}> = ({ id, name, artist }) => { }> = ({ id, name, artist }) => {
const navigation = useNavigation() const navigation = useNavigation()
const size = 125
return ( return (
<Pressable <Pressable style={styles.item} onPress={() => navigation.navigate('AlbumView', { id, title: name })}>
style={{ <AlbumArt id={id} height={styles.art.height} width={styles.art.height} />
alignItems: 'center', <View style={styles.itemDetails}>
marginVertical: 8, <Text style={styles.title} numberOfLines={2}>
flex: 1 / 3,
}}
onPress={() => navigation.navigate('AlbumView', { id, title: name })}>
<AlbumArt id={id} height={size} width={size} />
<View
style={{
flex: 1,
width: size,
}}>
<Text
style={{
...textStyles.itemTitle,
marginTop: 4,
}}
numberOfLines={2}>
{name} {name}
</Text> </Text>
<Text style={{ ...textStyles.itemSubtitle }} numberOfLines={1}> <Text style={styles.subtitle} numberOfLines={1}>
{artist} {artist}
</Text> </Text>
</View> </View>
@ -66,7 +50,7 @@ const AlbumsList = () => {
}) })
return ( return (
<View style={{ flex: 1 }}> <View style={styles.container}>
<GradientFlatList <GradientFlatList
data={albumsList} data={albumsList}
renderItem={AlbumListRenderItem} renderItem={AlbumListRenderItem}
@ -87,4 +71,33 @@ const AlbumsTab = () => (
</React.Suspense> </React.Suspense>
) )
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
alignItems: 'center',
marginVertical: 8,
flex: 1 / 3,
},
art: {
height: 125,
},
itemDetails: {
flex: 1,
width: 125,
},
title: {
fontSize: 13,
fontFamily: font.semiBold,
color: colors.text.primary,
marginTop: 4,
},
subtitle: {
fontSize: 12,
fontFamily: font.regular,
color: colors.text.secondary,
},
})
export default React.memo(AlbumsTab) export default React.memo(AlbumsTab)

View File

@ -1,34 +1,22 @@
import { useNavigation } from '@react-navigation/native' 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 { Pressable } from 'react-native' import { Pressable, StyleSheet } from 'react-native'
import { Text } from 'react-native' import { Text } from 'react-native'
import { Artist } from '@app/models/music' import { Artist } from '@app/models/music'
import { artistsAtom, artistsUpdatingAtom, useUpdateArtists } from '@app/state/music' import { artistsAtom, artistsUpdatingAtom, useUpdateArtists } from '@app/state/music'
import textStyles from '@app/styles/text' import font from '@app/styles/font'
import ArtistArt from '@app/components/ArtistArt' import ArtistArt from '@app/components/ArtistArt'
import GradientFlatList from '@app/components/GradientFlatList' import GradientFlatList from '@app/components/GradientFlatList'
import colors from '@app/styles/colors'
const ArtistItem: React.FC<{ item: Artist }> = ({ item }) => { const ArtistItem: React.FC<{ item: Artist }> = ({ item }) => {
const navigation = useNavigation() const navigation = useNavigation()
return ( return (
<Pressable <Pressable style={styles.item} onPress={() => navigation.navigate('ArtistView', { id: item.id, title: item.name })}>
style={{
flexDirection: 'row',
alignItems: 'center',
marginVertical: 6,
marginLeft: 6,
}}
onPress={() => navigation.navigate('ArtistView', { id: item.id, title: item.name })}>
<ArtistArt id={item.id} width={56} height={56} /> <ArtistArt id={item.id} width={56} height={56} />
<Text <Text style={styles.title}>{item.name}</Text>
style={{
...textStyles.paragraph,
marginLeft: 12,
}}>
{item.name}
</Text>
</Pressable> </Pressable>
) )
} }
@ -66,4 +54,19 @@ const ArtistsList = () => {
const ArtistsTab = () => <ArtistsList /> const ArtistsTab = () => <ArtistsList />
const styles = StyleSheet.create({
item: {
flexDirection: 'row',
alignItems: 'center',
marginVertical: 6,
marginLeft: 6,
},
title: {
fontFamily: font.regular,
fontSize: 16,
color: colors.text.primary,
marginLeft: 12,
},
})
export default ArtistsTab export default ArtistsTab

View File

@ -20,11 +20,12 @@ import {
useProgress, useProgress,
} from '@app/state/trackplayer' } from '@app/state/trackplayer'
import colors from '@app/styles/colors' import colors from '@app/styles/colors'
import { Font } from '@app/styles/text' import font from '@app/styles/font'
import formatDuration from '@app/util/formatDuration' import formatDuration from '@app/util/formatDuration'
import CoverArt from '@app/components/CoverArt' import CoverArt from '@app/components/CoverArt'
import ImageGradientBackground from '@app/components/ImageGradientBackground' import ImageGradientBackground from '@app/components/ImageGradientBackground'
import PressableOpacity from '@app/components/PressableOpacity' import PressableOpacity from '@app/components/PressableOpacity'
import dimensions from '@app/styles/dimensions'
const NowPlayingHeader = () => { const NowPlayingHeader = () => {
const queueName = useAtomValue(queueNameAtom) const queueName = useAtomValue(queueNameAtom)
@ -47,7 +48,7 @@ const NowPlayingHeader = () => {
const headerStyles = StyleSheet.create({ const headerStyles = StyleSheet.create({
container: { container: {
height: 58, height: dimensions.header,
width: '100%', width: '100%',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
@ -59,7 +60,7 @@ const headerStyles = StyleSheet.create({
marginHorizontal: 8, marginHorizontal: 8,
}, },
queueName: { queueName: {
fontFamily: Font.bold, fontFamily: font.bold,
fontSize: 16, fontSize: 16,
color: colors.text.primary, color: colors.text.primary,
flex: 1, flex: 1,
@ -126,13 +127,13 @@ const infoStyles = StyleSheet.create({
}, },
title: { title: {
height: 28, height: 28,
fontFamily: Font.bold, fontFamily: font.bold,
fontSize: 22, fontSize: 22,
color: colors.text.primary, color: colors.text.primary,
}, },
artist: { artist: {
height: 20, height: 20,
fontFamily: Font.regular, fontFamily: font.regular,
fontSize: 16, fontSize: 16,
color: colors.text.secondary, color: colors.text.secondary,
}, },
@ -194,7 +195,7 @@ const seekStyles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
}, },
text: { text: {
fontFamily: Font.regular, fontFamily: font.regular,
fontSize: 15, fontSize: 15,
color: colors.text.primary, color: colors.text.primary,
}, },

View File

@ -2,11 +2,10 @@ import { useNavigation } from '@react-navigation/core'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import md5 from 'md5' import md5 from 'md5'
import React from 'react' import React from 'react'
import { Button, Text, View } from 'react-native' import { Button, StyleSheet, Text, View } from 'react-native'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { appSettingsAtom } from '@app/state/settings' import { appSettingsAtom } from '@app/state/settings'
import { getAllKeys, multiRemove } from '@app/storage/asyncstorage' import { getAllKeys, multiRemove } from '@app/storage/asyncstorage'
import text from '@app/styles/text'
const TestControls = () => { const TestControls = () => {
const navigation = useNavigation() const navigation = useNavigation()
@ -57,8 +56,8 @@ const ServerSettingsView = () => {
<Button title="Add default server" onPress={bootstrapServer} /> <Button title="Add default server" onPress={bootstrapServer} />
{appSettings.servers.map(s => ( {appSettings.servers.map(s => (
<View key={s.id}> <View key={s.id}>
<Text style={text.paragraph}>{s.address}</Text> <Text style={styles.text}>{s.address}</Text>
<Text style={text.paragraph}>{s.username}</Text> <Text style={styles.text}>{s.username}</Text>
</View> </View>
))} ))}
</View> </View>
@ -74,4 +73,10 @@ const SettingsView = () => (
</View> </View>
) )
const styles = StyleSheet.create({
text: {
color: 'white',
},
})
export default SettingsView export default SettingsView

14
app/styles/dimensions.ts Normal file
View File

@ -0,0 +1,14 @@
import { StatusBar } from 'react-native'
const header = 56
const tabBar = 54
const top = () => header + (StatusBar.currentHeight || 0)
const bottom = () => tabBar
export default {
header,
tabBar,
top,
bottom,
}

7
app/styles/font.ts Normal file
View File

@ -0,0 +1,7 @@
enum font {
regular = 'Metropolis-Regular',
semiBold = 'Metropolis-SemiBold',
bold = 'Metropolis-Bold',
}
export default font

View File

@ -1,73 +0,0 @@
import { TextStyle } from 'react-native'
import colors from '@app/styles/colors'
export enum Font {
regular = 'Metropolis-Regular',
semiBold = 'Metropolis-SemiBold',
bold = 'Metropolis-Bold',
}
const paragraph: TextStyle = {
fontFamily: Font.regular,
fontSize: 16,
color: colors.text.primary,
}
const header: TextStyle = {
...paragraph,
fontSize: 18,
fontFamily: Font.semiBold,
}
const title: TextStyle = {
...paragraph,
fontSize: 24,
fontFamily: Font.bold,
}
const itemTitle: TextStyle = {
...paragraph,
fontSize: 13,
fontFamily: Font.semiBold,
}
const itemSubtitle: TextStyle = {
...paragraph,
fontSize: 12,
color: colors.text.secondary,
}
const songListTitle: TextStyle = {
...paragraph,
fontSize: 16,
fontFamily: Font.semiBold,
}
const songListSubtitle: TextStyle = {
...paragraph,
fontSize: 14,
color: colors.text.secondary,
}
const xsmall: TextStyle = {
...paragraph,
fontSize: 10,
}
const button: TextStyle = {
...paragraph,
fontSize: 15,
fontFamily: Font.bold,
}
export default {
paragraph,
header,
title,
itemTitle,
itemSubtitle,
songListTitle,
songListSubtitle,
xsmall,
button,
}