Partitionvisual (#122)
* first tries with transition entry from native base * setup moti for mobile and web use * added Easing linear for consistant scroll scpeed * pause works ! * added startAt, rewind and fast forward * created temporary view for partition viewer * Cleanup for PR * fix little bug in rewind
This commit is contained in:
18
front/API.ts
18
front/API.ts
@@ -231,4 +231,22 @@ export default class API {
|
||||
userId: 1
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a partition images
|
||||
* @param songId the id of the song
|
||||
* This API may be merged with the fetch song in the future
|
||||
*/
|
||||
public static async getPartitionRessources(songId: number): Promise<[string, number, number][]> {
|
||||
return [
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469560426545222/vivaldi_split_1.png", 1868, 400],
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469560900505660/vivaldi_split_2.png", 1868, 400],
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469561261203506/vivaldi_split_3.png", 1868, 400],
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469561546424381/vivaldi_split_4.png", 1868, 400],
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469562058133564/vivaldi_split_5.png", 1868, 400],
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469562347528202/vivaldi_split_6.png", 1868, 400],
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469562792136815/vivaldi_split_7.png", 1868, 400],
|
||||
["https://media.discordapp.net/attachments/717080637038788731/1067469563073142804/vivaldi_split_8.png", 1868, 400],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import SongLobbyView from './views/SongLobbyView';
|
||||
import AuthenticationView from './views/AuthenticationView';
|
||||
import HomeView from './views/HomeView';
|
||||
import SearchView from './views/SearchView';
|
||||
import PartitionView from './views/PartitionView';
|
||||
import SetttingsNavigator from './views/SettingsView';
|
||||
import { useQuery } from 'react-query';
|
||||
import API from './API';
|
||||
@@ -20,6 +21,7 @@ export const protectedRoutes = <>
|
||||
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
||||
<Stack.Screen name="Search" component={SearchView} options={{ title: translate('search') }} />
|
||||
<Stack.Screen name="User" component={ProfileView} options={{ title: translate('user') }} />
|
||||
<Stack.Screen name="Partition" component={PartitionView} options={{ title: translate('partition') }} />
|
||||
</>;
|
||||
|
||||
export const publicRoutes = <React.Fragment>
|
||||
|
||||
@@ -23,9 +23,7 @@
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/adaptive-icon.png",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"package": "com.chromacase.chromacase",
|
||||
"versionCode": 1
|
||||
"backgroundColor": "#FFFFFF"
|
||||
},
|
||||
"package": "build.apk"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ module.exports = function(api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
env: {
|
||||
production: {
|
||||
plugins: ['react-native-paper/babel'],
|
||||
|
||||
29
front/components/PartitionVisualizer/PartitionVisualizer.tsx
Normal file
29
front/components/PartitionVisualizer/PartitionVisualizer.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useTheme, Box, Center } from "native-base";
|
||||
import React from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import LoadingComponent from "../Loading";
|
||||
import SlideView from "./SlideView";
|
||||
import API from "../../API";
|
||||
|
||||
type PartitionVisualizerProps = {
|
||||
songId: number;
|
||||
};
|
||||
|
||||
const PartitionVisualizer = ({ songId }: PartitionVisualizerProps) => {
|
||||
const partitionRessources = useQuery(["partition"], () =>
|
||||
API.getPartitionRessources(songId)
|
||||
);
|
||||
|
||||
if (!partitionRessources.data) {
|
||||
return (
|
||||
<Center style={{ flexGrow: 1 }}>
|
||||
<LoadingComponent />
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SlideView sources={partitionRessources.data} speed={200} startAt={0} />
|
||||
);
|
||||
};
|
||||
|
||||
export default PartitionVisualizer;
|
||||
151
front/components/PartitionVisualizer/SlideView.tsx
Normal file
151
front/components/PartitionVisualizer/SlideView.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import {
|
||||
useTheme,
|
||||
Box,
|
||||
Image,
|
||||
Row,
|
||||
Column,
|
||||
ZStack,
|
||||
Button,
|
||||
Icon,
|
||||
} from "native-base";
|
||||
import { MotiView, useDynamicAnimation } from "moti";
|
||||
import { abs, Easing } from "react-native-reanimated";
|
||||
import React from "react";
|
||||
import { FontAwesome5, MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
|
||||
type ImgSlideViewProps = {
|
||||
sources: [url: string, width: number, height: number][];
|
||||
// number of pixels per second
|
||||
speed: number;
|
||||
// percentage of the partition
|
||||
startAt: number | null;
|
||||
};
|
||||
|
||||
const range = (start: number, end: number, step: number) => {
|
||||
const arr = [];
|
||||
for (let i = start; i < end; i += step) {
|
||||
arr.push(i);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
const SlideView = ({ sources, speed, startAt }: ImgSlideViewProps) => {
|
||||
const totalWidth = sources.reduce((acc, [_, width]) => acc + width, 0);
|
||||
const stepSize = speed / 2;
|
||||
const stepDuration = 1000 / 2;
|
||||
const animation = useDynamicAnimation(() => ({
|
||||
translateX: 0,
|
||||
}));
|
||||
|
||||
let stepCount = 0;
|
||||
|
||||
if (startAt) {
|
||||
const nbPixelsToSkip = totalWidth * startAt;
|
||||
animation.animateTo({
|
||||
translateX: -nbPixelsToSkip,
|
||||
transition: {
|
||||
type: "timing",
|
||||
delay: 0,
|
||||
easing: Easing.linear,
|
||||
},
|
||||
});
|
||||
stepCount = Math.floor(nbPixelsToSkip / stepSize);
|
||||
}
|
||||
|
||||
const jumpAt = (value: number, absolute: boolean) => {
|
||||
if (absolute && value < 0) value = 0;
|
||||
if (value > totalWidth) value = totalWidth;
|
||||
if (!absolute) {
|
||||
stepCount += Math.floor(value / stepSize);
|
||||
} else {
|
||||
stepCount = Math.floor(value / stepSize);
|
||||
}
|
||||
if (stepCount < 0) stepCount = 0;
|
||||
animation.animateTo({
|
||||
translateX: -(stepCount * stepSize),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<Box overflow={"hidden"}>
|
||||
<MotiView
|
||||
state={animation}
|
||||
onDidAnimate={(
|
||||
styleProp,
|
||||
didAnimationFinish,
|
||||
_maybeValue,
|
||||
{ attemptedValue }
|
||||
) => {
|
||||
if (styleProp === "translateX" && didAnimationFinish) {
|
||||
stepCount++;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
{sources.map(([source, w, h], index) => (
|
||||
<Image
|
||||
key={index}
|
||||
source={{ uri: source }}
|
||||
alt="image"
|
||||
resizeMode="contain"
|
||||
height={h}
|
||||
width={w}
|
||||
/>
|
||||
))}
|
||||
</Row>
|
||||
</MotiView>
|
||||
</Box>
|
||||
<Button.Group margin={3}>
|
||||
<Button
|
||||
leftIcon={<Icon as={FontAwesome5} name="play" size="sm" />}
|
||||
onPress={() => {
|
||||
animation.animateTo({
|
||||
translateX: range(-totalWidth, 0, stepSize)
|
||||
.reverse()
|
||||
.slice(stepCount),
|
||||
transition: {
|
||||
type: "timing",
|
||||
easing: Easing.linear,
|
||||
duration: stepDuration,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
leftIcon={<Icon as={FontAwesome5} name="pause" size="sm" />}
|
||||
onPress={() => {
|
||||
animation.animateTo({});
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
leftIcon={
|
||||
<Icon as={MaterialCommunityIcons} name="rewind-10" size="sm" />
|
||||
}
|
||||
onPress={() => jumpAt(-200, false)}
|
||||
/>
|
||||
<Button
|
||||
leftIcon={
|
||||
<Icon
|
||||
as={MaterialCommunityIcons}
|
||||
name="fast-forward-10"
|
||||
size="sm"
|
||||
/>
|
||||
}
|
||||
onPress={() => jumpAt(200, false)}
|
||||
/>
|
||||
<Button
|
||||
leftIcon={<Icon as={FontAwesome5} name="stop" size="sm" />}
|
||||
onPress={() => {
|
||||
stepCount = 0;
|
||||
animation.animateTo({
|
||||
translateX: 0,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default SlideView;
|
||||
@@ -81,6 +81,8 @@ export const en = {
|
||||
changeemailBtn: 'Change Email',
|
||||
googleacctBtn: 'Google Account',
|
||||
forgottenPassword: 'Forgotten password',
|
||||
|
||||
partition: 'Partition',
|
||||
};
|
||||
|
||||
export const fr: typeof en = {
|
||||
@@ -166,6 +168,8 @@ export const fr: typeof en = {
|
||||
changeemailBtn: 'Changer l\'email',
|
||||
googleacctBtn: 'Compte Google',
|
||||
forgottenPassword: "Mot de passe oublié",
|
||||
|
||||
partition: 'Partition',
|
||||
};
|
||||
|
||||
export const sp: typeof en = {
|
||||
@@ -251,4 +255,6 @@ export const sp: typeof en = {
|
||||
changepasswdBtn: 'Changer le mot de pass',
|
||||
changeemailBtn: 'Change l\'email',
|
||||
googleacctBtn: 'Compte Google',
|
||||
|
||||
partition: 'Partition',
|
||||
};
|
||||
@@ -32,16 +32,19 @@
|
||||
"expo-secure-store": "~11.2.0",
|
||||
"expo-splash-screen": "~0.15.1",
|
||||
"expo-status-bar": "~1.3.0",
|
||||
"expo-updates": "~0.13.4",
|
||||
"format-duration": "^2.0.0",
|
||||
"i18next": "^21.8.16",
|
||||
"jest": "^26.6.3",
|
||||
"jest-expo": "^45.0.1",
|
||||
"moti": "^0.22.0",
|
||||
"native-base": "^3.4.17",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-native": "0.68.2",
|
||||
"react-native-paper": "^4.12.5",
|
||||
"react-native-reanimated": "~2.8.0",
|
||||
"react-native-safe-area-context": "4.2.4",
|
||||
"react-native-screens": "~3.11.1",
|
||||
"react-native-super-grid": "^4.6.1",
|
||||
@@ -62,6 +65,7 @@
|
||||
"@storybook/manager-webpack4": "^6.5.15",
|
||||
"@storybook/react": "^6.5.15",
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@expo/webpack-config": "^0.17.4",
|
||||
"@testing-library/react-native": "^11.0.0",
|
||||
"@types/node": "^18.11.8",
|
||||
"@types/react": "^18.0.18",
|
||||
|
||||
@@ -102,6 +102,9 @@ const HomeView = () => {
|
||||
<Button backgroundColor={theme.colors.primary[600]} rounded={"full"} size="sm" onPress={() => navigation.navigate('Settings')} >
|
||||
<Translate translationKey='settingsBtn'/>
|
||||
</Button>
|
||||
<Button backgroundColor={theme.colors.primary[600]} rounded={"full"} size="sm" onPress={() => navigation.navigate('Partition')} >
|
||||
<Translate translationKey='partition'/>
|
||||
</Button>
|
||||
</Box>
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
15
front/views/PartitionView.tsx
Normal file
15
front/views/PartitionView.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import { Box } from "native-base";
|
||||
import API from "../API";
|
||||
import PartitionVisualizer from "../components/PartitionVisualizer/PartitionVisualizer";
|
||||
|
||||
const PartitionView = () => {
|
||||
|
||||
return (
|
||||
<Box style={{ padding: 10 }}>
|
||||
<PartitionVisualizer songId={1} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PartitionView;
|
||||
15
front/webpack.config.js
Normal file
15
front/webpack.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const createExpoWebpackConfigAsync = require('@expo/webpack-config')
|
||||
|
||||
module.exports = async function (env, argv) {
|
||||
const config = await createExpoWebpackConfigAsync(
|
||||
{
|
||||
...env,
|
||||
babel: { dangerouslyAddModulePathsToTranspile: ['moti'] },
|
||||
},
|
||||
argv
|
||||
)
|
||||
|
||||
config.resolve.alias['framer-motion'] = 'framer-motion/dist/framer-motion'
|
||||
|
||||
return config
|
||||
}
|
||||
1941
front/yarn.lock
1941
front/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user