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:
Clément Le Bihan
2023-01-25 16:44:00 +09:00
committed by GitHub
parent e971073ac4
commit 2f5c9481aa
12 changed files with 2099 additions and 90 deletions

View File

@@ -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],
];
}
}

View File

@@ -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>

View File

@@ -23,9 +23,7 @@
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF",
"package": "com.chromacase.chromacase",
"versionCode": 1
"backgroundColor": "#FFFFFF"
},
"package": "build.apk"
},

View File

@@ -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'],

View 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;

View 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;

View File

@@ -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',
};

View File

@@ -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",

View File

@@ -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>

View 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
View 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
}

File diff suppressed because it is too large Load Diff