[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 SignupView from './views/SignupView';
|
||||||
import PasswordResetView from './views/PasswordResetView';
|
import PasswordResetView from './views/PasswordResetView';
|
||||||
import ForgotPasswordView from './views/ForgotPasswordView';
|
import ForgotPasswordView from './views/ForgotPasswordView';
|
||||||
import ScaffoldCC from './components/UI/Scaffold';
|
import ScaffoldCC from './components/UI/ScaffoldCC';
|
||||||
import DiscoveryView from './views/V2/DiscoveryView';
|
import DiscoveryView from './views/V2/DiscoveryView';
|
||||||
|
|
||||||
// Util function to hide route props in URL
|
// Util function to hide route props in URL
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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
|
<ElementList
|
||||||
style={{
|
style={{
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: '90%',
|
width: '100%',
|
||||||
maxWidth: 850,
|
maxWidth: 850,
|
||||||
}}
|
}}
|
||||||
elements={[
|
elements={[
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Center, Text } from 'native-base';
|
import { Center, Flex, Text } from 'native-base';
|
||||||
import ProfileSettings from './SettingsProfileView';
|
import ProfileSettings from './SettingsProfileView';
|
||||||
import NotificationsView from './NotificationView';
|
import NotificationsView from './NotificationView';
|
||||||
import PrivacyView from './PrivacyView';
|
import PrivacyView from './PrivacyView';
|
||||||
import PreferencesView from './PreferencesView';
|
import PreferencesView from './PreferencesView';
|
||||||
import { View, useWindowDimensions } from 'react-native';
|
import { useWindowDimensions } from 'react-native';
|
||||||
import {
|
import {
|
||||||
TabView,
|
TabView,
|
||||||
SceneMap,
|
SceneMap,
|
||||||
@@ -110,7 +110,7 @@ const SetttingsNavigator = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ width: '100%' }}>
|
<Flex style={{ flex: 1 }}>
|
||||||
<TabView
|
<TabView
|
||||||
style={{ height: 'fit-content' }}
|
style={{ height: 'fit-content' }}
|
||||||
renderTabBar={renderTabBar}
|
renderTabBar={renderTabBar}
|
||||||
@@ -119,7 +119,7 @@ const SetttingsNavigator = () => {
|
|||||||
onIndexChange={setIndex}
|
onIndexChange={setIndex}
|
||||||
initialLayout={{ width: layout.width }}
|
initialLayout={{ width: layout.width }}
|
||||||
/>
|
/>
|
||||||
</View>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user