Compare commits
5 Commits
main
...
clem-fixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aae0bbea3a | ||
|
|
f5136ae59b | ||
|
|
f632ed42a3 | ||
|
|
00ee5cd531 | ||
|
|
1d496301d9 |
@@ -8,20 +8,17 @@ import { Audio } from 'expo-av';
|
||||
import { SvgContainer } from './SvgContainer';
|
||||
import LoadingComponent from '../Loading';
|
||||
import { SplendidGrandPiano } from 'smplr';
|
||||
import { atom, useAtom } from 'jotai';
|
||||
import { useStopwatch } from 'react-use-precision-timer';
|
||||
|
||||
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 = (
|
||||
@@ -37,30 +34,38 @@ const getCursorToPlay = (
|
||||
const cursorInfo = cursorInfos[i]!;
|
||||
if (cursorInfo.timestamp <= timestamp) {
|
||||
onCursorMove(cursorInfo, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const transitionDuration = 50;
|
||||
const startResumePauseWatch = (stopwatch: ReturnType<typeof useStopwatch>, shouldPlay: boolean) => {
|
||||
if (shouldPlay) {
|
||||
if (stopwatch.isPaused()) {
|
||||
stopwatch.resume();
|
||||
return;
|
||||
}
|
||||
stopwatch.start();
|
||||
return;
|
||||
}
|
||||
stopwatch.pause();
|
||||
};
|
||||
|
||||
const PartitionMagic = ({
|
||||
timestamp,
|
||||
songID,
|
||||
shouldPlay,
|
||||
onEndReached,
|
||||
onError,
|
||||
onReady,
|
||||
onPlay,
|
||||
onPause,
|
||||
}: ParitionMagicProps) => {
|
||||
const transitionDuration = 200;
|
||||
|
||||
const PartitionMagic = ({ songID }: ParitionMagicProps) => {
|
||||
const { data, isLoading, isError } = useQuery(API.getSongCursorInfos(songID));
|
||||
const currentCurIdx = React.useRef(-1);
|
||||
const stopwatch = useStopwatch();
|
||||
const [endPartitionReached, setEndPartitionReached] = React.useState(false);
|
||||
const [isPartitionSvgLoaded, setIsPartitionSvgLoaded] = React.useState(false);
|
||||
const partitionOffset = useSharedValue(0);
|
||||
const melodySound = React.useRef<Audio.Sound | null>(null);
|
||||
const piano = React.useRef<SplendidGrandPiano | null>(null);
|
||||
const [isPianoLoaded, setIsPianoLoaded] = React.useState(false);
|
||||
const [timestamp, setTimestamp] = useAtom(timestampAtom);
|
||||
const [shouldPlay, setShouldPlay] = useAtom(shouldPlayAtom);
|
||||
const [partitionState, setPartitionState] = useAtom(partitionStateAtom);
|
||||
const cursorPaddingVertical = 10;
|
||||
const cursorPaddingHorizontal = 3;
|
||||
|
||||
@@ -72,12 +77,36 @@ const PartitionMagic = ({
|
||||
const cursorTop = (data?.cursors[cursorDisplayIdx]?.y ?? 0) - cursorPaddingVertical;
|
||||
const cursorLeft = (data?.cursors[0]?.x ?? 0) - cursorPaddingHorizontal;
|
||||
|
||||
console.log('state', partitionState, timestamp);
|
||||
|
||||
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();
|
||||
piano.current?.stop();
|
||||
setPartitionState('ended');
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isError) {
|
||||
setPartitionState('error');
|
||||
}
|
||||
}, [isError]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isPartitionSvgLoaded && !isLoading && (melodySound.current?._loaded || isPianoLoaded)) {
|
||||
setPartitionState('ready');
|
||||
} else if (partitionState !== 'loading') {
|
||||
setPartitionState('loading');
|
||||
}
|
||||
|
||||
return () => {
|
||||
setPartitionState('loading');
|
||||
setTimestamp(0);
|
||||
setShouldPlay(false);
|
||||
};
|
||||
}, [isPartitionSvgLoaded, isLoading, melodySound.current?._loaded, isPianoLoaded]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (Platform.OS === 'web' && !piano.current) {
|
||||
const audio = new AudioContext();
|
||||
@@ -86,14 +115,9 @@ const PartitionMagic = ({
|
||||
setIsPianoLoaded(true);
|
||||
});
|
||||
} else if (!melodySound.current) {
|
||||
Audio.Sound.createAsync(
|
||||
{
|
||||
uri: API.getPartitionMelodyUrl(songID),
|
||||
},
|
||||
{
|
||||
progressUpdateIntervalMillis: 200,
|
||||
}
|
||||
).then((track) => {
|
||||
Audio.Sound.createAsync({
|
||||
uri: API.getPartitionMelodyUrl(songID),
|
||||
}).then((track) => {
|
||||
melodySound.current = track.sound;
|
||||
});
|
||||
}
|
||||
@@ -115,72 +139,38 @@ const PartitionMagic = ({
|
||||
}, [data]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (onError && isError) {
|
||||
onError('Error while loading partition');
|
||||
return;
|
||||
}
|
||||
}, [onError, isError]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isPartitionSvgLoaded && !isLoading && (melodySound.current?._loaded || isPianoLoaded)) {
|
||||
onReady();
|
||||
}
|
||||
}, [isPartitionSvgLoaded, isLoading, melodySound.current?._loaded, isPianoLoaded]);
|
||||
const interval = setInterval(
|
||||
() => {
|
||||
// if (partitionState !== 'playing') return;
|
||||
setTimestamp(stopwatch.getElapsedRunningTime());
|
||||
},
|
||||
Platform.OS === 'web' ? 200 : 500
|
||||
);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (Platform.OS === 'web') {
|
||||
if (!piano.current || !isPianoLoaded) {
|
||||
return;
|
||||
}
|
||||
shouldPlay ? onPlay() : onPause();
|
||||
return;
|
||||
}
|
||||
if (!melodySound.current || !melodySound.current._loaded) {
|
||||
if (!piano.current || !isPianoLoaded) return;
|
||||
setPartitionState(shouldPlay ? 'playing' : 'paused');
|
||||
startResumePauseWatch(stopwatch, shouldPlay);
|
||||
return;
|
||||
}
|
||||
if (!melodySound.current || !melodySound.current._loaded) return;
|
||||
setPartitionState(shouldPlay ? 'playing' : 'paused');
|
||||
startResumePauseWatch(stopwatch, shouldPlay);
|
||||
if (shouldPlay) {
|
||||
melodySound.current.playAsync().then(onPlay).catch(console.error);
|
||||
melodySound.current.playAsync().catch(console.error);
|
||||
} else {
|
||||
melodySound.current.pauseAsync().then(onPause).catch(console.error);
|
||||
melodySound.current.pauseAsync().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;
|
||||
|
||||
melodySound.current.setOnPlaybackStatusUpdate((status) => {
|
||||
//@ts-expect-error positionMillis is not in the type
|
||||
const timestamp = status?.positionMillis ?? 0;
|
||||
getCursorToPlay(
|
||||
data!.cursors,
|
||||
currentCurIdx.current,
|
||||
timestamp + transitionDuration,
|
||||
(cursor, idx) => {
|
||||
currentCurIdx.current = idx;
|
||||
partitionOffset.value = withTiming(
|
||||
-(cursor.x - data!.cursors[0]!.x) / partitionDims[0],
|
||||
{
|
||||
duration: transitionDuration,
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}, [data?.cursors, melodySound.current?._loaded]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!shouldPlay) return;
|
||||
if (!piano.current || !isPianoLoaded) return;
|
||||
if (!melodySound.current || !melodySound.current._loaded) return;
|
||||
if (!data || data?.cursors.length === 0) return;
|
||||
getCursorToPlay(
|
||||
data!.cursors,
|
||||
@@ -195,6 +185,7 @@ const PartitionMagic = ({
|
||||
easing: Easing.inOut(Easing.ease),
|
||||
}
|
||||
);
|
||||
if (!piano.current || !isPianoLoaded) return;
|
||||
cursor.notes.forEach((note) => {
|
||||
piano.current?.start({
|
||||
note: note.note,
|
||||
@@ -249,7 +240,7 @@ const PartitionMagic = ({
|
||||
}}
|
||||
>
|
||||
<SvgContainer
|
||||
url={getSVGURL(songID)}
|
||||
url={API.getPartitionSvgUrl(songID)}
|
||||
onReady={() => {
|
||||
setIsPartitionSvgLoaded(true);
|
||||
}}
|
||||
@@ -277,11 +268,4 @@ const PartitionMagic = ({
|
||||
);
|
||||
};
|
||||
|
||||
PartitionMagic.defaultProps = {
|
||||
onError: () => {},
|
||||
onReady: () => {},
|
||||
onPlay: () => {},
|
||||
onPause: () => {},
|
||||
};
|
||||
|
||||
export default PartitionMagic;
|
||||
|
||||
31
front/components/Play/PartitionTimestampText.tsx
Normal file
31
front/components/Play/PartitionTimestampText.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
31
front/components/Play/PlayEndModal.tsx
Normal file
31
front/components/Play/PlayEndModal.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import PopupCC from '../UI/PopupCC';
|
||||
import { shouldEndAtom } from './PlayViewControlBar';
|
||||
import { partitionStateAtom } from './PartitionMagic';
|
||||
import ScoreModal from '../ScoreModal';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
export const PlayEndModal = () => {
|
||||
const [shouldEnd] = useAtom(shouldEndAtom);
|
||||
const [partitionState] = useAtom(partitionStateAtom);
|
||||
|
||||
const isEnd = shouldEnd || partitionState === 'ended';
|
||||
return (
|
||||
<PopupCC isVisible={isEnd}>
|
||||
<ScoreModal
|
||||
songId={0}
|
||||
overallScore={0}
|
||||
precision={0}
|
||||
score={{
|
||||
max_score: 0,
|
||||
missed: 0,
|
||||
wrong: 0,
|
||||
good: 0,
|
||||
great: 0,
|
||||
perfect: 0,
|
||||
current_streak: 0,
|
||||
max_streak: 0,
|
||||
}}
|
||||
/>
|
||||
</PopupCC>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
import { View } from 'react-native';
|
||||
import * as React from 'react';
|
||||
import { Row, Image, Text, useBreakpointValue, IconButton } from 'native-base';
|
||||
import { Row, Image, Text, useBreakpointValue, IconButton, Button } from 'native-base';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
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 isPlaying = partitionState === 'playing';
|
||||
const isPartitionLoading = partitionState === 'loading';
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
setShouldPlay(false);
|
||||
setShouldEnd(false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Row
|
||||
style={{
|
||||
@@ -107,17 +109,20 @@ const PlayViewControlBar = ({
|
||||
gap: isPhone ? 10 : 25,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="solid"
|
||||
disabled={disabled}
|
||||
_icon={{
|
||||
as: Ionicons,
|
||||
color: colors.coolGray[900],
|
||||
name: paused ? 'play' : 'pause',
|
||||
}}
|
||||
onPress={paused ? onResume : onPause}
|
||||
/>
|
||||
{isPartitionLoading ? (
|
||||
<Button isLoading size="sm" variant="solid" color={colors.coolGray[900]} />
|
||||
) : (
|
||||
<IconButton
|
||||
size="sm"
|
||||
variant="solid"
|
||||
_icon={{
|
||||
as: Ionicons,
|
||||
color: colors.coolGray[900],
|
||||
name: isPlaying ? 'pause' : 'play',
|
||||
}}
|
||||
onPress={() => setShouldPlay(!isPlaying)}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
size="sm"
|
||||
colorScheme="coolGray"
|
||||
@@ -126,22 +131,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={{
|
||||
@@ -162,17 +155,10 @@ const PlayViewControlBar = ({
|
||||
minWidth: 120,
|
||||
}}
|
||||
>
|
||||
<MetronomeControls paused={paused} bpm={bpm.current} />
|
||||
<MetronomeControls paused={isPlaying} bpm={bpm.current} />
|
||||
</View>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
PlayViewControlBar.defaultProps = {
|
||||
onResume: () => {},
|
||||
onPause: () => {},
|
||||
onEnd: () => {},
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
export default PlayViewControlBar;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Clock, Cup } from 'iconsax-react-native';
|
||||
import PlayViewControlBar from '../components/Play/PlayViewControlBar';
|
||||
import ScoreModal from '../components/ScoreModal';
|
||||
import { PlayScore, ScoreMessage } from '../components/Play/PlayScore';
|
||||
import { PlayEndModal } from '../components/Play/PlayEndModal';
|
||||
|
||||
type PlayViewProps = {
|
||||
songId: number;
|
||||
@@ -63,22 +64,22 @@ const PlayView = ({ songId }: PlayViewProps) => {
|
||||
const isPhone = screenSize === 'small';
|
||||
const song = useQuery(API.getSong(songId, ['artist']), { staleTime: Infinity });
|
||||
const toast = useToast();
|
||||
const [lastScoreMessage, setLastScoreMessage] = useState<ScoreMessage>();
|
||||
// const [lastScoreMessage, setLastScoreMessage] = useState<ScoreMessage>();
|
||||
const webSocket = useRef<WebSocket>();
|
||||
const [paused, setPause] = useState<boolean>(true);
|
||||
const stopwatch = useStopwatch();
|
||||
const [time, setTime] = useState(0);
|
||||
// const [paused, setPause] = useState<boolean>(true);
|
||||
// const stopwatch = useStopwatch();
|
||||
// const [time, setTime] = useState(0);
|
||||
const [endResult, setEndResult] = useState<unknown>();
|
||||
const [shouldPlay, setShouldPlay] = useState(false);
|
||||
// const [shouldPlay, setShouldPlay] = useState(false);
|
||||
const songHistory = useQuery(API.getSongHistory(songId));
|
||||
const [score, setScore] = useState(0); // Between 0 and 100
|
||||
const getElapsedTime = () => stopwatch.getElapsedRunningTime();
|
||||
const [readyToPlay, setReadyToPlay] = useState(false);
|
||||
// const [score, setScore] = useState(0); // Between 0 and 100
|
||||
// const getElapsedTime = () => stopwatch.getElapsedRunningTime();
|
||||
// const [readyToPlay, setReadyToPlay] = useState(false);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [midiKeyboardFound, setMidiKeyboardFound] = useState<boolean>();
|
||||
// first number is the note, the other is the time when pressed on release the key is removed
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [streak, setStreak] = useState(0);
|
||||
// const [streak, setStreak] = useState(0);
|
||||
const colorScheme = useColorScheme();
|
||||
const { colors } = useTheme();
|
||||
const statColor = colors.lightText;
|
||||
@@ -131,7 +132,7 @@ const PlayView = ({ songId }: PlayViewProps) => {
|
||||
console.log('MIDI inputs', inputs);
|
||||
let endMsgReceived = false; // Used to know if to go to error screen when websocket closes
|
||||
|
||||
if (inputs.size <= 0) {
|
||||
if (inputs.size <= 10) {
|
||||
toast.show({ description: 'No MIDI Keyboard found' });
|
||||
return;
|
||||
}
|
||||
@@ -238,14 +239,14 @@ const PlayView = ({ songId }: PlayViewProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE).catch(() => {});
|
||||
const interval = setInterval(() => {
|
||||
setTime(() => getElapsedTime()); // Countdown
|
||||
}, 200);
|
||||
// const interval = setInterval(() => {
|
||||
// setTime(() => getElapsedTime()); // Countdown
|
||||
// }, 200);
|
||||
|
||||
return () => {
|
||||
ScreenOrientation.unlockAsync().catch(() => {});
|
||||
stopwatch.stop();
|
||||
clearInterval(interval);
|
||||
// stopwatch.stop();
|
||||
// clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -290,12 +291,13 @@ const PlayView = ({ songId }: PlayViewProps) => {
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<PopupCC isVisible={endResult != undefined}>
|
||||
<PlayEndModal />
|
||||
{/* <PopupCC isVisible={endResult != undefined}>
|
||||
{
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(() => (endResult ? <ScoreModal {...(endResult as any)} /> : <></>))()
|
||||
endResult ? <ScoreModal {...(endResult as any)} /> : <></>
|
||||
}
|
||||
</PopupCC>
|
||||
</PopupCC> */}
|
||||
<PopupCC
|
||||
title={translate('selectPlayMode')}
|
||||
description={translate('selectPlayModeExplaination')}
|
||||
@@ -374,7 +376,7 @@ const PlayView = ({ songId }: PlayViewProps) => {
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<PlayScore score={score} streak={streak} message={lastScoreMessage} />
|
||||
<PlayScore />
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
@@ -386,38 +388,36 @@ 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
|
||||
score={score}
|
||||
time={time}
|
||||
paused={paused}
|
||||
disabled={playType == null || !readyToPlay}
|
||||
// score={score}
|
||||
// time={time}
|
||||
// paused={paused}
|
||||
// disabled={playType == null || !readyToPlay}
|
||||
song={song.data}
|
||||
onEnd={onEnd}
|
||||
onPause={() => {
|
||||
setShouldPlay(false);
|
||||
}}
|
||||
onResume={() => {
|
||||
setShouldPlay(true);
|
||||
}}
|
||||
// onEnd={onEnd}
|
||||
// onPause={() => {
|
||||
// setShouldPlay(false);
|
||||
// }}
|
||||
// onResume={() => {
|
||||
// setShouldPlay(true);
|
||||
// }}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
{colorScheme === 'dark' && (
|
||||
|
||||
@@ -34,7 +34,12 @@ const ProfileView = () => {
|
||||
const isBigScreen = layout.width > 650;
|
||||
|
||||
return (
|
||||
<Flex flex={1}>
|
||||
<Flex
|
||||
flex={1}
|
||||
style={{
|
||||
padding: 8,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
@@ -68,9 +73,15 @@ const ProfileView = () => {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 5,
|
||||
}}
|
||||
>
|
||||
<Text fontSize={'xl'} style={{ paddingRight: 'auto' }}>
|
||||
<Text
|
||||
fontSize={'xl'}
|
||||
isTruncated
|
||||
numberOfLines={2}
|
||||
style={{ flexShrink: 1 }}
|
||||
>
|
||||
{userQuery.data.name}
|
||||
</Text>
|
||||
<ButtonBase
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user