prototype animated fading header

This commit is contained in:
austinried 2021-08-12 09:25:49 +09:00
parent f6ecc0bf40
commit f523a231f1
3 changed files with 124 additions and 51 deletions

View File

@ -2,10 +2,11 @@ import GradientBackground from '@app/components/GradientBackground'
import colors from '@app/styles/colors'
import dimensions from '@app/styles/dimensions'
import React from 'react'
import { ScrollView, ScrollViewProps, useWindowDimensions } from 'react-native'
import { ScrollViewProps, useWindowDimensions } from 'react-native'
import Animated from 'react-native-reanimated'
const GradientScrollView: React.FC<
ScrollViewProps & {
Animated.AnimateProps<ScrollViewProps> & {
offset?: number
}
> = props => {
@ -14,14 +15,14 @@ const GradientScrollView: React.FC<
const minHeight = layout.height - (dimensions.top() + dimensions.bottom())
return (
<ScrollView
<Animated.ScrollView
overScrollMode="never"
{...props}
style={[props.style, { backgroundColor: colors.gradient.low }]}
contentContainerStyle={[props.contentContainerStyle, { minHeight }]}>
contentContainerStyle={[props.contentContainerStyle as any, { minHeight }]}>
<GradientBackground style={{ top: props.offset }} />
{props.children}
</ScrollView>
</Animated.ScrollView>
)
}

View File

@ -90,7 +90,7 @@ function createTabStackNavigator(Component: React.ComponentType<any>) {
<Stack.Navigator initialRouteName="main">
<Stack.Screen name="main" component={Component} options={{ headerShown: false }} />
<Stack.Screen name="album" component={AlbumScreen} options={itemScreenOptions} />
<Stack.Screen name="artist" component={ArtistScreen} options={itemScreenOptions} />
<Stack.Screen name="artist" component={ArtistScreen} options={{ headerShown: false }} />
<Stack.Screen name="playlist" component={PlaylistScreen} options={itemScreenOptions} />
</Stack.Navigator>
)

View File

@ -4,16 +4,20 @@ import GradientBackground from '@app/components/GradientBackground'
import GradientScrollView from '@app/components/GradientScrollView'
import Header from '@app/components/Header'
import ListItem from '@app/components/ListItem'
import PressableOpacity from '@app/components/PressableOpacity'
import { useArtistInfo } from '@app/hooks/music'
import { useSetQueue } from '@app/hooks/trackplayer'
import { Album, Song } from '@app/models/music'
import colors from '@app/styles/colors'
import dimensions from '@app/styles/dimensions'
import font from '@app/styles/font'
import { useLayout } from '@react-native-community/hooks'
import { useNavigation } from '@react-navigation/native'
import React, { useEffect } from 'react'
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'
import React, { useCallback } from 'react'
import { ActivityIndicator, StatusBar, StyleSheet, Text, View } from 'react-native'
import FastImage from 'react-native-fast-image'
import Animated, { useAnimatedScrollHandler, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'
import IconMat from 'react-native-vector-icons/MaterialIcons'
const AlbumItem = React.memo<{
album: Album
@ -70,10 +74,79 @@ const ArtistDetailsFallback = React.memo(() => (
</GradientBackground>
))
const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
const NowPlayingHeader = React.memo<{ title: string; animatedOpacity: { opacity: number } }>(
({ title, animatedOpacity }) => {
const navigation = useNavigation()
const back = useCallback(() => {
navigation.goBack()
}, [navigation])
return (
<Animated.View style={[headerStyles.container, animatedOpacity]}>
<PressableOpacity onPress={back} style={headerStyles.icons} ripple={true}>
<IconMat name="arrow-back" color="white" size={25} />
</PressableOpacity>
<View style={headerStyles.center}>
<Text numberOfLines={1} style={headerStyles.queueName}>
{title}
</Text>
</View>
{/* <PressableOpacity style={headerStyles.icons} ripple={true}>
<IconMat name="more-vert" color="white" size={25} />
</PressableOpacity> */}
</Animated.View>
)
},
)
const headerStyles = StyleSheet.create({
container: {
height: dimensions.top(),
paddingTop: StatusBar.currentHeight,
backgroundColor: colors.gradient.high,
width: '100%',
position: 'absolute',
zIndex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
icons: {
height: 42,
width: 42,
marginHorizontal: 8,
},
center: {
flex: 1,
},
queueName: {
fontFamily: font.semiBold,
fontSize: 18,
color: colors.text.primary,
flex: 1,
textAlignVertical: 'center',
paddingLeft: 14,
},
})
const ArtistView = React.memo<{ id: string; title: string }>(({ id, title }) => {
const artist = useArtistInfo(id)
const albumsLayout = useLayout()
const coverLayout = useLayout()
const headerOpacity = useSharedValue(0)
const onScroll = useAnimatedScrollHandler({
onScroll: event => {
headerOpacity.value = Math.max(0, event.contentOffset.y - 70) / (artistCoverHeight - (70 + dimensions.header))
},
})
const animatedOpacity = useAnimatedStyle(() => {
return {
opacity: headerOpacity.value,
}
})
const albumSize = albumsLayout.width / 2 - styles.container.paddingHorizontal / 2
@ -86,54 +159,53 @@ const ArtistDetails: React.FC<{ id: string }> = ({ id }) => {
.sort((a, b) => (b.year || 0) - (a.year || 0))
return (
<GradientScrollView
onLayout={coverLayout.onLayout}
offset={artistCoverHeight}
style={styles.scroll}
contentContainerStyle={styles.scrollContent}>
<CoverArt
artistId={artist.id}
style={styles.artistCover}
resizeMode={FastImage.resizeMode.cover}
round={false}
imageSize="original"
/>
<View style={styles.titleContainer}>
<Text style={styles.title}>{artist.name}</Text>
</View>
<View style={styles.container}>
{artist.topSongs.length > 0 ? (
<TopSongs songs={artist.topSongs} name={artist.name} artistId={artist.id} />
) : (
<></>
)}
<Header>Albums</Header>
<View style={styles.albums} onLayout={albumsLayout.onLayout}>
{_albums.map(a => (
<AlbumItem key={a.id} album={a} height={albumSize} width={albumSize} />
))}
<View style={{ flex: 1 }}>
<NowPlayingHeader title={title} animatedOpacity={animatedOpacity} />
<GradientScrollView
onLayout={coverLayout.onLayout}
offset={artistCoverHeight}
style={styles.scroll}
contentContainerStyle={styles.scrollContent}
onScroll={onScroll}>
<CoverArt
artistId={artist.id}
style={styles.artistCover}
resizeMode={FastImage.resizeMode.cover}
round={false}
imageSize="original"
/>
<View style={styles.titleContainer}>
<Text style={styles.title}>{artist.name}</Text>
</View>
</View>
</GradientScrollView>
<View style={styles.container}>
{artist.topSongs.length > 0 ? (
<TopSongs songs={artist.topSongs} name={artist.name} artistId={artist.id} />
) : (
<></>
)}
<Header>Albums</Header>
<View style={styles.albums} onLayout={albumsLayout.onLayout}>
{_albums.map(a => (
<AlbumItem key={a.id} album={a} height={albumSize} width={albumSize} />
))}
</View>
</View>
</GradientScrollView>
</View>
)
}
const ArtistView = React.memo<{
id: string
title: string
}>(({ id, title }) => {
const navigation = useNavigation()
useEffect(() => {
navigation.setOptions({ title })
})
return <ArtistDetails id={id} />
})
const artistCoverHeight = 280
const artistCoverHeight = 350
const styles = StyleSheet.create({
headerText: {
flex: 1,
textAlign: 'center',
textAlignVertical: 'center',
fontFamily: font.semiBold,
fontSize: 22,
color: colors.text.primary,
},
scroll: {
flex: 1,
},