15 Commits

Author SHA1 Message Date
mathysPaul 1d45a6b934 [IMP] InteractiveCC: split in InteractiveBase & AnimatedBase 2024-01-09 17:18:14 +01:00
mathysPaul 13050e52f9 [IMP] lint, prettier, tsc 2024-01-08 01:27:39 +01:00
mathysPaul 5ef3885f72 [FIX] MusicList, MusicItem, IconButton: Prevent double-add on consecutive likes
Fixes the issue where consecutive likes on a track mistakenly
added it twice to the liked list. Now ensures correct toggling
between like and unlike.
2024-01-08 01:27:39 +01:00
mathysPaul a103666caf [REF] MusicView.tsx: Refactor & create MusicListCC 2024-01-08 01:27:39 +01:00
mathysPaul 29da5c2788 [ADD] Music view with Favorite, Last played and suggestion tabs 2024-01-08 01:27:39 +01:00
Arthur Jamet 1880b89b0c CI: Trigger Job if their source file has changes 2024-01-07 10:34:56 +01:00
Arthur Jamet e769ff1f13 CI: Attempt to fix Action's trigger 2024-01-07 10:34:56 +01:00
Clément Le Bihan 9e7873cdd7 Merge pull request #352 from Chroma-Case/feat/adc/search-history-V2
Feat/adc/search history v2
2024-01-04 22:24:34 +01:00
Clément Le Bihan f46c2cfb4a fix ci 2024-01-04 22:22:56 +01:00
Clément Le Bihan 9f14061efd Now using european date format 2024-01-04 22:21:11 +01:00
danis 851ee7420f fix(../V2/SearchHistory): fixed hard coded color + lightmode thing 2024-01-04 19:31:36 +01:00
danis ef57eb752d fix(../V2/SearchHistory): fixed background width of history type prop 2024-01-04 18:41:44 +01:00
danis fcb29ae484 Merge branch 'main' into feat/adc/search-history-V2 2024-01-02 20:52:57 +01:00
danis 5c4847ae2c style(searchBarV2): fixed coding style eslint error 2024-01-02 20:24:48 +01:00
danis 3353a17611 feat(search histo v2): created search history component + historyRow + fetching da things 2023-12-06 22:41:02 +01:00
107 changed files with 597 additions and 225 deletions
+13 -10
View File
@@ -12,27 +12,30 @@ jobs:
pull-requests: read
# Set job outputs to values from filter step
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
scoro: ${{ steps.filter.outputs.scoro }}
back: ${{ steps.filter.outputs.back }}
front: ${{ steps.filter.outputs.front }}
scorometer: ${{ steps.filter.outputs.scorometer }}
steps:
# For pull requests it's not necessary to checkout the code
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend/**'
scoro:
back:
- 'back/**'
- '.github/workflows/back.yml'
front:
- 'front/**'
- '.github/workflows/front.yml'
scorometer:
- 'scorometer/**'
- '.github/workflows/scoro.yml'
back_build:
runs-on: ubuntu-latest
timeout-minutes: 10
needs: changes
if: ${{ needs.changes.outputs.backend == 'true' }}
if: ${{ needs.changes.outputs.back == 'true' }}
defaults:
run:
working-directory: ./back
@@ -47,7 +50,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [ back_build ]
if: ${{ needs.changes.outputs.frontend == 'true' }}
if: ${{ needs.changes.outputs.back == 'true' }}
steps:
- uses: actions/checkout@v3
+13 -10
View File
@@ -12,28 +12,31 @@ jobs:
pull-requests: read
# Set job outputs to values from filter step
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
scoro: ${{ steps.filter.outputs.scoro }}
back: ${{ steps.filter.outputs.back }}
front: ${{ steps.filter.outputs.front }}
scorometer: ${{ steps.filter.outputs.scorometer }}
steps:
# For pull requests it's not necessary to checkout the code
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend/**'
scoro:
back:
- 'back/**'
- '.github/workflows/back.yml'
front:
- 'front/**'
- '.github/workflows/front.yml'
scorometer:
- 'scorometer/**'
- '.github/workflows/scoro.yml'
front_check:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./front
needs: changes
if: ${{ needs.changes.outputs.frontend == 'true' }}
if: ${{ needs.changes.outputs.front == 'true' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
@@ -54,7 +57,7 @@ jobs:
defaults:
run:
working-directory: ./front
if: ${{ needs.changes.outputs.frontend == 'true' }}
if: ${{ needs.changes.outputs.front == 'true' }}
needs: [ front_check ]
steps:
- uses: actions/checkout@v3
+12 -9
View File
@@ -11,25 +11,28 @@ jobs:
pull-requests: read
# Set job outputs to values from filter step
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
scoro: ${{ steps.filter.outputs.scoro }}
back: ${{ steps.filter.outputs.back }}
front: ${{ steps.filter.outputs.front }}
scorometer: ${{ steps.filter.outputs.scorometer }}
steps:
# For pull requests it's not necessary to checkout the code
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
backend:
- 'backend/**'
frontend:
- 'frontend/**'
scoro:
back:
- 'back/**'
- '.github/workflows/back.yml'
front:
- 'front/**'
- '.github/workflows/front.yml'
scorometer:
- 'scorometer/**'
- '.github/workflows/scoro.yml'
scoro_test:
runs-on: ubuntu-latest
needs: changes
if: ${{ needs.changes.outputs.scoro == 'true' }}
if: ${{ needs.changes.outputs.scorometer == 'true' }}
steps:
- uses: actions/checkout@v3
+8 -8
View File
@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from 'react';
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<null>(null);
const audio = useRef<Audio.Sound | null>(null);
const [enabled, setEnabled] = useState<boolean>(false);
const volume = useRef<number>(50);
@@ -15,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(() => {
@@ -28,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}>
+16 -42
View File
@@ -5,12 +5,9 @@ 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 = {
@@ -54,7 +51,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, Sound> | null>(null);
const pianoSounds = React.useRef<Record<string, Audio.Sound> | null>(null);
const cursorPaddingVertical = 10;
const cursorPaddingHorizontal = 3;
@@ -75,40 +72,21 @@ 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)
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);
}
});
});
})
Object.entries(PianoNotes).map(([midiNumber, noteResource]) =>
Audio.Sound.createAsync(noteResource, {
volume: 1,
progressUpdateIntervalMillis: 100,
}).then((sound) => [midiNumber, sound.sound] as const)
)
).then((res) => {
// 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];
});
pianoSounds.current = res.reduce(
(prev, curr) => ({ ...prev, [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]);
@@ -144,16 +122,12 @@ const PartitionMagic = ({
cursor.notes.forEach(({ note, duration }) => {
try {
const sound = pianoSounds.current![note]!;
sound.play((success) => {
if (!success) {
console.log('Sound did not play');
}
});
sound.playAsync().catch(console.error);
setTimeout(() => {
sound.stop();
sound.stopAsync();
}, duration - 10);
} catch (e) {
console.log('Error key: ', note, e);
console.log(e);
}
});
}
+128
View File
@@ -0,0 +1,128 @@
import React, { useRef, useEffect } from 'react';
import { Animated, StyleProp, StyleSheet, ViewStyle } from 'react-native';
type StyleObject = Record<string, any>;
type InterpolatedStyleObject = Record<string, Animated.AnimatedInterpolation<any>>;
interface AnimatedBaseProps {
style?: StyleObject;
defaultStyle: StyleObject;
hoverStyle: StyleObject;
pressStyle: StyleObject;
currentState: number;
duration?: number;
children?: React.ReactNode;
styleContainer?: StyleProp<ViewStyle>;
}
const AnimatedBase: React.FC<AnimatedBaseProps> = ({
style,
defaultStyle,
hoverStyle,
pressStyle,
currentState,
children,
duration = 250,
styleContainer,
}) => {
const animatedValues = useRef<Record<string, Animated.Value>>({}).current;
const extractTransformKeys = (styleObject: StyleObject) => {
return styleObject.transform
? styleObject.transform.map((t: any) => Object.keys(t)[0])
: [];
};
useEffect(() => {
const allStyleKeys = new Set([
...Object.keys(defaultStyle),
...Object.keys(hoverStyle),
...Object.keys(pressStyle),
]);
allStyleKeys.forEach((key) => {
if (!animatedValues[key]) {
animatedValues[key] = new Animated.Value(0);
console.log('key; ', key);
}
});
const allTransformKeys = new Set([
...extractTransformKeys(defaultStyle),
...extractTransformKeys(hoverStyle),
...extractTransformKeys(pressStyle),
]);
allTransformKeys.forEach((key) => {
if (!animatedValues[key]) {
animatedValues[key] = new Animated.Value(0);
console.log('keyxx; ', key);
}
});
}, [defaultStyle, hoverStyle, pressStyle]);
useEffect(() => {
animateToState(currentState);
}, [currentState]);
const getTransformValue = (key: string, style: StyleObject) => {
const transformObject = style.transform?.find((t: any) => t.hasOwnProperty(key));
return transformObject ? transformObject[key] : 0;
};
const interpolateStyle = (stateStyle: StyleObject): InterpolatedStyleObject => {
const interpolatedStyle: InterpolatedStyleObject = {};
const transform: any = [];
Object.keys(animatedValues).forEach((key) => {
if (stateStyle.transform?.some((t: any) => t.hasOwnProperty(key))) {
const defaultValue = getTransformValue(key, defaultStyle);
const hoverValue = getTransformValue(key, hoverStyle);
const pressValue = getTransformValue(key, pressStyle);
const interpolated = animatedValues[key]!.interpolate({
inputRange: [0, 1, 2],
outputRange: [defaultValue, hoverValue, pressValue],
});
transform.push({ [key]: interpolated });
} else if (stateStyle[key]) {
const defaultValue = defaultStyle[key] || 0;
const hoverValue = hoverStyle[key] !== undefined ? hoverStyle[key] : defaultValue;
const pressValue = pressStyle[key] !== undefined ? pressStyle[key] : defaultValue;
interpolatedStyle[key] = animatedValues[key]!.interpolate({
inputRange: [0, 1, 2],
outputRange: [defaultValue, hoverValue, pressValue],
});
}
});
if (transform.length > 0) {
interpolatedStyle.transform = transform;
}
return interpolatedStyle;
};
const animateToState = (stateValue: number) => {
Object.keys(animatedValues).forEach((key) => {
Animated.timing(animatedValues[key]!, {
toValue: stateValue,
duration: duration,
useNativeDriver: false,
}).start();
});
};
const animatedStyle = StyleSheet.flatten([
styleContainer,
interpolateStyle(defaultStyle),
interpolateStyle(hoverStyle),
interpolateStyle(pressStyle),
]);
return <Animated.View style={[style, animatedStyle]}>{children && children}</Animated.View>;
};
export default AnimatedBase;
+2 -2
View File
@@ -30,7 +30,7 @@ type IconButtonProps = {
/**
* Callback function triggered when the button is pressed.
*/
onPress?: () => void | Promise<void>;
onPress?: (state: boolean) => void | Promise<void>;
/**
* Size of the icon.
@@ -183,7 +183,7 @@ const IconButton: React.FC<IconButtonProps> = ({
const toggleState = async () => {
// Execute onPress if provided.
if (onPress) {
await onPress();
await onPress(!isActiveState);
}
// Toggle isActiveState.
+34
View File
@@ -0,0 +1,34 @@
import React from 'react';
import { Pressable } from 'react-native';
interface InteractiveBaseProps {
handleHoverIn: () => void;
handleHoverOut: () => void;
handlePressIn: () => void;
handlePressOut: () => void;
children: React.ReactNode;
style?: any;
}
const InteractiveBase: React.FC<InteractiveBaseProps> = ({
handleHoverIn,
handleHoverOut,
handlePressIn,
handlePressOut,
children,
style,
}) => {
return (
<Pressable
style={style}
onHoverIn={handleHoverIn}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onHoverOut={handleHoverOut}
>
{children}
</Pressable>
);
};
export default InteractiveBase;
-13
View File
@@ -5,19 +5,6 @@ import { Animated, StyleSheet, Pressable, ViewStyle, StyleProp } from 'react-nat
type StyleObject = Record<string, any>;
type InterpolatedStyleObject = Record<string, Animated.AnimatedInterpolation<any>>;
const TRANSFORM_WHITELIST = {
translateX: true,
translateY: true,
scale: true,
scaleX: true,
scaleY: true,
rotate: true,
rotateX: true,
rotateY: true,
rotateZ: true,
perspective: true,
};
interface InteractiveCCProps {
defaultStyle: StyleObject;
hoverStyle: StyleObject;
+1 -1
View File
@@ -33,7 +33,7 @@ export interface MusicItemType {
style?: ViewStyle | ViewStyle[];
/** Callback function triggered when the like button is pressed. */
onLike: () => void;
onLike: (state: boolean) => void;
/** Callback function triggered when the song is played. */
onPlay: () => void;
-2
View File
@@ -237,8 +237,6 @@ const styles = StyleSheet.create({
// Using `memo` to optimize rendering performance by memorizing the component's output.
// This ensures that the component only re-renders when its props change.
const MusicList = memo(MusicListComponent, (prev, next) => {
console.log('AAAAA');
console.log(prev.initialMusics, next.initialMusics);
return prev.initialMusics.length == next.initialMusics.length;
});
@@ -0,0 +1,48 @@
import { useState, useCallback } from 'react';
const InteractionStates = {
NORMAL: 0,
HOVER: 1,
PRESSED: 2,
};
interface InteractionStateProps {
onHoverIn?: () => void;
onHoverOut?: () => void;
onPressIn?: () => void;
onPressOut?: () => void;
}
const useInteractionState = ({ onHoverIn, onHoverOut, onPressIn, onPressOut }: InteractionStateProps = {}) => {
const [state, setState] = useState(InteractionStates.NORMAL);
const handleHoverIn = useCallback(() => {
setState(InteractionStates.HOVER);
if (onHoverIn) onHoverIn();
}, [onHoverIn]);
const handleHoverOut = useCallback(() => {
setState(InteractionStates.NORMAL);
if (onHoverOut) onHoverOut();
}, [onHoverOut]);
const handlePressIn = useCallback(() => {
setState(InteractionStates.PRESSED);
if (onPressIn) onPressIn();
}, [onPressIn]);
const handlePressOut = useCallback(() => {
setState(InteractionStates.HOVER);
if (onPressOut) onPressOut();
}, [onPressOut]);
return {
state,
handleHoverIn,
handleHoverOut,
handlePressIn,
handlePressOut,
};
};
export default useInteractionState;

Some files were not shown because too many files have changed in this diff Show More