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
|
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 AuthenticationView from './views/AuthenticationView';
|
||||||
import HomeView from './views/HomeView';
|
import HomeView from './views/HomeView';
|
||||||
import SearchView from './views/SearchView';
|
import SearchView from './views/SearchView';
|
||||||
|
import PartitionView from './views/PartitionView';
|
||||||
import SetttingsNavigator from './views/SettingsView';
|
import SetttingsNavigator from './views/SettingsView';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import API from './API';
|
import API from './API';
|
||||||
@@ -20,6 +21,7 @@ export const protectedRoutes = <>
|
|||||||
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
<Stack.Screen name="Song" component={SongLobbyView} options={{ title: translate('play') }} />
|
||||||
<Stack.Screen name="Search" component={SearchView} options={{ title: translate('search') }} />
|
<Stack.Screen name="Search" component={SearchView} options={{ title: translate('search') }} />
|
||||||
<Stack.Screen name="User" component={ProfileView} options={{ title: translate('user') }} />
|
<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>
|
export const publicRoutes = <React.Fragment>
|
||||||
|
|||||||
@@ -23,9 +23,7 @@
|
|||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
"backgroundColor": "#FFFFFF",
|
"backgroundColor": "#FFFFFF"
|
||||||
"package": "com.chromacase.chromacase",
|
|
||||||
"versionCode": 1
|
|
||||||
},
|
},
|
||||||
"package": "build.apk"
|
"package": "build.apk"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module.exports = function(api) {
|
|||||||
api.cache(true);
|
api.cache(true);
|
||||||
return {
|
return {
|
||||||
presets: ['babel-preset-expo'],
|
presets: ['babel-preset-expo'],
|
||||||
|
plugins: ['react-native-reanimated/plugin'],
|
||||||
env: {
|
env: {
|
||||||
production: {
|
production: {
|
||||||
plugins: ['react-native-paper/babel'],
|
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',
|
changeemailBtn: 'Change Email',
|
||||||
googleacctBtn: 'Google Account',
|
googleacctBtn: 'Google Account',
|
||||||
forgottenPassword: 'Forgotten password',
|
forgottenPassword: 'Forgotten password',
|
||||||
|
|
||||||
|
partition: 'Partition',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fr: typeof en = {
|
export const fr: typeof en = {
|
||||||
@@ -166,6 +168,8 @@ export const fr: typeof en = {
|
|||||||
changeemailBtn: 'Changer l\'email',
|
changeemailBtn: 'Changer l\'email',
|
||||||
googleacctBtn: 'Compte Google',
|
googleacctBtn: 'Compte Google',
|
||||||
forgottenPassword: "Mot de passe oublié",
|
forgottenPassword: "Mot de passe oublié",
|
||||||
|
|
||||||
|
partition: 'Partition',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sp: typeof en = {
|
export const sp: typeof en = {
|
||||||
@@ -251,4 +255,6 @@ export const sp: typeof en = {
|
|||||||
changepasswdBtn: 'Changer le mot de pass',
|
changepasswdBtn: 'Changer le mot de pass',
|
||||||
changeemailBtn: 'Change l\'email',
|
changeemailBtn: 'Change l\'email',
|
||||||
googleacctBtn: 'Compte Google',
|
googleacctBtn: 'Compte Google',
|
||||||
|
|
||||||
|
partition: 'Partition',
|
||||||
};
|
};
|
||||||
@@ -32,16 +32,19 @@
|
|||||||
"expo-secure-store": "~11.2.0",
|
"expo-secure-store": "~11.2.0",
|
||||||
"expo-splash-screen": "~0.15.1",
|
"expo-splash-screen": "~0.15.1",
|
||||||
"expo-status-bar": "~1.3.0",
|
"expo-status-bar": "~1.3.0",
|
||||||
|
"expo-updates": "~0.13.4",
|
||||||
"format-duration": "^2.0.0",
|
"format-duration": "^2.0.0",
|
||||||
"i18next": "^21.8.16",
|
"i18next": "^21.8.16",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"jest-expo": "^45.0.1",
|
"jest-expo": "^45.0.1",
|
||||||
|
"moti": "^0.22.0",
|
||||||
"native-base": "^3.4.17",
|
"native-base": "^3.4.17",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-i18next": "^11.18.3",
|
"react-i18next": "^11.18.3",
|
||||||
"react-native": "0.68.2",
|
"react-native": "0.68.2",
|
||||||
"react-native-paper": "^4.12.5",
|
"react-native-paper": "^4.12.5",
|
||||||
|
"react-native-reanimated": "~2.8.0",
|
||||||
"react-native-safe-area-context": "4.2.4",
|
"react-native-safe-area-context": "4.2.4",
|
||||||
"react-native-screens": "~3.11.1",
|
"react-native-screens": "~3.11.1",
|
||||||
"react-native-super-grid": "^4.6.1",
|
"react-native-super-grid": "^4.6.1",
|
||||||
@@ -62,6 +65,7 @@
|
|||||||
"@storybook/manager-webpack4": "^6.5.15",
|
"@storybook/manager-webpack4": "^6.5.15",
|
||||||
"@storybook/react": "^6.5.15",
|
"@storybook/react": "^6.5.15",
|
||||||
"@storybook/testing-library": "^0.0.13",
|
"@storybook/testing-library": "^0.0.13",
|
||||||
|
"@expo/webpack-config": "^0.17.4",
|
||||||
"@testing-library/react-native": "^11.0.0",
|
"@testing-library/react-native": "^11.0.0",
|
||||||
"@types/node": "^18.11.8",
|
"@types/node": "^18.11.8",
|
||||||
"@types/react": "^18.0.18",
|
"@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')} >
|
<Button backgroundColor={theme.colors.primary[600]} rounded={"full"} size="sm" onPress={() => navigation.navigate('Settings')} >
|
||||||
<Translate translationKey='settingsBtn'/>
|
<Translate translationKey='settingsBtn'/>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button backgroundColor={theme.colors.primary[600]} rounded={"full"} size="sm" onPress={() => navigation.navigate('Partition')} >
|
||||||
|
<Translate translationKey='partition'/>
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Box>
|
</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