Compare commits
28 Commits
v0.8.4
...
sound-expe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0db8d49618 | ||
|
|
4923fc72b2 | ||
|
|
60a73781bd | ||
|
|
4e3b378d6a | ||
|
|
2bf1e783a9 | ||
|
|
375d36f6c5 | ||
|
|
495380ec43 | ||
|
|
af0531bb0c | ||
|
|
c5124fa6ad | ||
|
|
962cf58e77 | ||
|
|
60988dd599 | ||
|
|
004a541302 | ||
|
|
f4cd9e18ea | ||
|
|
2dc301addf | ||
|
|
e85a959c26 | ||
|
|
339e808d27 | ||
|
|
22d1a97abd | ||
|
|
ce4baa61dc | ||
|
|
e90c7f05a8 | ||
|
|
fb0e43af88 | ||
|
|
4577997b1c | ||
|
|
9bb256f2ee | ||
|
|
d3994ff26e | ||
|
|
00d097f643 | ||
|
|
99da77f23e | ||
|
|
7a6dc8b0c9 | ||
|
|
b4f04f9b71 | ||
|
|
9df0c98100 |
BIN
front/assets/google.png
Normal file
BIN
front/assets/google.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
40
front/components/APKDownloadButton.tsx
Normal file
40
front/components/APKDownloadButton.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ArrowCircleDown2 } from 'iconsax-react-native';
|
||||
import ButtonBase from './UI/ButtonBase';
|
||||
import { translate } from '../i18n/i18n';
|
||||
import { Linking } from 'react-native';
|
||||
import { useState } from 'react';
|
||||
import PopupCC from './UI/PopupCC';
|
||||
|
||||
const APKDownloadButton = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonBase
|
||||
style={{}}
|
||||
icon={ArrowCircleDown2}
|
||||
type={'filled'}
|
||||
title={translate('downloadAPK')}
|
||||
onPress={() => setIsOpen(true)}
|
||||
/>
|
||||
<PopupCC
|
||||
title={translate('downloadAPK')}
|
||||
description={translate('downloadAPKInstructions')}
|
||||
isVisible={isOpen}
|
||||
setIsVisible={setIsOpen}
|
||||
>
|
||||
<ButtonBase
|
||||
style={{}}
|
||||
icon={ArrowCircleDown2}
|
||||
type={'filled'}
|
||||
title={translate('downloadAPK')}
|
||||
onPress={() =>
|
||||
Linking.openURL('https://github.com/Chroma-Case/Chromacase/releases')
|
||||
}
|
||||
/>
|
||||
</PopupCC>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default APKDownloadButton;
|
||||
@@ -3,7 +3,7 @@ import Card, { CardBorderRadius } from './Card';
|
||||
import { VStack, Text, Image } from 'native-base';
|
||||
|
||||
type ArtistCardProps = {
|
||||
image: string;
|
||||
image?: string;
|
||||
name: string;
|
||||
id: number;
|
||||
onPress: () => void;
|
||||
@@ -18,6 +18,7 @@ const ArtistCard = (props: ArtistCardProps) => {
|
||||
<Image
|
||||
style={{ zIndex: 0, aspectRatio: 1, borderRadius: CardBorderRadius }}
|
||||
source={{ uri: image }}
|
||||
fallbackSource={{ uri: require('../assets/icon.jpg') }}
|
||||
alt={name}
|
||||
/>
|
||||
<VStack>
|
||||
@@ -30,11 +31,4 @@ const ArtistCard = (props: ArtistCardProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
ArtistCard.defaultProps = {
|
||||
image: 'https://picsum.photos/200',
|
||||
name: 'Artist',
|
||||
id: 0,
|
||||
onPress: () => {},
|
||||
};
|
||||
|
||||
export default ArtistCard;
|
||||
|
||||
@@ -2,9 +2,9 @@ import { HStack, IconButton, Image, Text } from 'native-base';
|
||||
import RowCustom from './RowCustom';
|
||||
import TextButton from './TextButton';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import API from '../API';
|
||||
import DurationComponent from './DurationComponent';
|
||||
import Song from '../models/Song';
|
||||
import { useLikeSongMutation } from '../utils/likeSongMutation';
|
||||
|
||||
type FavSongRowProps = {
|
||||
song: Song;
|
||||
@@ -13,6 +13,8 @@ type FavSongRowProps = {
|
||||
};
|
||||
|
||||
const FavSongRow = ({ song, addedDate, onPress }: FavSongRowProps) => {
|
||||
const { mutate } = useLikeSongMutation();
|
||||
|
||||
return (
|
||||
<RowCustom width={'100%'}>
|
||||
<HStack px={2} space={5} justifyContent={'space-between'}>
|
||||
@@ -63,7 +65,7 @@ const FavSongRow = ({ song, addedDate, onPress }: FavSongRowProps) => {
|
||||
variant={'ghost'}
|
||||
borderRadius={'full'}
|
||||
onPress={() => {
|
||||
API.removeLikedSong(song.id);
|
||||
mutate({ songId: song.id, like: false });
|
||||
}}
|
||||
_icon={{
|
||||
as: MaterialIcons,
|
||||
|
||||
@@ -30,6 +30,7 @@ const GenreCard = (props: GenreCardProps) => {
|
||||
source={{
|
||||
uri: image,
|
||||
}}
|
||||
fallbackSource={{ uri: require('../assets/icon.jpg') }}
|
||||
size="md"
|
||||
/>
|
||||
</Box>
|
||||
@@ -43,10 +44,4 @@ const GenreCard = (props: GenreCardProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
GenreCard.defaultProps = {
|
||||
icon: 'https://picsum.photos/200',
|
||||
name: 'Genre',
|
||||
onPress: () => {},
|
||||
};
|
||||
|
||||
export default GenreCard;
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from './ElementTypes';
|
||||
import { ArrowDown2 } from 'iconsax-react-native';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
import Translate from '../Translate';
|
||||
|
||||
type RawElementProps = {
|
||||
element: ElementProps;
|
||||
@@ -149,7 +150,7 @@ export const RawElement = ({ element }: RawElementProps) => {
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <Text>Unknown type</Text>;
|
||||
return <Translate translationKey="unknownError" />;
|
||||
}
|
||||
})()}
|
||||
</Row>
|
||||
|
||||
135
front/components/Hoverable.ts
Normal file
135
front/components/Hoverable.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
// credit to https://gist.github.com/ianmartorell/32bb7df95e5eff0a5ee2b2f55095e6a6
|
||||
// this file was repurosed from there
|
||||
// via this issue https://gist.github.com/necolas/1c494e44e23eb7f8c5864a2fac66299a
|
||||
// because RNW's pressable doesn't bubble events to parent pressables: https://github.com/necolas/react-native-web/issues/1875
|
||||
|
||||
/* eslint-disable no-inner-declarations */
|
||||
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
|
||||
|
||||
let isEnabled = false;
|
||||
|
||||
if (canUseDOM) {
|
||||
/**
|
||||
* Web browsers emulate mouse events (and hover states) after touch events.
|
||||
* This code infers when the currently-in-use modality supports hover
|
||||
* (including for multi-modality devices) and considers "hover" to be enabled
|
||||
* if a mouse movement occurs more than 1 second after the last touch event.
|
||||
* This threshold is long enough to account for longer delays between the
|
||||
* browser firing touch and mouse events on low-powered devices.
|
||||
*/
|
||||
const HOVER_THRESHOLD_MS = 1000;
|
||||
let lastTouchTimestamp = 0;
|
||||
|
||||
function enableHover() {
|
||||
if (isEnabled || Date.now() - lastTouchTimestamp < HOVER_THRESHOLD_MS) {
|
||||
return;
|
||||
}
|
||||
isEnabled = true;
|
||||
}
|
||||
|
||||
function disableHover() {
|
||||
lastTouchTimestamp = Date.now();
|
||||
if (isEnabled) {
|
||||
isEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('touchstart', disableHover, true);
|
||||
document.addEventListener('touchmove', disableHover, true);
|
||||
document.addEventListener('mousemove', enableHover, true);
|
||||
}
|
||||
|
||||
function isHoverEnabled(): boolean {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
import React, { useCallback, ReactChild, useRef } from 'react';
|
||||
import { useSharedValue, useAnimatedReaction } from 'react-native-reanimated';
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
export interface HoverableProps {
|
||||
onHoverIn?: () => void;
|
||||
onHoverOut?: () => void;
|
||||
onPressIn?: () => void;
|
||||
onPressOut?: () => void;
|
||||
children: NonNullable<ReactChild>;
|
||||
}
|
||||
|
||||
export default function Hoverable({
|
||||
onHoverIn,
|
||||
onHoverOut,
|
||||
children,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
}: HoverableProps) {
|
||||
const showHover = useSharedValue(true);
|
||||
const isHovered = useSharedValue(false);
|
||||
|
||||
const hoverIn = useRef<undefined | (() => void)>(() => onHoverIn?.());
|
||||
const hoverOut = useRef<undefined | (() => void)>(() => onHoverOut?.());
|
||||
const pressIn = useRef<undefined | (() => void)>(() => onPressIn?.());
|
||||
const pressOut = useRef<undefined | (() => void)>(() => onPressOut?.());
|
||||
|
||||
hoverIn.current = onHoverIn;
|
||||
hoverOut.current = onHoverOut;
|
||||
pressIn.current = onPressIn;
|
||||
pressOut.current = onPressOut;
|
||||
|
||||
useAnimatedReaction(
|
||||
() => {
|
||||
return Platform.OS === 'web' && showHover.value && isHovered.value;
|
||||
},
|
||||
(hovered, previouslyHovered) => {
|
||||
if (hovered !== previouslyHovered) {
|
||||
if (hovered && hoverIn.current) {
|
||||
// no need for runOnJS, it's always web
|
||||
hoverIn.current();
|
||||
} else if (hoverOut.current) {
|
||||
hoverOut.current();
|
||||
}
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
if (isHoverEnabled() && !isHovered.value) {
|
||||
isHovered.value = true;
|
||||
}
|
||||
}, [isHovered]);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
if (isHovered.value) {
|
||||
isHovered.value = false;
|
||||
}
|
||||
}, [isHovered]);
|
||||
|
||||
const handleGrant = useCallback(() => {
|
||||
showHover.value = false;
|
||||
pressIn.current?.();
|
||||
}, [showHover]);
|
||||
|
||||
const handleRelease = useCallback(() => {
|
||||
showHover.value = true;
|
||||
pressOut.current?.();
|
||||
}, [showHover]);
|
||||
|
||||
let webProps = {};
|
||||
if (Platform.OS === 'web') {
|
||||
webProps = {
|
||||
onMouseEnter: handleMouseEnter,
|
||||
onMouseLeave: handleMouseLeave,
|
||||
// prevent hover showing while responder
|
||||
onResponderGrant: handleGrant,
|
||||
onResponderRelease: handleRelease,
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return React.cloneElement(React.Children.only(children) as any, {
|
||||
...webProps,
|
||||
// if child is Touchable
|
||||
onPressIn: handleGrant,
|
||||
onPressOut: handleRelease,
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Slider, Text, View, IconButton, Icon } from 'native-base';
|
||||
import { Slider, View, IconButton, Icon } from 'native-base';
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||
import { Audio } from 'expo-av';
|
||||
// import { Audio } from 'expo-av';
|
||||
import { VolumeHigh, VolumeSlash } from 'iconsax-react-native';
|
||||
import { Translate } from '../i18n/i18n';
|
||||
|
||||
export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; bpm: number }) => {
|
||||
const audio = useRef<Audio.Sound | null>(null);
|
||||
const audio = useRef<null>(null);
|
||||
const [enabled, setEnabled] = useState<boolean>(false);
|
||||
const volume = useRef<number>(50);
|
||||
|
||||
@@ -14,12 +15,12 @@ export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; b
|
||||
return;
|
||||
} else if (!audio.current) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
Audio.Sound.createAsync(require('../assets/metronome.mp3')).then((a) => {
|
||||
audio.current = a.sound;
|
||||
});
|
||||
// Audio.Sound.createAsync(require('../assets/metronome.mp3')).then((a) => {
|
||||
// audio.current = a.sound;
|
||||
// });
|
||||
}
|
||||
return () => {
|
||||
audio.current?.unloadAsync();
|
||||
// audio.current?.unloadAsync();
|
||||
};
|
||||
}, [enabled]);
|
||||
useEffect(() => {
|
||||
@@ -27,12 +28,12 @@ export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; b
|
||||
const int = setInterval(() => {
|
||||
if (!enabled) return;
|
||||
if (!audio.current) return;
|
||||
audio.current?.playAsync();
|
||||
// audio.current?.playAsync();
|
||||
}, 60000 / bpm);
|
||||
return () => clearInterval(int);
|
||||
}, [bpm, paused]);
|
||||
useEffect(() => {
|
||||
audio.current?.setVolumeAsync(volume.current / 100);
|
||||
// audio.current?.setVolumeAsync(volume.current / 100);
|
||||
}, [volume.current]);
|
||||
return (
|
||||
<View flex={1}>
|
||||
@@ -43,7 +44,7 @@ export const MetronomeControls = ({ paused = false, bpm }: { paused?: boolean; b
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Text>Metronome</Text>
|
||||
<Translate translationKey="metronome" />
|
||||
<Icon as={<MaterialCommunityIcons name="metronome" size={24} color="white" />} />
|
||||
</View>
|
||||
<View
|
||||
|
||||
@@ -5,9 +5,12 @@ import { useQuery } from '../../Queries';
|
||||
import Animated, { useSharedValue, withTiming, Easing } from 'react-native-reanimated';
|
||||
import { CursorInfoItem } from '../../models/SongCursorInfos';
|
||||
import { PianoNotes } from '../../state/SoundPlayerSlice';
|
||||
import { Audio } from 'expo-av';
|
||||
// import { Audio } from 'expo-av';
|
||||
import { SvgContainer } from './SvgContainer';
|
||||
import LoadingComponent from '../Loading';
|
||||
import Sound from 'react-native-sound';
|
||||
|
||||
Sound.setCategory('Playback');
|
||||
|
||||
// note we are also using timestamp in a context
|
||||
export type ParitionMagicProps = {
|
||||
@@ -51,7 +54,7 @@ const PartitionMagic = ({
|
||||
const [endPartitionReached, setEndPartitionReached] = React.useState(false);
|
||||
const [isPartitionSvgLoaded, setIsPartitionSvgLoaded] = React.useState(false);
|
||||
const partitionOffset = useSharedValue(0);
|
||||
const pianoSounds = React.useRef<Record<string, Audio.Sound> | null>(null);
|
||||
const pianoSounds = React.useRef<Record<string, Sound> | null>(null);
|
||||
const cursorPaddingVertical = 10;
|
||||
const cursorPaddingHorizontal = 3;
|
||||
|
||||
@@ -72,21 +75,40 @@ const PartitionMagic = ({
|
||||
React.useEffect(() => {
|
||||
if (!pianoSounds.current) {
|
||||
Promise.all(
|
||||
Object.entries(PianoNotes).map(([midiNumber, noteResource]) =>
|
||||
Audio.Sound.createAsync(noteResource, {
|
||||
volume: 1,
|
||||
progressUpdateIntervalMillis: 100,
|
||||
}).then((sound) => [midiNumber, sound.sound] as const)
|
||||
)
|
||||
Object.entries(PianoNotes).map(([midiNumber, noteResource]) => {
|
||||
// Audio.Sound.createAsync(noteResource, {
|
||||
// volume: 1,
|
||||
// progressUpdateIntervalMillis: 100,
|
||||
// }).then((sound) => [midiNumber, sound.sound] as const)
|
||||
return new Promise((resolve, reject) => {
|
||||
const sound = new Sound(noteResource, Sound.MAIN_BUNDLE, (error: any) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve([midiNumber, sound] as const);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
).then((res) => {
|
||||
pianoSounds.current = res.reduce(
|
||||
(prev, curr) => ({ ...prev, [curr[0]]: curr[1] }),
|
||||
{}
|
||||
);
|
||||
// pianoSounds.current = res.reduce(
|
||||
// (prev, curr) => ({ ...prev, [curr[0]]: curr[1] }),
|
||||
// {}
|
||||
// );
|
||||
pianoSounds.current = {};
|
||||
(res as [string, Sound][]).forEach((curr) => {
|
||||
pianoSounds.current![curr[0]] = curr[1];
|
||||
});
|
||||
console.log('sound loaded');
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [
|
||||
() => {
|
||||
pianoSounds?.current?.forEach((sound) => {
|
||||
sound.release();
|
||||
});
|
||||
},
|
||||
]);
|
||||
const partitionDims = React.useMemo<[number, number]>(() => {
|
||||
return [data?.pageWidth ?? 0, data?.pageHeight ?? 1];
|
||||
}, [data]);
|
||||
@@ -122,12 +144,16 @@ const PartitionMagic = ({
|
||||
cursor.notes.forEach(({ note, duration }) => {
|
||||
try {
|
||||
const sound = pianoSounds.current![note]!;
|
||||
sound.playAsync().catch(console.error);
|
||||
sound.play((success) => {
|
||||
if (!success) {
|
||||
console.log('Sound did not play');
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
sound.stopAsync();
|
||||
sound.stop();
|
||||
}, duration - 10);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log('Error key: ', note, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,9 +89,11 @@ const PlayViewControlBar = ({
|
||||
<Text color={textColor[800]} fontSize={14} maxW={'100%'} isTruncated>
|
||||
{song.name}
|
||||
</Text>
|
||||
<Text color={textColor[900]} fontSize={12} maxW={'100%'} isTruncated>
|
||||
{song.artistId}
|
||||
</Text>
|
||||
{song.artist && (
|
||||
<Text color={textColor[900]} fontSize={12} maxW={'100%'} isTruncated>
|
||||
{song.artist?.name}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { translate } from '../i18n/i18n';
|
||||
import { Box, Text, VStack, Progress, Stack } from 'native-base';
|
||||
import { Translate } from '../i18n/i18n';
|
||||
import { Box, VStack, Progress, Stack } from 'native-base';
|
||||
import { useNavigation } from '../Navigation';
|
||||
import Card from '../components/Card';
|
||||
import UserAvatar from './UserAvatar';
|
||||
@@ -18,13 +18,14 @@ const ProgressBar = ({ xp }: { xp: number }) => {
|
||||
<Stack padding={4} space={2} direction="row" alignItems="center">
|
||||
<UserAvatar />
|
||||
<VStack alignItems={'center'} flexGrow={1} space={2}>
|
||||
<Text>{`${translate('level')} ${level}`}</Text>
|
||||
<Translate translationKey="level" format={(e) => `${e} ${level}`} />
|
||||
<Box w="100%">
|
||||
<Progress value={progessValue} mx="4" />
|
||||
</Box>
|
||||
<Text>
|
||||
{xp} / {nextLevelThreshold} {translate('levelProgress')}
|
||||
</Text>
|
||||
<Translate
|
||||
translationKey="levelProgress"
|
||||
format={(e) => `${xp} / ${nextLevelThreshold} ${e}`}
|
||||
/>
|
||||
</VStack>
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import {
|
||||
VStack,
|
||||
Heading,
|
||||
Text,
|
||||
Box,
|
||||
Card,
|
||||
Flex,
|
||||
@@ -13,7 +12,7 @@ import {
|
||||
import { SafeAreaView } from 'react-native';
|
||||
import { SearchContext } from '../views/SearchView';
|
||||
import { useQuery } from '../Queries';
|
||||
import { translate } from '../i18n/i18n';
|
||||
import { Translate, translate } from '../i18n/i18n';
|
||||
import API from '../API';
|
||||
import LoadingComponent, { LoadingView } from './Loading';
|
||||
import ArtistCard from './ArtistCard';
|
||||
@@ -25,12 +24,13 @@ import Song from '../models/Song';
|
||||
import { useNavigation } from '../Navigation';
|
||||
import SongRow from '../components/SongRow';
|
||||
import FavSongRow from './FavSongRow';
|
||||
import { useLikeSongMutation } from '../utils/likeSongMutation';
|
||||
|
||||
const swaToSongCardProps = (song: Song) => ({
|
||||
songId: song.id,
|
||||
name: song.name,
|
||||
artistName: song.artist!.name,
|
||||
cover: song.cover ?? 'https://picsum.photos/200',
|
||||
cover: song.cover,
|
||||
});
|
||||
|
||||
const HomeSearchComponent = () => {
|
||||
@@ -84,18 +84,12 @@ type SongsSearchComponentProps = {
|
||||
const SongsSearchComponent = (props: SongsSearchComponentProps) => {
|
||||
const navigation = useNavigation();
|
||||
const { songData } = React.useContext(SearchContext);
|
||||
const favoritesQuery = useQuery(API.getLikedSongs());
|
||||
|
||||
const handleFavoriteButton = async (state: boolean, songId: number): Promise<void> => {
|
||||
if (state == false) await API.removeLikedSong(songId);
|
||||
else await API.addLikedSong(songId);
|
||||
};
|
||||
const favoritesQuery = useQuery(API.getLikedSongs(['artist']));
|
||||
const { mutate } = useLikeSongMutation();
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
||||
{translate('songsFilter')}
|
||||
</Text>
|
||||
<Translate translationKey="songsFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
||||
<Box>
|
||||
{songData?.length ? (
|
||||
songData.slice(0, props.maxRows).map((comp, index) => (
|
||||
@@ -105,8 +99,8 @@ const SongsSearchComponent = (props: SongsSearchComponentProps) => {
|
||||
isLiked={
|
||||
!favoritesQuery.data?.find((query) => query?.songId == comp.id)
|
||||
}
|
||||
handleLike={(state: boolean, songId: number) =>
|
||||
handleFavoriteButton(state, songId)
|
||||
handleLike={async (state: boolean, songId: number) =>
|
||||
mutate({ songId: songId, like: state })
|
||||
}
|
||||
onPress={() => {
|
||||
API.createSearchHistoryEntry(comp.name, 'song');
|
||||
@@ -115,7 +109,7 @@ const SongsSearchComponent = (props: SongsSearchComponentProps) => {
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Text>{translate('errNoResults')}</Text>
|
||||
<Translate translationKey="errNoResults" />
|
||||
)}
|
||||
</Box>
|
||||
</ScrollView>
|
||||
@@ -132,9 +126,7 @@ const ArtistSearchComponent = (props: ItemSearchComponentProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
||||
{translate('artistFilter')}
|
||||
</Text>
|
||||
<Translate translationKey="artistFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
||||
{artistData?.length ? (
|
||||
<CardGridCustom
|
||||
content={artistData
|
||||
@@ -151,7 +143,7 @@ const ArtistSearchComponent = (props: ItemSearchComponentProps) => {
|
||||
cardComponent={ArtistCard}
|
||||
/>
|
||||
) : (
|
||||
<Text>{translate('errNoResults')}</Text>
|
||||
<Translate translationKey="errNoResults" />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
@@ -163,9 +155,7 @@ const GenreSearchComponent = (props: ItemSearchComponentProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
||||
{translate('genreFilter')}
|
||||
</Text>
|
||||
<Translate translationKey="genreFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
||||
{genreData?.length ? (
|
||||
<CardGridCustom
|
||||
content={genreData.slice(0, props.maxItems ?? genreData.length).map((g) => ({
|
||||
@@ -180,7 +170,7 @@ const GenreSearchComponent = (props: ItemSearchComponentProps) => {
|
||||
cardComponent={GenreCard}
|
||||
/>
|
||||
) : (
|
||||
<Text>{translate('errNoResults')}</Text>
|
||||
<Translate translationKey="errNoResults" />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
@@ -200,9 +190,7 @@ const FavoritesComponent = () => {
|
||||
|
||||
return (
|
||||
<ScrollView>
|
||||
<Text fontSize="xl" fontWeight="bold" mt={4}>
|
||||
{translate('songsFilter')}
|
||||
</Text>
|
||||
<Translate translationKey="songsFilter" fontSize="xl" fontWeight="bold" mt={4} />
|
||||
<Box>
|
||||
{favoritesQuery.data?.map((songData) => (
|
||||
<FavSongRow
|
||||
@@ -268,7 +256,9 @@ const FilterSwitch = () => {
|
||||
case 'favorites':
|
||||
return <FavoritesComponent />;
|
||||
default:
|
||||
return <Text>Something very bad happened: {currentFilter}</Text>;
|
||||
return (
|
||||
<Translate translationKey="unknownError" format={(e) => `${e}: ${currentFilter}`} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user