[add] Scaffold for mobile & desktop
This commit is contained in:
@@ -34,7 +34,7 @@ import SigninView from './views/SigninView';
|
||||
import SignupView from './views/SignupView';
|
||||
import PasswordResetView from './views/PasswordResetView';
|
||||
import ForgotPasswordView from './views/ForgotPasswordView';
|
||||
import ScaffoldCC from './components/UI/Scaffold';
|
||||
import ScaffoldCC from './components/UI/ScaffoldCC';
|
||||
import DiscoveryView from './views/V2/DiscoveryView';
|
||||
|
||||
// Util function to hide route props in URL
|
||||
|
||||
87
front/components/UI/ScaffoldCC.tsx
Normal file
87
front/components/UI/ScaffoldCC.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { View, Image } from 'react-native';
|
||||
import { Divider, Text, ScrollView, Flex, Row, Popover, Heading, Button, useBreakpointValue } from 'native-base';
|
||||
import useColorScheme from '../../hooks/colorScheme';
|
||||
import { useQuery, useQueries } from '../../Queries';
|
||||
import API from '../../API';
|
||||
import Song from '../../models/Song';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import ButtonBase from './ButtonBase';
|
||||
import {
|
||||
Cup,
|
||||
Discover,
|
||||
Icon,
|
||||
LogoutCurve,
|
||||
Music,
|
||||
SearchNormal1,
|
||||
Setting2,
|
||||
User,
|
||||
} from 'iconsax-react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { LoadingView } from '../Loading';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { unsetAccessToken } from '../../state/UserSlice';
|
||||
import { useNavigation } from '../../Navigation';
|
||||
import Spacer from './Spacer';
|
||||
import ScaffoldDesktopCC from './ScaffoldDesktopCC';
|
||||
import ScaffoldMobileCC from './ScaffoldMobileCC';
|
||||
|
||||
const menu: {
|
||||
type: "main" | "sub";
|
||||
title: string;
|
||||
icon: Icon;
|
||||
link: string;
|
||||
}[] = [
|
||||
{ type: "main", title: 'Discovery', icon: Discover, link: 'HomeNew' },
|
||||
{ type: "main", title: 'Profile', icon: User, link: 'User' },
|
||||
{ type: "main", title: 'Music', icon: Music, link: 'Home' },
|
||||
{ type: "main", title: 'Search', icon: SearchNormal1, link: 'Search' },
|
||||
{ type: "main", title: 'LeaderBoard', icon: Cup, link: 'Score' },
|
||||
{ type: "sub", title: 'Settings', icon: Setting2, link: 'Settings' },
|
||||
];
|
||||
|
||||
type ScaffoldCCProps = {
|
||||
children?: React.ReactNode;
|
||||
routeName: string;
|
||||
};
|
||||
|
||||
const ScaffoldCC = (props: ScaffoldCCProps) => {
|
||||
const screenSize = useBreakpointValue({ base: 'small', md: 'big' });
|
||||
|
||||
const userQuery = useQuery(API.getUserInfo);
|
||||
|
||||
if (!userQuery.data || userQuery.isLoading) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
|
||||
if (screenSize === 'small') {
|
||||
return (
|
||||
<ScaffoldMobileCC
|
||||
user={userQuery.data}
|
||||
logo={colorScheme == 'light'
|
||||
? require('../../assets/icon_light.png')
|
||||
: require('../../assets/icon_dark.png')}
|
||||
routeName={props.routeName}
|
||||
menu={menu}
|
||||
>
|
||||
{props.children}
|
||||
</ScaffoldMobileCC>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScaffoldDesktopCC
|
||||
user={userQuery.data}
|
||||
logo={colorScheme == 'light'
|
||||
? require('../../assets/icon_light.png')
|
||||
: require('../../assets/icon_dark.png')}
|
||||
routeName={props.routeName}
|
||||
menu={menu}
|
||||
>
|
||||
{props.children}
|
||||
</ScaffoldDesktopCC>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScaffoldCC;
|
||||
257
front/components/UI/ScaffoldDesktopCC.tsx
Normal file
257
front/components/UI/ScaffoldDesktopCC.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
import { View, Image } from 'react-native';
|
||||
import { Divider, Text, ScrollView, Flex, Row, Popover, Heading, Button } from 'native-base';
|
||||
import { useQuery, useQueries } from '../../Queries';
|
||||
import API from '../../API';
|
||||
import Song from '../../models/Song';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import ButtonBase from './ButtonBase';
|
||||
import { Icon, LogoutCurve } from 'iconsax-react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { LoadingView } from '../Loading';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import { unsetAccessToken } from '../../state/UserSlice';
|
||||
import { useNavigation } from '../../Navigation';
|
||||
import Spacer from './Spacer';
|
||||
import User from '../../models/User';
|
||||
|
||||
type ScaffoldDesktopCCProps = {
|
||||
children?: React.ReactNode;
|
||||
user: User;
|
||||
logo: string;
|
||||
routeName: string;
|
||||
menu: {
|
||||
type: "main" | "sub";
|
||||
title: string;
|
||||
icon: Icon;
|
||||
link: string;
|
||||
}[]
|
||||
};
|
||||
|
||||
const ScaffoldDesktopCC = (props: ScaffoldDesktopCCProps) => {
|
||||
const navigation = useNavigation();
|
||||
const userQuery = useQuery(API.getUserInfo);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (!userQuery.data || userQuery.isLoading) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
const playHistoryQuery = useQuery(API.getUserPlayHistory);
|
||||
const songHistory = useQueries(
|
||||
playHistoryQuery.data?.map(({ songID }) => API.getSong(songID)) ?? []
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<View style={{ height: '100%', flexDirection: 'row', overflow: 'hidden' }}>
|
||||
<View
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '300px',
|
||||
height: '100vh',
|
||||
maxHeight: '100vh',
|
||||
padding: '32px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<View style={{ width: '100%' }}>
|
||||
<Row>
|
||||
<Image
|
||||
source={{ uri: props.logo }}
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
width: '40px',
|
||||
height: 'auto',
|
||||
marginRight: '10px',
|
||||
}}
|
||||
/>
|
||||
<Spacer width="xs" />
|
||||
<Text fontSize={'2xl'} selectable={false}>
|
||||
Chromacase
|
||||
</Text>
|
||||
</Row>
|
||||
<Spacer height="xl" />
|
||||
<View style={{ width: '100%' }}>
|
||||
{props.menu.map((value) => (
|
||||
value.type === "main" &&
|
||||
<View key={'key-menu-link-' + value.title}>
|
||||
<ButtonBase
|
||||
style={{ width: '100%' }}
|
||||
type="menu"
|
||||
icon={value.icon}
|
||||
title={value.title}
|
||||
isDisabled={props.routeName === value.link}
|
||||
iconVariant={
|
||||
props.routeName === value.link ? 'Bold' : 'Outline'
|
||||
}
|
||||
onPress={async () =>
|
||||
navigation.navigate(value.link as never)
|
||||
}
|
||||
/>
|
||||
<Spacer />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ width: '100%' }}>
|
||||
<Divider />
|
||||
<Spacer />
|
||||
<Text
|
||||
bold
|
||||
style={{
|
||||
paddingHorizontal: '16px',
|
||||
paddingVertical: '10px',
|
||||
fontSize: 20,
|
||||
}}
|
||||
>
|
||||
Recently played
|
||||
</Text>
|
||||
{songHistory.length === 0 && (
|
||||
<Text
|
||||
style={{
|
||||
paddingHorizontal: '16px',
|
||||
paddingVertical: '10px',
|
||||
}}
|
||||
>
|
||||
No songs played yet
|
||||
</Text>
|
||||
)}
|
||||
{songHistory
|
||||
.map((h) => h.data)
|
||||
.filter((data): data is Song => data !== undefined)
|
||||
.filter(
|
||||
(song, i, array) =>
|
||||
array.map((s) => s.id).findIndex((id) => id == song.id) == i
|
||||
)
|
||||
.slice(0, 4)
|
||||
.map((histoItem, index) => (
|
||||
<View
|
||||
key={'tab-navigation-other-' + index}
|
||||
style={{
|
||||
paddingHorizontal: '16px',
|
||||
paddingVertical: '10px',
|
||||
}}
|
||||
>
|
||||
<Text numberOfLines={1}>{histoItem.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<Spacer />
|
||||
<View style={{ width: '100%' }}>
|
||||
<Divider />
|
||||
<Spacer />
|
||||
{props.menu.map((value) => (
|
||||
value.type === "sub" &&
|
||||
<ButtonBase
|
||||
key={'key-menu-link-' + value.title}
|
||||
style={{ width: '100%' }}
|
||||
type="menu"
|
||||
icon={value.icon}
|
||||
title={value.title}
|
||||
isDisabled={props.routeName === value.link}
|
||||
iconVariant={
|
||||
props.routeName === value.link ? 'Bold' : 'Outline'
|
||||
}
|
||||
onPress={async () =>
|
||||
navigation.navigate(value.link as never)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<Spacer />
|
||||
{!props.user.isGuest && (
|
||||
<ButtonBase
|
||||
style={{ width: '100%' }}
|
||||
icon={LogoutCurve}
|
||||
title={translate('signOutBtn')}
|
||||
type="menu"
|
||||
onPress={async () => {
|
||||
dispatch(unsetAccessToken());
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.user.isGuest && (
|
||||
<Popover
|
||||
trigger={(triggerProps) => (
|
||||
<ButtonBase {...triggerProps}>
|
||||
{translate('signOutBtn')}
|
||||
</ButtonBase>
|
||||
)}
|
||||
>
|
||||
<Popover.Content>
|
||||
<Popover.Arrow />
|
||||
<Popover.Body>
|
||||
<Heading size="md" mb={2}>
|
||||
{translate('Attention')}
|
||||
</Heading>
|
||||
<Text>
|
||||
{translate(
|
||||
'YouAreCurrentlyConnectedWithAGuestAccountWarning'
|
||||
)}
|
||||
</Text>
|
||||
<Button.Group variant="ghost" space={2}>
|
||||
<Button
|
||||
onPress={() => dispatch(unsetAccessToken())}
|
||||
colorScheme="red"
|
||||
>
|
||||
{translate('signOutBtn')}
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => {
|
||||
navigation.navigate('Login');
|
||||
}}
|
||||
colorScheme="green"
|
||||
>
|
||||
{translate('signUpBtn')}
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Popover.Body>
|
||||
</Popover.Content>
|
||||
</Popover>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
<ScrollView
|
||||
style={{ flex: 1, maxHeight: '100vh' }}
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: 'rgba(16,16,20,0.5)',
|
||||
flex: 1,
|
||||
margin: 8,
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
minHeight: 'fit-content',
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</View>
|
||||
<Spacer/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
<LinearGradient
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
colors={['#101014', '#6075F9']}
|
||||
style={{
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minHeight: 'fit-content',
|
||||
minWidth: 'fit-content',
|
||||
flex: 1,
|
||||
position: 'absolute',
|
||||
zIndex: -2,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScaffoldDesktopCC;
|
||||
100
front/components/UI/ScaffoldMobileCC.tsx
Normal file
100
front/components/UI/ScaffoldMobileCC.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { View, useWindowDimensions } from 'react-native';
|
||||
import { ScrollView, Flex, useBreakpointValue, useMediaQuery } from 'native-base';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import ButtonBase from './ButtonBase';
|
||||
import { Icon } from 'iconsax-react-native';
|
||||
import { useNavigation } from '../../Navigation';
|
||||
import Spacer from './Spacer';
|
||||
import User from '../../models/User';
|
||||
|
||||
type ScaffoldMobileCCProps = {
|
||||
children?: React.ReactNode;
|
||||
user: User;
|
||||
logo: string;
|
||||
routeName: string;
|
||||
menu: {
|
||||
type: "main" | "sub";
|
||||
title: string;
|
||||
icon: Icon;
|
||||
link: string;
|
||||
}[]
|
||||
};
|
||||
|
||||
const ScaffoldMobileCC = (props: ScaffoldMobileCCProps) => {
|
||||
const navigation = useNavigation();
|
||||
const [isSmallScreen] = useMediaQuery({ maxWidth: 400 });
|
||||
|
||||
|
||||
console.log(isSmallScreen);
|
||||
|
||||
return (
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<View style={{ height: '100%', flexDirection: 'column', overflow: 'hidden' }}>
|
||||
<ScrollView
|
||||
style={{
|
||||
flex: 1,
|
||||
maxHeight: '100vh',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
padding: 16
|
||||
}}
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
>
|
||||
<View style={{ flex: 1, minHeight: 'fit-content' }}>
|
||||
{props.children}
|
||||
</View>
|
||||
<Spacer/>
|
||||
</ScrollView>
|
||||
<View style={{padding: 8, paddingTop: 0}}>
|
||||
<Flex
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'fit-content',
|
||||
flexDirection: 'row',
|
||||
backgroundColor: 'rgba(16,16,20,0.5)',
|
||||
padding: 8,
|
||||
justifyContent: 'space-between',
|
||||
borderRadius: 8,
|
||||
}}
|
||||
>
|
||||
{props.menu.map((value) => (
|
||||
<ButtonBase
|
||||
key={'key-menu-link-' + value.title}
|
||||
type="menu"
|
||||
icon={value.icon}
|
||||
title={props.routeName === value.link && !isSmallScreen ? value.title : undefined}
|
||||
isDisabled={props.routeName === value.link}
|
||||
iconVariant={
|
||||
props.routeName === value.link ? 'Bold' : 'Outline'
|
||||
}
|
||||
onPress={async () =>
|
||||
navigation.navigate(value.link as never)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</View>
|
||||
</View>
|
||||
<LinearGradient
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
colors={['#101014', '#6075F9']}
|
||||
style={{
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
minHeight: 'fit-content',
|
||||
minWidth: 'fit-content',
|
||||
flex: 1,
|
||||
position: 'absolute',
|
||||
zIndex: -2,
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScaffoldMobileCC;
|
||||
@@ -40,7 +40,7 @@ const ProfileSettings = () => {
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
width: '90%',
|
||||
width: '100%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Center, Text } from 'native-base';
|
||||
import { Center, Flex, Text } from 'native-base';
|
||||
import ProfileSettings from './SettingsProfileView';
|
||||
import NotificationsView from './NotificationView';
|
||||
import PrivacyView from './PrivacyView';
|
||||
import PreferencesView from './PreferencesView';
|
||||
import { View, useWindowDimensions } from 'react-native';
|
||||
import { useWindowDimensions } from 'react-native';
|
||||
import {
|
||||
TabView,
|
||||
SceneMap,
|
||||
@@ -110,7 +110,7 @@ const SetttingsNavigator = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ width: '100%' }}>
|
||||
<Flex style={{ flex: 1 }}>
|
||||
<TabView
|
||||
style={{ height: 'fit-content' }}
|
||||
renderTabBar={renderTabBar}
|
||||
@@ -119,7 +119,7 @@ const SetttingsNavigator = () => {
|
||||
onIndexChange={setIndex}
|
||||
initialLayout={{ width: layout.width }}
|
||||
/>
|
||||
</View>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user