[FIX]:
- Prettier - Linter
This commit is contained in:
@@ -13,7 +13,7 @@ import { Translate, translate } from './i18n/i18n';
|
||||
import SongLobbyView from './views/SongLobbyView';
|
||||
import HomeView from './views/HomeView';
|
||||
import SearchView from './views/SearchView';
|
||||
import SetttingsNavigator from './views/settings/SettingsView';
|
||||
import SettingsTab from './views/settings/SettingsView';
|
||||
import { useQuery } from './Queries';
|
||||
import API, { APIError } from './API';
|
||||
import PlayView from './views/PlayView';
|
||||
@@ -58,7 +58,7 @@ const protectedRoutes = () =>
|
||||
},
|
||||
Play: { component: PlayView, options: { title: translate('play') }, link: '/play/:songId' },
|
||||
Settings: {
|
||||
component: SetttingsNavigator,
|
||||
component: SettingsTab,
|
||||
options: { headerShown: false },
|
||||
link: '/settings/:screen?',
|
||||
stringify: {
|
||||
|
||||
@@ -32,7 +32,7 @@ const ThemeProvider = ({ children }: { children: JSX.Element }) => {
|
||||
};
|
||||
|
||||
const glassmorphism = colorScheme === 'light' ? lightGlassmorphism : darkGlassmorphism;
|
||||
const text = colorScheme === 'light' ? darkGlassmorphism : lightGlassmorphism
|
||||
const text = colorScheme === 'light' ? darkGlassmorphism : lightGlassmorphism;
|
||||
|
||||
return (
|
||||
<NativeBaseProvider
|
||||
|
||||
@@ -22,7 +22,7 @@ export const RawElement = ({ element }: RawElementProps) => {
|
||||
const isSmallScreen = screenSize === 'small';
|
||||
const { width: screenWidth } = useWindowDimensions();
|
||||
const colorScheme = useColorScheme();
|
||||
const color = colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'
|
||||
const color = colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)';
|
||||
|
||||
return (
|
||||
<Column
|
||||
|
||||
@@ -127,7 +127,13 @@ const ButtonBase: React.FC<ButtonProps> = ({
|
||||
{icon && (
|
||||
<MyIcon
|
||||
size={'18'}
|
||||
color={type === 'outlined' ? colors.primary[300] : colorScheme === 'dark' || type === 'filled' ? '#FFFFFF' : colors.black[500] }
|
||||
color={
|
||||
type === 'outlined'
|
||||
? colors.primary[300]
|
||||
: colorScheme === 'dark' || type === 'filled'
|
||||
? '#FFFFFF'
|
||||
: colors.black[500]
|
||||
}
|
||||
variant={iconVariant}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -54,17 +54,9 @@ const CheckboxBase: React.FC<CheckboxProps> = ({ title, color, style, check, set
|
||||
>
|
||||
<View style={styles.content}>
|
||||
{check ? (
|
||||
<TickSquare
|
||||
size="24"
|
||||
color={color ?? colors.primary[300]}
|
||||
variant="Bold"
|
||||
/>
|
||||
<TickSquare size="24" color={color ?? colors.primary[300]} variant="Bold" />
|
||||
) : (
|
||||
<AddSquare
|
||||
size="24"
|
||||
color={color ?? colors.primary[300]}
|
||||
variant="Outline"
|
||||
/>
|
||||
<AddSquare size="24" color={color ?? colors.primary[300]} variant="Outline" />
|
||||
)}
|
||||
<Text style={styles.text} selectable={false}>
|
||||
{title}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
// Import required dependencies and components.
|
||||
import { Icon } from "iconsax-react-native";
|
||||
import { useRef, useState, useMemo } from "react";
|
||||
import { Animated, StyleProp, TouchableOpacity, ViewStyle } from "react-native";
|
||||
import { Icon } from 'iconsax-react-native';
|
||||
import { useRef, useState, useMemo } from 'react';
|
||||
import { Animated, StyleProp, TouchableOpacity, ViewStyle } from 'react-native';
|
||||
|
||||
// Default values for the component props.
|
||||
const DEFAULT_SCALE_FACTOR: number = 1.25;
|
||||
@@ -10,110 +11,110 @@ const DEFAULT_PADDING: number = 0;
|
||||
|
||||
// Define the type for the IconButton props.
|
||||
type IconButtonProps = {
|
||||
/**
|
||||
* Indicates if the button starts in an active state.
|
||||
* @default false
|
||||
*/
|
||||
isActive?: boolean;
|
||||
|
||||
/**
|
||||
* Color of the icon.
|
||||
*/
|
||||
color: string;
|
||||
|
||||
/**
|
||||
* Optional color of the icon when active.
|
||||
*/
|
||||
colorActive?: string;
|
||||
|
||||
/**
|
||||
* Callback function triggered when the button is pressed.
|
||||
*/
|
||||
onPress?: () => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Size of the icon.
|
||||
* @default 24
|
||||
*/
|
||||
size?: number;
|
||||
|
||||
/**
|
||||
* Icon to display.
|
||||
*/
|
||||
icon: Icon;
|
||||
|
||||
/**
|
||||
* Optional icon to display when active.
|
||||
*/
|
||||
iconActive?: Icon;
|
||||
|
||||
/**
|
||||
* Variant style of the icon.
|
||||
* @default "Outline"
|
||||
*/
|
||||
variant?: "Outline" | "Bold" | "Bulk" | "Broken" | "TwoTone";
|
||||
|
||||
/**
|
||||
* Variant style of the icon when active.
|
||||
* @default "Outline"
|
||||
*/
|
||||
activeVariant?: "Outline" | "Bold" | "Bulk" | "Broken" | "TwoTone";
|
||||
|
||||
/**
|
||||
* Custom style for the icon.
|
||||
*/
|
||||
style?: ViewStyle | ViewStyle[];
|
||||
|
||||
/**
|
||||
* Custom style for the icon's container.
|
||||
*/
|
||||
containerStyle?: ViewStyle | ViewStyle[];
|
||||
|
||||
/**
|
||||
* Scale factor when the icon animates on press.
|
||||
* @default 1.25
|
||||
*/
|
||||
scaleFactor?: number;
|
||||
|
||||
/**
|
||||
* Duration of the icon animation in milliseconds.
|
||||
* @default 250
|
||||
*/
|
||||
animationDuration?: number;
|
||||
|
||||
/**
|
||||
* Padding around the icon.
|
||||
* @default 0
|
||||
*/
|
||||
padding?: number;
|
||||
/**
|
||||
* Indicates if the button starts in an active state.
|
||||
* @default false
|
||||
*/
|
||||
isActive?: boolean;
|
||||
|
||||
/**
|
||||
* Color of the icon.
|
||||
*/
|
||||
color: string;
|
||||
|
||||
/**
|
||||
* Optional color of the icon when active.
|
||||
*/
|
||||
colorActive?: string;
|
||||
|
||||
/**
|
||||
* Callback function triggered when the button is pressed.
|
||||
*/
|
||||
onPress?: () => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* Size of the icon.
|
||||
* @default 24
|
||||
*/
|
||||
size?: number;
|
||||
|
||||
/**
|
||||
* Icon to display.
|
||||
*/
|
||||
icon: Icon;
|
||||
|
||||
/**
|
||||
* Optional icon to display when active.
|
||||
*/
|
||||
iconActive?: Icon;
|
||||
|
||||
/**
|
||||
* Variant style of the icon.
|
||||
* @default "Outline"
|
||||
*/
|
||||
variant?: 'Outline' | 'Bold' | 'Bulk' | 'Broken' | 'TwoTone';
|
||||
|
||||
/**
|
||||
* Variant style of the icon when active.
|
||||
* @default "Outline"
|
||||
*/
|
||||
activeVariant?: 'Outline' | 'Bold' | 'Bulk' | 'Broken' | 'TwoTone';
|
||||
|
||||
/**
|
||||
* Custom style for the icon.
|
||||
*/
|
||||
style?: ViewStyle | ViewStyle[];
|
||||
|
||||
/**
|
||||
* Custom style for the icon's container.
|
||||
*/
|
||||
containerStyle?: ViewStyle | ViewStyle[];
|
||||
|
||||
/**
|
||||
* Scale factor when the icon animates on press.
|
||||
* @default 1.25
|
||||
*/
|
||||
scaleFactor?: number;
|
||||
|
||||
/**
|
||||
* Duration of the icon animation in milliseconds.
|
||||
* @default 250
|
||||
*/
|
||||
animationDuration?: number;
|
||||
|
||||
/**
|
||||
* Padding around the icon.
|
||||
* @default 0
|
||||
*/
|
||||
padding?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* `IconButton` Component
|
||||
*
|
||||
*
|
||||
* Render an interactive icon that can toggle between active and inactive states.
|
||||
* Supports customization of colors, icons, animation speed, size, and more.
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Can render two different icons for active and inactive states.
|
||||
* - Includes an animation that scales the icon when pressed.
|
||||
* - Supports custom styling for both the icon and its container.
|
||||
* - Accepts various icon variants for flexibility in design.
|
||||
*
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
*
|
||||
* ```jsx
|
||||
* <IconButton
|
||||
* <IconButton
|
||||
* color="#000"
|
||||
* icon={SomeIcon}
|
||||
* onPress={() => console.log('Icon pressed!')}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* To use with active states:
|
||||
*
|
||||
*
|
||||
* ```jsx
|
||||
* <IconButton
|
||||
* <IconButton
|
||||
* isActive={true}
|
||||
* color="#000"
|
||||
* colorActive="#ff0000"
|
||||
@@ -122,138 +123,142 @@ type IconButtonProps = {
|
||||
* onPress={() => console.log('Icon toggled!')}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Note: If `iconActive` is provided but `colorActive` is not,
|
||||
* the `color` prop will be used for both active and inactive states.
|
||||
*/
|
||||
const IconButton: React.FC<IconButtonProps> = ({
|
||||
isActive = false,
|
||||
color,
|
||||
colorActive,
|
||||
onPress,
|
||||
size = 24,
|
||||
icon: Icon,
|
||||
iconActive: IconActive,
|
||||
variant = "Outline",
|
||||
activeVariant = "Outline",
|
||||
style,
|
||||
containerStyle,
|
||||
scaleFactor = DEFAULT_SCALE_FACTOR,
|
||||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||
padding = DEFAULT_PADDING,
|
||||
isActive = false,
|
||||
color,
|
||||
colorActive,
|
||||
onPress,
|
||||
size = 24,
|
||||
icon: Icon,
|
||||
iconActive: IconActive,
|
||||
variant = 'Outline',
|
||||
activeVariant = 'Outline',
|
||||
style,
|
||||
containerStyle,
|
||||
scaleFactor = DEFAULT_SCALE_FACTOR,
|
||||
animationDuration = DEFAULT_ANIMATION_DURATION,
|
||||
padding = DEFAULT_PADDING,
|
||||
}) => {
|
||||
// State to track active status.
|
||||
const [isActiveState, setIsActiveState] = useState<boolean>(isActive);
|
||||
// State to track active status.
|
||||
const [isActiveState, setIsActiveState] = useState<boolean>(isActive);
|
||||
|
||||
// Animation values.
|
||||
const scaleValue: Animated.Value = useRef(new Animated.Value(1)).current;
|
||||
const animateValue: Animated.Value = useRef(new Animated.Value(isActive ? 0 : 1)).current;
|
||||
|
||||
// Check for active icon.
|
||||
const hasActiveIcon: boolean = !!IconActive;
|
||||
// Animation values.
|
||||
const scaleValue: Animated.Value = useRef(new Animated.Value(1)).current;
|
||||
const animateValue: Animated.Value = useRef(new Animated.Value(isActive ? 0 : 1)).current;
|
||||
|
||||
// Interpolation for icon colors between active and inactive states.
|
||||
const colorValue: Animated.AnimatedInterpolation<string | number> = animateValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [color, colorActive || color],
|
||||
});
|
||||
// Check for active icon.
|
||||
const hasActiveIcon: boolean = !!IconActive;
|
||||
|
||||
// Combine styles for the icon container.
|
||||
const combinedContainerStyle: StyleProp<ViewStyle> = useMemo<(ViewStyle | ViewStyle[] | undefined)[]>(
|
||||
() => [
|
||||
{
|
||||
position: 'relative',
|
||||
// Adjust width and height to account for specified padding. Since the icons
|
||||
// are absolutely positioned inside the container, the container's size needs
|
||||
// to include the padding to ensure the icons are properly centered and spaced.
|
||||
width: size + (padding * 2),
|
||||
height: size + (padding * 2),
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
containerStyle
|
||||
],
|
||||
[padding, containerStyle]
|
||||
);
|
||||
// Interpolation for icon colors between active and inactive states.
|
||||
const colorValue: Animated.AnimatedInterpolation<string | number> = animateValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [color, colorActive || color],
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles the active state of the icon.
|
||||
* Executes the onPress callback and triggers the animation.
|
||||
*/
|
||||
const toggleState = async () => {
|
||||
// Execute onPress if provided.
|
||||
if (onPress) {
|
||||
await onPress();
|
||||
}
|
||||
// Combine styles for the icon container.
|
||||
const combinedContainerStyle: StyleProp<ViewStyle> = useMemo<
|
||||
(ViewStyle | ViewStyle[] | undefined)[]
|
||||
>(
|
||||
() => [
|
||||
{
|
||||
position: 'relative',
|
||||
// Adjust width and height to account for specified padding. Since the icons
|
||||
// are absolutely positioned inside the container, the container's size needs
|
||||
// to include the padding to ensure the icons are properly centered and spaced.
|
||||
width: size + padding * 2,
|
||||
height: size + padding * 2,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
containerStyle,
|
||||
],
|
||||
[padding, containerStyle]
|
||||
);
|
||||
|
||||
// Toggle isActiveState.
|
||||
setIsActiveState(!isActiveState);
|
||||
/**
|
||||
* Toggles the active state of the icon.
|
||||
* Executes the onPress callback and triggers the animation.
|
||||
*/
|
||||
const toggleState = async () => {
|
||||
// Execute onPress if provided.
|
||||
if (onPress) {
|
||||
await onPress();
|
||||
}
|
||||
|
||||
// Animation sequences.
|
||||
const animations: Animated.CompositeAnimation[] = [
|
||||
Animated.sequence([
|
||||
Animated.timing(scaleValue, {
|
||||
toValue: scaleFactor,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(scaleValue, {
|
||||
toValue: 1,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]),
|
||||
];
|
||||
// Toggle isActiveState.
|
||||
setIsActiveState(!isActiveState);
|
||||
|
||||
if (hasActiveIcon || colorActive) {
|
||||
animations.push(
|
||||
Animated.timing(animateValue, {
|
||||
toValue: isActiveState ? 1 : 0,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
// Animation sequences.
|
||||
const animations: Animated.CompositeAnimation[] = [
|
||||
Animated.sequence([
|
||||
Animated.timing(scaleValue, {
|
||||
toValue: scaleFactor,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(scaleValue, {
|
||||
toValue: 1,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]),
|
||||
];
|
||||
|
||||
// Start animations in parallel.
|
||||
Animated.parallel(animations).start();
|
||||
};
|
||||
if (hasActiveIcon || colorActive) {
|
||||
animations.push(
|
||||
Animated.timing(animateValue, {
|
||||
toValue: isActiveState ? 1 : 0,
|
||||
duration: animationDuration,
|
||||
useNativeDriver: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
onPress={toggleState}
|
||||
style={combinedContainerStyle}
|
||||
>
|
||||
{hasActiveIcon && (
|
||||
<Animated.View
|
||||
style={[{
|
||||
padding: padding,
|
||||
color: colorValue,
|
||||
opacity: animateValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [1, 0],
|
||||
}),
|
||||
position: 'absolute',
|
||||
transform: [{ scale: scaleValue }],
|
||||
}, style]}
|
||||
>
|
||||
<IconActive size={size} variant={activeVariant}/>
|
||||
</Animated.View>
|
||||
)}
|
||||
<Animated.View
|
||||
style={[{
|
||||
padding: padding,
|
||||
color: colorValue,
|
||||
opacity: animateValue,
|
||||
position: 'absolute',
|
||||
transform: [{ scale: scaleValue }],
|
||||
}, style]}
|
||||
>
|
||||
<Icon size={size} variant={variant} />
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
// Start animations in parallel.
|
||||
Animated.parallel(animations).start();
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity activeOpacity={1} onPress={toggleState} style={combinedContainerStyle}>
|
||||
{hasActiveIcon && (
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
padding: padding,
|
||||
color: colorValue,
|
||||
opacity: animateValue.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [1, 0],
|
||||
}),
|
||||
position: 'absolute',
|
||||
transform: [{ scale: scaleValue }],
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<IconActive size={size} variant={activeVariant} />
|
||||
</Animated.View>
|
||||
)}
|
||||
<Animated.View
|
||||
style={[
|
||||
{
|
||||
padding: padding,
|
||||
color: colorValue,
|
||||
opacity: animateValue,
|
||||
position: 'absolute',
|
||||
transform: [{ scale: scaleValue }],
|
||||
},
|
||||
style,
|
||||
]}
|
||||
>
|
||||
<Icon size={size} variant={variant} />
|
||||
</Animated.View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconButton;
|
||||
|
||||
@@ -48,28 +48,30 @@ const LinkBase: React.FC<LinkBaseProps> = ({ text, onPress }) => {
|
||||
}).start();
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.container}
|
||||
onPress={onPress}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<Column>
|
||||
<Text>{text}</Text>
|
||||
<Animated.View style={[
|
||||
styles.underline,
|
||||
{
|
||||
backgroundColor: theme.colors.primary[300],
|
||||
height: underlineHeight,
|
||||
opacity: opacity
|
||||
}
|
||||
]}/>
|
||||
</Column>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
return (
|
||||
<TouchableOpacity
|
||||
style={styles.container}
|
||||
onPress={onPress}
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<Column>
|
||||
<Text>{text}</Text>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.underline,
|
||||
{
|
||||
backgroundColor: theme.colors.primary[300],
|
||||
height: underlineHeight,
|
||||
opacity: opacity,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Column>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -38,30 +38,34 @@ const LogoutButtonCC = ({
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonBase
|
||||
style={style}
|
||||
icon={LogoutCurve}
|
||||
title={!collapse ? translate('signOutBtn') : undefined}
|
||||
type={buttonType}
|
||||
onPress={async () => {isGuest ? setIsVisible(true) : dispatch(unsetAccessToken());}}
|
||||
/>
|
||||
<PopupCC
|
||||
title={translate('Attention')}
|
||||
description={translate('transformGuestToUserExplanations')}
|
||||
isVisible={isVisible}
|
||||
setIsVisible={setIsVisible}
|
||||
>
|
||||
<SignUpForm onSubmit={handleSubmit} />
|
||||
<ButtonBase
|
||||
style={!collapse ? { width: '100%' } : {}}
|
||||
type="outlined"
|
||||
icon={LogoutCurve}
|
||||
title={translate('signOutBtn')}
|
||||
onPress={async () => { dispatch(unsetAccessToken()) }}
|
||||
/>
|
||||
</PopupCC>
|
||||
</>
|
||||
<>
|
||||
<ButtonBase
|
||||
style={style}
|
||||
icon={LogoutCurve}
|
||||
title={!collapse ? translate('signOutBtn') : undefined}
|
||||
type={buttonType}
|
||||
onPress={async () => {
|
||||
isGuest ? setIsVisible(true) : dispatch(unsetAccessToken());
|
||||
}}
|
||||
/>
|
||||
<PopupCC
|
||||
title={translate('Attention')}
|
||||
description={translate('transformGuestToUserExplanations')}
|
||||
isVisible={isVisible}
|
||||
setIsVisible={setIsVisible}
|
||||
>
|
||||
<SignUpForm onSubmit={handleSubmit} />
|
||||
<ButtonBase
|
||||
style={!collapse ? { width: '100%' } : {}}
|
||||
type="outlined"
|
||||
icon={LogoutCurve}
|
||||
title={translate('signOutBtn')}
|
||||
onPress={async () => {
|
||||
dispatch(unsetAccessToken());
|
||||
}}
|
||||
/>
|
||||
</PopupCC>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useMemo, memo } from 'react';
|
||||
import { StyleSheet } from 'react-native';
|
||||
import { Column, HStack, Row, Stack, Text, useBreakpointValue, useTheme } from 'native-base';
|
||||
@@ -11,56 +12,56 @@ import { useTranslation } from 'react-i18next';
|
||||
* Props for the MusicItem component.
|
||||
*/
|
||||
interface MusicItemProps {
|
||||
/** The artist's name. */
|
||||
artist: string;
|
||||
|
||||
/** The song's title. */
|
||||
song: string;
|
||||
|
||||
/** The URL for the song's cover image. */
|
||||
image: string;
|
||||
|
||||
/** The level of the song difficulty . */
|
||||
level: number;
|
||||
|
||||
/** The last score achieved for this song. */
|
||||
lastScore: number;
|
||||
|
||||
/** The highest score achieved for this song. */
|
||||
bestScore: number;
|
||||
|
||||
/** Indicates whether the song is liked/favorited by the user. */
|
||||
liked: boolean;
|
||||
|
||||
/** Callback function triggered when the like button is pressed. */
|
||||
onLike: () => void;
|
||||
|
||||
/** Callback function triggered when the song is played. */
|
||||
onPlay?: () => void;
|
||||
/** The artist's name. */
|
||||
artist: string;
|
||||
|
||||
/** The song's title. */
|
||||
song: string;
|
||||
|
||||
/** The URL for the song's cover image. */
|
||||
image: string;
|
||||
|
||||
/** The level of the song difficulty . */
|
||||
level: number;
|
||||
|
||||
/** The last score achieved for this song. */
|
||||
lastScore: number;
|
||||
|
||||
/** The highest score achieved for this song. */
|
||||
bestScore: number;
|
||||
|
||||
/** Indicates whether the song is liked/favorited by the user. */
|
||||
liked: boolean;
|
||||
|
||||
/** Callback function triggered when the like button is pressed. */
|
||||
onLike: () => void;
|
||||
|
||||
/** Callback function triggered when the song is played. */
|
||||
onPlay?: () => void;
|
||||
}
|
||||
|
||||
// Custom hook to handle the number formatting based on the current user's language.
|
||||
function useNumberFormatter() {
|
||||
const { i18n } = useTranslation();
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
// Memoizing the number formatter to avoid unnecessary recalculations.
|
||||
// It will be recreated only when the language changes.
|
||||
const formatter = useMemo(() => {
|
||||
return new Intl.NumberFormat(i18n.language, {
|
||||
notation: "compact",
|
||||
compactDisplay: "short"
|
||||
});
|
||||
}, [i18n.language]);
|
||||
// Memoizing the number formatter to avoid unnecessary recalculations.
|
||||
// It will be recreated only when the language changes.
|
||||
const formatter = useMemo(() => {
|
||||
return new Intl.NumberFormat(i18n.language, {
|
||||
notation: 'compact',
|
||||
compactDisplay: 'short',
|
||||
});
|
||||
}, [i18n.language]);
|
||||
|
||||
return (num: number) => formatter.format(num);
|
||||
return (num: number) => formatter.format(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* `MusicItem` Component
|
||||
*
|
||||
*
|
||||
* Display individual music tracks with artist information, cover image, song title, and associated stats.
|
||||
* Designed for optimal performance and responsiveness across different screen sizes.
|
||||
*
|
||||
*
|
||||
* Features:
|
||||
* - Displays artist name, song title, and track cover image.
|
||||
* - Indicates user interaction with a like/favorite feature.
|
||||
@@ -68,11 +69,11 @@ function useNumberFormatter() {
|
||||
* - Adapts its layout and design responsively according to screen size.
|
||||
* - Optimized performance to ensure smooth rendering even in long lists.
|
||||
* - Automatic number formatting based on user's language preference.
|
||||
*
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
*
|
||||
* ```jsx
|
||||
* <MusicItem
|
||||
* <MusicItem
|
||||
* artist="John Doe"
|
||||
* song="A Beautiful Song"
|
||||
* image="https://example.com/image.jpg"
|
||||
@@ -84,101 +85,109 @@ function useNumberFormatter() {
|
||||
* onPlay={() => {() => console.log('Play music!')}
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* Note:
|
||||
*
|
||||
* Note:
|
||||
* - The number formatting for `level`, `lastScore`, and `bestScore` adapts automatically based on the user's language preference using the i18n module.
|
||||
* - Given its optimized performance characteristics, this component is suitable for rendering in lists with potentially hundreds of items.
|
||||
*/
|
||||
const MusicItem: React.FC<MusicItemProps> = memo((props) => {
|
||||
// Accessing theme colors and breakpoint values for responsive design
|
||||
const { colors } = useTheme();
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: 'md', xl: 'xl' });
|
||||
const formatNumber = useNumberFormatter();
|
||||
function MusicItemComponent(props: MusicItemProps) {
|
||||
// Accessing theme colors and breakpoint values for responsive design
|
||||
const { colors } = useTheme();
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: 'md', xl: 'xl' });
|
||||
const formatNumber = useNumberFormatter();
|
||||
|
||||
// Styles are memoized to optimize performance.
|
||||
const styles = useMemo(() => StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: colors.coolGray[500],
|
||||
paddingRight: screenSize === 'small' ? 8 : 16,
|
||||
},
|
||||
playButtonContainer: {
|
||||
zIndex: 1,
|
||||
position: 'absolute',
|
||||
right: -8,
|
||||
bottom: -6,
|
||||
},
|
||||
playButton: {
|
||||
backgroundColor: colors.primary[300],
|
||||
borderRadius: 999,
|
||||
},
|
||||
image: {
|
||||
position: 'relative',
|
||||
width: screenSize === 'xl' ? 80 : 60,
|
||||
height: screenSize === 'xl' ? 80 : 60,
|
||||
},
|
||||
artistText: {
|
||||
color: colors.text[700],
|
||||
fontWeight: "bold",
|
||||
},
|
||||
songContainer: {
|
||||
width: '100%',
|
||||
},
|
||||
stats: {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
maxWidth: screenSize === 'xl' ? 150 : 50,
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: screenSize === 'xl' ? 'flex-end' : 'center',
|
||||
}
|
||||
}), [colors, screenSize]);
|
||||
// Styles are memoized to optimize performance.
|
||||
const styles = useMemo(
|
||||
() =>
|
||||
StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: colors.coolGray[500],
|
||||
paddingRight: screenSize === 'small' ? 8 : 16,
|
||||
},
|
||||
playButtonContainer: {
|
||||
zIndex: 1,
|
||||
position: 'absolute',
|
||||
right: -8,
|
||||
bottom: -6,
|
||||
},
|
||||
playButton: {
|
||||
backgroundColor: colors.primary[300],
|
||||
borderRadius: 999,
|
||||
},
|
||||
image: {
|
||||
position: 'relative',
|
||||
width: screenSize === 'xl' ? 80 : 60,
|
||||
height: screenSize === 'xl' ? 80 : 60,
|
||||
},
|
||||
artistText: {
|
||||
color: colors.text[700],
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
songContainer: {
|
||||
width: '100%',
|
||||
},
|
||||
stats: {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
maxWidth: screenSize === 'xl' ? 150 : 50,
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: screenSize === 'xl' ? 'flex-end' : 'center',
|
||||
},
|
||||
}),
|
||||
[colors, screenSize]
|
||||
);
|
||||
|
||||
// Memoizing formatted numbers to avoid unnecessary computations.
|
||||
const formattedLevel = useMemo(() => formatNumber(props.level), [props.level]);
|
||||
const formattedLastScore = useMemo(() => formatNumber(props.lastScore), [props.lastScore]);
|
||||
const formattedBestScore = useMemo(() => formatNumber(props.bestScore), [props.bestScore]);
|
||||
// Memoizing formatted numbers to avoid unnecessary computations.
|
||||
const formattedLevel = useMemo(() => formatNumber(props.level), [props.level]);
|
||||
const formattedLastScore = useMemo(() => formatNumber(props.lastScore), [props.lastScore]);
|
||||
const formattedBestScore = useMemo(() => formatNumber(props.bestScore), [props.bestScore]);
|
||||
|
||||
return (
|
||||
<HStack space={screenSize === 'xl' ? 2 : 1} style={styles.container}>
|
||||
<Stack style={{ position: 'relative', overflow: 'hidden' }}>
|
||||
<IconButton
|
||||
containerStyle={styles.playButtonContainer}
|
||||
style={styles.playButton}
|
||||
padding={8}
|
||||
onPress={props.onPlay}
|
||||
color='#FFF'
|
||||
icon={Play}
|
||||
variant="Bold"
|
||||
size={24}
|
||||
/>
|
||||
<Image source={{ uri: props.image }} style={styles.image} />
|
||||
</Stack>
|
||||
<Column style={{ flex: 4, width: '100%', justifyContent: 'center' }}>
|
||||
<Text numberOfLines={1} style={styles.artistText}>
|
||||
{props.artist}
|
||||
</Text>
|
||||
{screenSize === 'xl' && <Spacer height='xs' />}
|
||||
<Row space={2} style={styles.songContainer}>
|
||||
<Text numberOfLines={1}>{props.song}</Text>
|
||||
<IconButton
|
||||
colorActive={colors.text[700]}
|
||||
color={colors.primary[300]}
|
||||
icon={HeartAdd}
|
||||
iconActive={HeartRemove}
|
||||
activeVariant="Bold"
|
||||
size={screenSize === 'xl' ? 24 : 18}
|
||||
isActive={props.liked}
|
||||
onPress={props.onLike}
|
||||
/>
|
||||
</Row>
|
||||
</Column>
|
||||
{[formattedLevel, formattedLastScore, formattedBestScore].map((value, index) => (
|
||||
<Text key={index} style={styles.stats}>
|
||||
{value}
|
||||
</Text>
|
||||
))}
|
||||
</HStack>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<HStack space={screenSize === 'xl' ? 2 : 1} style={styles.container}>
|
||||
<Stack style={{ position: 'relative', overflow: 'hidden' }}>
|
||||
<IconButton
|
||||
containerStyle={styles.playButtonContainer}
|
||||
style={styles.playButton}
|
||||
padding={8}
|
||||
onPress={props.onPlay}
|
||||
color="#FFF"
|
||||
icon={Play}
|
||||
variant="Bold"
|
||||
size={24}
|
||||
/>
|
||||
<Image source={{ uri: props.image }} style={styles.image} />
|
||||
</Stack>
|
||||
<Column style={{ flex: 4, width: '100%', justifyContent: 'center' }}>
|
||||
<Text numberOfLines={1} style={styles.artistText}>
|
||||
{props.artist}
|
||||
</Text>
|
||||
{screenSize === 'xl' && <Spacer height="xs" />}
|
||||
<Row space={2} style={styles.songContainer}>
|
||||
<Text numberOfLines={1}>{props.song}</Text>
|
||||
<IconButton
|
||||
colorActive={colors.text[700]}
|
||||
color={colors.primary[300]}
|
||||
icon={HeartAdd}
|
||||
iconActive={HeartRemove}
|
||||
activeVariant="Bold"
|
||||
size={screenSize === 'xl' ? 24 : 18}
|
||||
isActive={props.liked}
|
||||
onPress={props.onLike}
|
||||
/>
|
||||
</Row>
|
||||
</Column>
|
||||
{[formattedLevel, formattedLastScore, formattedBestScore].map((value, index) => (
|
||||
<Text key={index} style={styles.stats}>
|
||||
{value}
|
||||
</Text>
|
||||
))}
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
export default MusicItem;
|
||||
// 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 MusicItem = memo(MusicItemComponent);
|
||||
|
||||
export default MusicItem;
|
||||
|
||||
@@ -1,40 +1,37 @@
|
||||
## IconButton
|
||||
|
||||
The `IconButton` is a responsive component displaying an interactive icon. It can toggle between active and inactive states, with customizable animations and styles for each state.
|
||||
The `IconButton` is a responsive component displaying an interactive icon. It can toggle between active and inactive states, with customizable animations and styles for each state.
|
||||
|
||||
### Features:
|
||||
|
||||
- Toggle between active and inactive icons.
|
||||
- Scale animation on press.
|
||||
- Full customization of icon colors, sizes, and variants.
|
||||
- Customizable styles for both the icon and its container.
|
||||
- Toggle between active and inactive icons.
|
||||
- Scale animation on press.
|
||||
- Full customization of icon colors, sizes, and variants.
|
||||
- Customizable styles for both the icon and its container.
|
||||
|
||||
### Preview
|
||||
|
||||
```jsx
|
||||
<IconButton
|
||||
color="#000"
|
||||
icon={SomeIcon}
|
||||
onPress={() => console.log('Icon pressed!')}
|
||||
/>
|
||||
<IconButton color="#000" icon={SomeIcon} onPress={() => console.log('Icon pressed!')} />
|
||||
```
|
||||
|
||||
With active states:
|
||||
|
||||
```jsx
|
||||
<IconButton
|
||||
isActive={true}
|
||||
color="#000"
|
||||
colorActive="#ff0000"
|
||||
icon={SomeIcon}
|
||||
iconActive={SomeActiveIcon}
|
||||
onPress={() => console.log('Icon toggled!')}
|
||||
<IconButton
|
||||
isActive={true}
|
||||
color="#000"
|
||||
colorActive="#ff0000"
|
||||
icon={SomeIcon}
|
||||
iconActive={SomeActiveIcon}
|
||||
onPress={() => console.log('Icon toggled!')}
|
||||
/>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
| Prop | Type | Description | Default Value |
|
||||
|-------------------|--------------|----------------------------------------------------|----------------|
|
||||
| ----------------- | ------------ | -------------------------------------------------- | -------------- |
|
||||
| isActive | boolean | Indicates if the button starts in an active state. | false |
|
||||
| color | string | Color of the icon. | - |
|
||||
| colorActive | string | Optional active state color for the icon. | Value of color |
|
||||
@@ -50,38 +47,37 @@ With active states:
|
||||
| style | object/style | Custom style for the icon. | - |
|
||||
| containerStyle | object/style | Custom style for the icon's container. | - |
|
||||
|
||||
|
||||
## MusicItem
|
||||
|
||||
The MusicItem is a responsive component designed to display individual music tracks with key details and interactive buttons, adapting its layout and design across various screen sizes.
|
||||
|
||||
### Features:
|
||||
|
||||
- Displays artist name, song title, and track cover image.
|
||||
- Provides interactivity through a play button and a like button.
|
||||
- Indicates song difficulty level, last score, and best score with automatic number formatting based on user's language preference.
|
||||
- Optimized for performance, ensuring smooth rendering even in extensive lists.
|
||||
- Displays artist name, song title, and track cover image.
|
||||
- Provides interactivity through a play button and a like button.
|
||||
- Indicates song difficulty level, last score, and best score with automatic number formatting based on user's language preference.
|
||||
- Optimized for performance, ensuring smooth rendering even in extensive lists.
|
||||
|
||||
### Preview
|
||||
|
||||
```jsx
|
||||
<MusicItem
|
||||
artist="John Doe"
|
||||
song="A Beautiful Song"
|
||||
image="https://example.com/image.jpg"
|
||||
level={5}
|
||||
lastScore={3200}
|
||||
bestScore={5000}
|
||||
liked={true}
|
||||
onLike={() => console.log('Music liked!')}
|
||||
onPlay={() => console.log('Play music!')}
|
||||
<MusicItem
|
||||
artist="John Doe"
|
||||
song="A Beautiful Song"
|
||||
image="https://example.com/image.jpg"
|
||||
level={5}
|
||||
lastScore={3200}
|
||||
bestScore={5000}
|
||||
liked={true}
|
||||
onLike={() => console.log('Music liked!')}
|
||||
onPlay={() => console.log('Play music!')}
|
||||
/>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
| Prop | Type | Description | Default Value |
|
||||
|-----------|----------|-------------------------------------------------------|---------------|
|
||||
| --------- | -------- | ----------------------------------------------------- | ------------- |
|
||||
| artist | string | Artist's name. | - |
|
||||
| song | string | Song's title. | - |
|
||||
| image | string | URL for the song's cover image. | - |
|
||||
|
||||
@@ -91,7 +91,11 @@ const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => {
|
||||
}}
|
||||
>
|
||||
<View style={!isSmallScreen ? { width: '100%' } : {}}>
|
||||
<Row space={2} flex={1} style={{ justifyContent: isSmallScreen ? 'center' : 'flex-start' }}>
|
||||
<Row
|
||||
space={2}
|
||||
flex={1}
|
||||
style={{ justifyContent: isSmallScreen ? 'center' : 'flex-start' }}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: props.logo }}
|
||||
style={{
|
||||
|
||||
@@ -37,14 +37,12 @@ const ScaffoldMobileCC = (props: ScaffoldMobileCCProps) => {
|
||||
maxHeight: '100%',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
padding: props.widthPadding ? 8 : 0
|
||||
padding: props.widthPadding ? 8 : 0,
|
||||
}}
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
>
|
||||
<View style={{ flex: 1 }}>
|
||||
{props.children}
|
||||
</View>
|
||||
<Spacer/>
|
||||
<View style={{ flex: 1 }}>{props.children}</View>
|
||||
<Spacer />
|
||||
</ScrollView>
|
||||
<View style={{ padding: 8, paddingTop: 0 }}>
|
||||
<Flex
|
||||
|
||||
@@ -61,7 +61,6 @@ export const en = {
|
||||
signinLinkLabel: "You don't have an account? ",
|
||||
signinLinkText: 'Sign up for free.',
|
||||
|
||||
|
||||
//music
|
||||
musicTabFavorites: 'Favorites',
|
||||
musicTabRecentlyPlayed: 'Recently Played',
|
||||
@@ -355,10 +354,10 @@ export const fr: typeof en = {
|
||||
signinLinkText: 'Inscrivez-vous gratuitement',
|
||||
|
||||
//music
|
||||
musicTabFavorites : 'Favoris',
|
||||
musicTabRecentlyPlayed : 'Récemment joué',
|
||||
musicTabStepUp : 'Recommandation',
|
||||
|
||||
musicTabFavorites: 'Favoris',
|
||||
musicTabRecentlyPlayed: 'Récemment joué',
|
||||
musicTabStepUp: 'Recommandation',
|
||||
|
||||
//search
|
||||
allFilter: 'Tout',
|
||||
artistFilter: 'Artistes',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Center, HStack, Row, Stack, Text, useBreakpointValue, useTheme } from 'native-base';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
import {
|
||||
@@ -9,7 +9,16 @@ import {
|
||||
Route,
|
||||
SceneRendererProps,
|
||||
} from 'react-native-tab-view';
|
||||
import { Heart, Clock, StatusUp, FolderCross, Chart2, ArrowRotateLeft, Cup, Icon } from 'iconsax-react-native';
|
||||
import {
|
||||
Heart,
|
||||
Clock,
|
||||
StatusUp,
|
||||
FolderCross,
|
||||
Chart2,
|
||||
ArrowRotateLeft,
|
||||
Cup,
|
||||
Icon,
|
||||
} from 'iconsax-react-native';
|
||||
import { Scene } from 'react-native-tab-view/lib/typescript/src/types';
|
||||
import useColorScheme from '../hooks/colorScheme';
|
||||
import { RouteProps } from '../Navigation';
|
||||
@@ -27,20 +36,28 @@ const MusicItemTitle = (props: MusicItemTitleProps) => {
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<Row style={{
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
maxWidth: props.isBigScreen ? 150 : 50,
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: props.isBigScreen ? 'flex-end' : 'center',
|
||||
|
||||
}}>
|
||||
{props.isBigScreen && <Text fontSize="lg" style={{paddingRight: 8}}>{props.text}</Text>}
|
||||
<props.icon size={18} color={colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}/>
|
||||
<Row
|
||||
style={{
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
maxWidth: props.isBigScreen ? 150 : 50,
|
||||
height: '100%',
|
||||
alignItems: 'center',
|
||||
justifyContent: props.isBigScreen ? 'flex-end' : 'center',
|
||||
}}
|
||||
>
|
||||
{props.isBigScreen && (
|
||||
<Text fontSize="lg" style={{ paddingRight: 8 }}>
|
||||
{props.text}
|
||||
</Text>
|
||||
)}
|
||||
<props.icon
|
||||
size={18}
|
||||
color={colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const FavoritesMusic = () => {
|
||||
const { colors } = useTheme();
|
||||
@@ -49,34 +66,62 @@ export const FavoritesMusic = () => {
|
||||
const isBigScreen = screenSize === 'xl';
|
||||
|
||||
return (
|
||||
<Stack style={{gap: 2, borderRadius: 10, overflow: 'hidden'}}>
|
||||
<HStack space={isSmallScreen ? 1 : 2} style={{backgroundColor: colors.coolGray[500], paddingHorizontal: isSmallScreen ? 8 : 16, paddingVertical: 12}}>
|
||||
<Text fontSize="lg" style={{flex: 4, width: '100%', justifyContent: 'center', paddingRight: 60}}>
|
||||
<Stack style={{ gap: 2, borderRadius: 10, overflow: 'hidden' }}>
|
||||
<HStack
|
||||
space={isSmallScreen ? 1 : 2}
|
||||
style={{
|
||||
backgroundColor: colors.coolGray[500],
|
||||
paddingHorizontal: isSmallScreen ? 8 : 16,
|
||||
paddingVertical: 12,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
fontSize="lg"
|
||||
style={{ flex: 4, width: '100%', justifyContent: 'center', paddingRight: 60 }}
|
||||
>
|
||||
Song
|
||||
</Text>
|
||||
{
|
||||
[{text: "level", icon: Chart2}, {text: "lastScore", icon: ArrowRotateLeft}, {text: "BastScore", icon: Cup}].map((value, index) => (
|
||||
<MusicItemTitle key={value.text + "key"} text={value.text} icon={value.icon} isBigScreen={isBigScreen}/>
|
||||
))
|
||||
}
|
||||
</HStack>
|
||||
{[
|
||||
{ text: 'level', icon: Chart2 },
|
||||
{ text: 'lastScore', icon: ArrowRotateLeft },
|
||||
{ text: 'BastScore', icon: Cup },
|
||||
].map((value) => (
|
||||
<MusicItemTitle
|
||||
key={value.text + 'key'}
|
||||
text={value.text}
|
||||
icon={value.icon}
|
||||
isBigScreen={isBigScreen}
|
||||
/>
|
||||
))}
|
||||
</HStack>
|
||||
<MusicItem
|
||||
image={"https://static.vecteezy.com/system/resources/previews/016/552/335/non_2x/luffy-kawai-chibi-cute-onepiece-anime-design-and-doodle-art-for-icon-logo-collection-and-others-free-vector.jpg"}
|
||||
image={
|
||||
'https://static.vecteezy.com/system/resources/previews/016/552/335/non_2x/luffy-kawai-chibi-cute-onepiece-anime-design-and-doodle-art-for-icon-logo-collection-and-others-free-vector.jpg'
|
||||
}
|
||||
liked={false}
|
||||
onLike={() => { } }
|
||||
onLike={() => {
|
||||
console.log('Liked !');
|
||||
}}
|
||||
level={3}
|
||||
lastScore={25550}
|
||||
bestScore={420}
|
||||
artist={'Ludwig van Beethoven'}
|
||||
song={'Piano Sonata No. 8'}
|
||||
/>
|
||||
/>
|
||||
<MusicItem
|
||||
image={"https://static.vecteezy.com/system/resources/previews/016/552/335/non_2x/luffy-kawai-chibi-cute-onepiece-anime-design-and-doodle-art-for-icon-logo-collection-and-others-free-vector.jpg"}
|
||||
image={
|
||||
'https://static.vecteezy.com/system/resources/previews/016/552/335/non_2x/luffy-kawai-chibi-cute-onepiece-anime-design-and-doodle-art-for-icon-logo-collection-and-others-free-vector.jpg'
|
||||
}
|
||||
liked={true}
|
||||
onLike={() => { } }
|
||||
onLike={() => {
|
||||
console.log('Liked !');
|
||||
}}
|
||||
level={3}
|
||||
lastScore={255500000}
|
||||
bestScore={42000} artist={'Ludwig van Beethoven'} song={'Sonata for Piano no. 20 in G major, op. 49 no. 2'} />
|
||||
bestScore={42000}
|
||||
artist={'Ludwig van Beethoven'}
|
||||
song={'Sonata for Piano no. 20 in G major, op. 49 no. 2'}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -113,11 +158,10 @@ const getTabData = (key: string) => {
|
||||
return { index: 2, icon: StatusUp };
|
||||
default:
|
||||
return { index: 3, icon: FolderCross };
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const SetttingsNavigator = (props: RouteProps<{}>) => {
|
||||
const MusicTab = (props: RouteProps<object>) => {
|
||||
const layout = useWindowDimensions();
|
||||
const [index, setIndex] = React.useState(0);
|
||||
const colorScheme = useColorScheme();
|
||||
@@ -139,8 +183,8 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
|
||||
borderBottomWidth: 1,
|
||||
borderColor: colors.primary[300],
|
||||
}}
|
||||
activeColor={ colorScheme === 'light' ? '#000' : '#fff'}
|
||||
inactiveColor={ colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
|
||||
activeColor={colorScheme === 'light' ? '#000' : '#fff'}
|
||||
inactiveColor={colorScheme === 'light' ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.7)'}
|
||||
indicatorStyle={{ backgroundColor: colors.primary[300] }}
|
||||
renderIcon={(
|
||||
scene: Scene<Route> & {
|
||||
@@ -150,13 +194,22 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
|
||||
) => {
|
||||
const tabHeader = getTabData(scene.route!.key);
|
||||
return (
|
||||
<tabHeader.icon size="18" color="#6075F9" variant={scene.focused ? "Bold" : "Outline"} />
|
||||
<tabHeader.icon
|
||||
size="18"
|
||||
color="#6075F9"
|
||||
variant={scene.focused ? 'Bold' : 'Outline'}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
renderLabel={({ route, color }) =>
|
||||
layout.width > 800 && (
|
||||
<Text style={{ color: color, paddingLeft: 10, overflow: 'hidden' }}>
|
||||
{translate(route.title as 'musicTabFavorites' | 'musicTabRecentlyPlayed' | 'musicTabStepUp')}
|
||||
{translate(
|
||||
route.title as
|
||||
| 'musicTabFavorites'
|
||||
| 'musicTabRecentlyPlayed'
|
||||
| 'musicTabStepUp'
|
||||
)}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
@@ -173,7 +226,7 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
|
||||
padding: isSmallScreen ? 4 : 20,
|
||||
paddingTop: 32,
|
||||
// maxWidth: 850,
|
||||
width: '100%'
|
||||
width: '100%',
|
||||
}}
|
||||
// style={{ height: 'fit-content' }}
|
||||
renderTabBar={renderTabBar}
|
||||
@@ -186,4 +239,4 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SetttingsNavigator;
|
||||
export default MusicTab;
|
||||
|
||||
@@ -66,7 +66,7 @@ const getTabData = (key: string) => {
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const SetttingsNavigator = (props: RouteProps<{}>) => {
|
||||
const SettingsTab = (props: RouteProps<{}>) => {
|
||||
const layout = useWindowDimensions();
|
||||
const [index, setIndex] = React.useState(0);
|
||||
const colorScheme = useColorScheme();
|
||||
@@ -148,4 +148,4 @@ const SetttingsNavigator = (props: RouteProps<{}>) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SetttingsNavigator;
|
||||
export default SettingsTab;
|
||||
|
||||
Reference in New Issue
Block a user