started to implement jotai states to control and manage the playview

This commit is contained in:
Clément Le Bihan
2024-01-13 00:39:20 +01:00
parent 1d496301d9
commit 00ee5cd531
8 changed files with 113 additions and 115 deletions

View File

@@ -8,20 +8,16 @@ import { Audio } from 'expo-av';
import { SvgContainer } from './SvgContainer';
import LoadingComponent from '../Loading';
import { SplendidGrandPiano } from 'smplr';
import { atom, useAtom } from 'jotai';
export const timestampAtom = atom(0);
export const shouldPlayAtom = atom(false);
export const partitionStateAtom = atom(
'loading' as 'loading' | 'ready' | 'playing' | 'paused' | 'ended' | 'error'
);
export type ParitionMagicProps = {
timestamp: number;
songID: number;
shouldPlay: boolean;
onEndReached: () => void;
onError: (err: string) => void;
onReady: () => void;
onPlay: () => void;
onPause: () => void;
};
const getSVGURL = (songID: number) => {
return API.getPartitionSvgUrl(songID);
};
const getCursorToPlay = (
@@ -43,16 +39,7 @@ const getCursorToPlay = (
const transitionDuration = 50;
const PartitionMagic = ({
timestamp,
songID,
shouldPlay,
onEndReached,
onError,
onReady,
onPlay,
onPause,
}: ParitionMagicProps) => {
const PartitionMagic = ({ songID }: ParitionMagicProps) => {
const { data, isLoading, isError } = useQuery(API.getSongCursorInfos(songID));
const currentCurIdx = React.useRef(-1);
const [endPartitionReached, setEndPartitionReached] = React.useState(false);
@@ -61,6 +48,9 @@ const PartitionMagic = ({
const melodySound = React.useRef<Audio.Sound | null>(null);
const piano = React.useRef<SplendidGrandPiano | null>(null);
const [isPianoLoaded, setIsPianoLoaded] = React.useState(false);
const timestamp = useAtom(timestampAtom)[0];
const shouldPlay = useAtom(shouldPlayAtom)[0];
const [, setPartitionState] = useAtom(partitionStateAtom);
const cursorPaddingVertical = 10;
const cursorPaddingHorizontal = 3;
@@ -75,7 +65,8 @@ const PartitionMagic = ({
if (!endPartitionReached && currentCurIdx.current + 1 === data?.cursors.length) {
// weird contraption but the mobile don't want classic functions to be called
// with the withTiming function :(
setEndPartitionReached(true);
melodySound.current?.pauseAsync();
setPartitionState('ended');
}
React.useEffect(() => {
@@ -115,15 +106,14 @@ const PartitionMagic = ({
}, [data]);
React.useEffect(() => {
if (onError && isError) {
onError('Error while loading partition');
return;
if (isError) {
setPartitionState('error');
}
}, [onError, isError]);
}, [isError]);
React.useEffect(() => {
if (isPartitionSvgLoaded && !isLoading && (melodySound.current?._loaded || isPianoLoaded)) {
onReady();
setPartitionState('ready');
}
}, [isPartitionSvgLoaded, isLoading, melodySound.current?._loaded, isPianoLoaded]);
@@ -132,27 +122,29 @@ const PartitionMagic = ({
if (!piano.current || !isPianoLoaded) {
return;
}
shouldPlay ? onPlay() : onPause();
setPartitionState(shouldPlay ? 'playing' : 'paused');
return;
}
if (!melodySound.current || !melodySound.current._loaded) {
return;
}
if (shouldPlay) {
melodySound.current.playAsync().then(onPlay).catch(console.error);
melodySound.current
.playAsync()
.then(() => {
setPartitionState('playing');
})
.catch(console.error);
} else {
melodySound.current.pauseAsync().then(onPause).catch(console.error);
melodySound.current
.pauseAsync()
.then(() => {
setPartitionState('paused');
})
.catch(console.error);
}
}, [shouldPlay]);
React.useEffect(() => {
if (endPartitionReached) {
// if the audio is unsync
melodySound.current?.pauseAsync();
onEndReached();
}
}, [endPartitionReached]);
React.useEffect(() => {
if (!melodySound.current || !melodySound.current._loaded) return;
if (!data || data?.cursors.length === 0) return;
@@ -249,7 +241,7 @@ const PartitionMagic = ({
}}
>
<SvgContainer
url={getSVGURL(songID)}
url={API.getPartitionSvgUrl(songID)}
onReady={() => {
setIsPartitionSvgLoaded(true);
}}
@@ -277,11 +269,4 @@ const PartitionMagic = ({
);
};
PartitionMagic.defaultProps = {
onError: () => {},
onReady: () => {},
onPlay: () => {},
onPause: () => {},
};
export default PartitionMagic;

View File

@@ -0,0 +1,31 @@
import { Text, useTheme } from 'native-base';
import { timestampAtom, partitionStateAtom } from './PartitionMagic';
import { useAtom } from 'jotai';
export const TimestampDisplay = () => {
const [time] = useAtom(timestampAtom);
const [partitionState] = useAtom(partitionStateAtom);
const { colors } = useTheme();
const textColor = colors.text;
const paused = partitionState === 'paused';
if (time < 0) {
if (paused) {
return <Text color={textColor[900]}>0:00</Text>;
}
return (
<Text color={textColor[900]}>
{Math.floor((time % 60000) / 1000)
.toFixed(0)
.toString()}
</Text>
);
}
return (
<Text color={textColor[900]}>
{`${Math.floor(time / 60000)}:${Math.floor((time % 60000) / 1000)
.toFixed(0)
.toString()
.padStart(2, '0')}`}
</Text>
);
};

View File

@@ -10,20 +10,21 @@ import Animated, {
Easing,
} from 'react-native-reanimated';
import { ColorSchemeType } from 'native-base/lib/typescript/components/types';
import { atom, useAtom } from 'jotai';
export const scoreMessageAtom = atom<ScoreMessage | null>(null);
export const scoreAtom = atom(0);
export type ScoreMessage = {
content: string;
color?: ColorSchemeType;
id: number;
};
type PlayScoreProps = {
score: number;
timestamp: number;
streak: number;
message?: ScoreMessage;
};
export const PlayScore = ({ score, streak, message }: PlayScoreProps) => {
export const PlayScore = () => {
const [message] = useAtom(scoreMessageAtom);
const [score] = useAtom(scoreAtom);
const scoreMessageScale = useSharedValue(0);
// this style should bounce in on enter and fade away
const scoreMsgStyle = useAnimatedStyle(() => {
@@ -92,9 +93,9 @@ export const PlayScore = ({ score, streak, message }: PlayScoreProps) => {
<Text color={textColor[900]} fontSize={20}>
{message.content}
</Text>
{streak > 0 && (
{message.streak > 0 && (
<Text color={textColor[900]} fontSize={15} bold>
{`x${streak}`}
{`x${message.streak}`}
</Text>
)}
</View>

View File

@@ -6,33 +6,27 @@ import { MetronomeControls } from '../Metronome';
import StarProgress from '../StarProgress';
import Song from '../../models/Song';
import { useTheme } from 'native-base';
import { atom, useAtom } from 'jotai';
import { partitionStateAtom, shouldPlayAtom } from './PartitionMagic';
import { TimestampDisplay } from './PartitionTimestampText';
export const shouldEndAtom = atom(false);
type PlayViewControlBarProps = {
song: Song;
time: number;
paused: boolean;
score: number;
disabled: boolean;
onResume: () => void;
onPause: () => void;
onEnd: () => void;
};
const PlayViewControlBar = ({
song,
time,
paused,
score,
disabled,
onResume,
onPause,
onEnd,
}: PlayViewControlBarProps) => {
const PlayViewControlBar = ({ song }: PlayViewControlBarProps) => {
const [, setShouldPlay] = useAtom(shouldPlayAtom);
const [partitionState] = useAtom(partitionStateAtom);
const [, setShouldEnd] = useAtom(shouldEndAtom);
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
const isPhone = screenSize === 'small';
const bpm = React.useRef<number>(60);
const { colors } = useTheme();
const textColor = colors.text;
const paused = partitionState === 'paused';
const disabled = partitionState === 'loading' || partitionState === 'error';
return (
<Row
style={{
@@ -116,7 +110,7 @@ const PlayViewControlBar = ({
color: colors.coolGray[900],
name: paused ? 'play' : 'pause',
}}
onPress={paused ? onResume : onPause}
onPress={() => setShouldPlay(paused)}
/>
<IconButton
size="sm"
@@ -126,22 +120,10 @@ const PlayViewControlBar = ({
as: Ionicons,
name: 'stop',
}}
onPress={onEnd}
onPress={() => setShouldEnd(true)}
/>
<Text color={textColor[900]}>
{time < 0
? paused
? '0:00'
: Math.floor((time % 60000) / 1000)
.toFixed(0)
.toString()
: `${Math.floor(time / 60000)}:${Math.floor((time % 60000) / 1000)
.toFixed(0)
.toString()
.padStart(2, '0')}`}
</Text>
<TimestampDisplay />
<StarProgress
value={score}
max={100}
starSteps={[50, 75, 90]}
style={{
@@ -168,11 +150,4 @@ const PlayViewControlBar = ({
);
};
PlayViewControlBar.defaultProps = {
onResume: () => {},
onPause: () => {},
onEnd: () => {},
disabled: false,
};
export default PlayViewControlBar;

View File

@@ -2,15 +2,17 @@ import * as React from 'react';
import { Progress } from 'native-base';
import { View, ViewStyle, StyleProp } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { scoreAtom } from './Play/PlayScore';
import { useAtom } from 'jotai';
export interface StarProgressProps {
value: number;
max: number;
starSteps: number[];
style?: StyleProp<ViewStyle>;
}
const StarProgress = (props: StarProgressProps) => {
const [score] = useAtom(scoreAtom);
return (
<View
style={[
@@ -29,15 +31,15 @@ const StarProgress = (props: StarProgressProps) => {
style={{
flex: 1,
}}
value={props.value}
value={score}
max={props.max}
/>
{props.starSteps.map((step) => {
return (
<Ionicons
key={step}
name={step <= props.value ? 'star' : 'star-outline'}
color={step <= props.value ? '#EBDA3C' : '#6075F9'}
name={step <= score ? 'star' : 'star-outline'}
color={step <= score ? '#EBDA3C' : '#6075F9'}
size={20}
style={{
position: 'absolute',

View File

@@ -38,6 +38,7 @@
"fbjs": "^3.0.5",
"i18next": "^23.5.1",
"iconsax-react-native": "^0.0.8",
"jotai": "^2.6.1",
"native-base": "^3.4.28",
"normalize-css-color": "^1.0.2",
"react": "18.2.0",

View File

@@ -374,7 +374,7 @@ const PlayView = ({ songId }: PlayViewProps) => {
position: 'absolute',
}}
>
<PlayScore score={score} streak={streak} message={lastScoreMessage} />
<PlayScore />
</View>
<View
style={{
@@ -386,23 +386,21 @@ const PlayView = ({ songId }: PlayViewProps) => {
}}
>
<PartitionMagic
shouldPlay={shouldPlay}
timestamp={time}
songID={song.data.id}
onEndReached={() => {
setTimeout(() => {
onEnd();
}, 200);
}}
onError={() => {
console.log('error from partition magic');
}}
onReady={() => {
console.log('ready from partition magic');
setReadyToPlay(true);
}}
onPlay={onResume}
onPause={onPause}
// onEndReached={() => {
// setTimeout(() => {
// onEnd();
// }, 200);
// }}
// onError={() => {
// console.log('error from partition magic');
// }}
// onReady={() => {
// console.log('ready from partition magic');
// setReadyToPlay(true);
// }}
// onPlay={onResume}
// onPause={onPause}
/>
</View>
<PlayViewControlBar

View File

@@ -7771,6 +7771,11 @@ join-component@^1.1.0:
resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.1.0.tgz#b8417b750661a392bee2c2537c68b2a9d4977cd5"
integrity sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==
jotai@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.6.1.tgz#ece33a50b604e41b0134f94dd621e55d1bdc66f7"
integrity sha512-GLQtAnA9iEKRMXnyCjf1azIxfQi5JausX2EI5qSlb59j4i73ZEyV/EXPDEAQj4uQNZYEefi3degv/Pw3+L/Dtg==
js-sha3@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"