[MOB-47] Location screen and header updates (#2056)

* Location screen and header updates

* use tw sizing

* remove un-necessary prop

* Nit: change name
This commit is contained in:
ameer2468 2024-02-06 06:03:33 +03:00 committed by GitHub
parent 58f9305965
commit 2d0c340e58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 200 additions and 50 deletions

View file

@ -18,6 +18,7 @@ import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
import FolderIcon from '../icons/FolderIcon';
import { Icon } from '../icons/Icon';
import Fade from '../layout/Fade';
import ImportModal from '../modal/ImportModal';
import { LocationModal } from '../modal/location/LocationModal';
@ -39,7 +40,7 @@ const BrowseLocationItem: React.FC<BrowseLocationItemProps> = ({
return (
<Pressable onPress={onPress}>
<View
style={tw`h-auto w-[100px] flex-col justify-center gap-3 rounded-md border border-sidebar-line/50 bg-sidebar-box p-2`}
style={tw`h-auto w-[110px] flex-col justify-center gap-3 rounded-md border border-sidebar-line/50 bg-sidebar-box p-2`}
>
<View style={tw`w-full flex-col justify-between gap-1`}>
<View style={tw`flex-row items-center justify-between`}>
@ -61,7 +62,7 @@ const BrowseLocationItem: React.FC<BrowseLocationItemProps> = ({
</Pressable>
</View>
<Text
style={tw`w-full max-w-[75px] text-xs font-bold text-white`}
style={tw`w-full max-w-[100px] text-xs font-bold text-white`}
numberOfLines={1}
>
{location.name}
@ -98,14 +99,19 @@ const BrowseLocations = () => {
return (
<View style={tw`gap-5`}>
<View style={tw`w-full flex-row items-center justify-between px-7`}>
<Text style={tw`text-xl font-bold text-white`}>Locations</Text>
<Text style={tw`text-lg font-bold text-white`}>Locations</Text>
<View style={tw`flex-row gap-3`}>
<Pressable
disabled={result.data?.nodes.length === 0}
onPress={() => {
navigation.navigate('Locations');
}}
>
<View style={tw`h-8 w-8 items-center justify-center rounded-md bg-accent`}>
<View
style={tw`h-8 w-8 items-center justify-center rounded-md bg-accent ${
result.data?.nodes.length === 0 ? 'opacity-40' : 'opacity-100'
}`}
>
<Eye weight="bold" size={18} style={tw`text-white`} />
</View>
</Pressable>
@ -121,6 +127,16 @@ const BrowseLocations = () => {
<Fade color="mobile-screen" width={30} height="100%">
<FlatList
data={locations}
ListEmptyComponent={() => (
<View
style={tw`relative h-auto w-[85.5vw] flex-col items-center justify-center overflow-hidden rounded-md border border-dashed border-sidebar-line p-4`}
>
<Icon name="Folder" size={38} />
<Text style={tw`mt-2 text-center font-medium text-ink-dull`}>
You have no locations
</Text>
</View>
)}
contentContainerStyle={tw`px-7`}
showsHorizontalScrollIndicator={false}
ItemSeparatorComponent={() => <View style={tw`w-2`} />}

View file

@ -8,6 +8,7 @@ import { ModalRef } from '~/components/layout/Modal';
import { tw, twStyle } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { Icon } from '../icons/Icon';
import Fade from '../layout/Fade';
import CreateTagModal from '../modal/tag/CreateTagModal';
import { TagModal } from '../modal/tag/TagModal';
@ -63,10 +64,14 @@ const BrowseTags = () => {
return (
<View style={tw`gap-5`}>
<View style={tw`w-full flex-row items-center justify-between px-7`}>
<Text style={tw`text-xl font-bold text-white`}>Tags</Text>
<Text style={tw`text-lg font-bold text-white`}>Tags</Text>
<View style={tw`flex-row gap-3`}>
<Pressable>
<View style={tw`h-8 w-8 items-center justify-center rounded-md bg-accent`}>
<View
style={tw`h-8 w-8 items-center justify-center rounded-md bg-accent ${
tags.data?.nodes.length === 0 ? 'opacity-40' : 'opacity-100'
}`}
>
<Eye weight="bold" size={18} style={tw`text-white`} />
</View>
</Pressable>
@ -82,10 +87,22 @@ const BrowseTags = () => {
<Fade color="mobile-screen" width={30} height="100%">
<FlatList
data={tagData}
ListEmptyComponent={() => (
<View
style={tw`relative h-auto w-[85.5vw] flex-col items-center justify-center overflow-hidden rounded-md border border-dashed border-sidebar-line p-4`}
>
<Icon name="Tags" size={38} />
<Text style={tw`mt-2 text-center font-medium text-ink-dull`}>
You have no tags
</Text>
</View>
)}
renderItem={({ item }) => (
<BrowseTagItem
tag={item}
onPress={() => navigation.navigate('Tag', { id: item.id })}
onPress={() =>
navigation.navigate('Tag', { id: item.id, color: item.color! })
}
/>
)}
keyExtractor={(item) => item.id.toString()}

View file

@ -29,7 +29,7 @@ const CATEGORIES_LIST = [
const Categories = () => {
return (
<View style={tw`relative gap-5`}>
<Text style={tw`px-7 text-xl font-bold text-white`}>Library</Text>
<Text style={tw`text-lg font-bold text-white px-7`}>Library</Text>
<Fade width={30} height="100%" color="mobile-screen">
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<View style={tw`flex-row gap-2 px-7`}>

View file

@ -11,7 +11,7 @@ const Jobs = () => {
return (
<View style={tw`gap-5`}>
<View style={tw`w-full flex-row items-center justify-between px-7`}>
<Text style={tw`text-xl font-bold text-white`}>Jobs</Text>
<Text style={tw`text-lg font-bold text-white`}>Jobs</Text>
</View>
<Fade color="mobile-screen" height="100%" width={30}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
@ -45,7 +45,7 @@ const Job = ({ progress, message, error }: JobProps) => {
: tw.color('accent');
return (
<View
style={tw`h-auto w-[310px] flex-col rounded-md border border-sidebar-line/50 bg-sidebar-box`}
style={tw`h-[170px] w-[310px] flex-col rounded-md border border-sidebar-line/50 bg-sidebar-box`}
>
<View
style={tw`w-full flex-row items-center justify-between rounded-t-md border-b border-sidebar-line/80 bg-mobile-header/50 px-5 py-2`}
@ -56,7 +56,7 @@ const Job = ({ progress, message, error }: JobProps) => {
</View>
<DotsThreeOutlineVertical weight="fill" size={20} color={tw.color('ink-faint')} />
</View>
<View style={tw`mx-auto flex-1 flex-row items-center justify-between gap-5 px-5 py-2`}>
<View style={tw`mx-auto flex-1 flex-row items-center justify-between gap-5 px-5 py-3`}>
<AnimatedCircularProgress
size={80}
width={7}

View file

@ -1,14 +1,14 @@
import { useNavigation } from '@react-navigation/native';
import { FlashList } from '@shopify/flash-list';
import { Rows, SquaresFour } from 'phosphor-react-native';
import { AnimatePresence, MotiView } from 'moti';
import { MonitorPlay, Rows, SlidersHorizontal, SquaresFour } from 'phosphor-react-native';
import { useState } from 'react';
import { Pressable, View } from 'react-native';
import { isPath, type ExplorerItem } from '@sd/client';
import SortByMenu from '~/components/menu/SortByMenu';
import Layout from '~/constants/Layout';
import { tw } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { getExplorerStore } from '~/stores/explorerStore';
import { ExplorerLayoutMode, getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
import { useActionsModalStore } from '~/stores/modalStore';
import FileItem from './FileItem';
@ -20,10 +20,10 @@ type ExplorerProps = {
const Explorer = ({ items }: ExplorerProps) => {
const navigation = useNavigation<BrowseStackScreenProps<'Location'>['navigation']>();
const explorerStore = useExplorerStore();
const [layoutMode, setLayoutMode] = useState<ExplorerLayoutMode>(getExplorerStore().layoutMode);
const [layoutMode, setLayoutMode] = useState<'grid' | 'list'>(getExplorerStore().layoutMode);
function changeLayoutMode(kind: 'grid' | 'list') {
function changeLayoutMode(kind: ExplorerLayoutMode) {
// We need to keep layoutMode as a state to make sure flash-list re-renders.
setLayoutMode(kind);
getExplorerStore().layoutMode = kind;
@ -44,13 +44,30 @@ const Explorer = ({ items }: ExplorerProps) => {
}
return (
<View style={tw`flex-1`}>
<View style={tw`flex-1 bg-mobile-screen`}>
{/* Header */}
<View style={tw`flex flex-row items-center justify-between p-3`}>
<View style={tw`flex flex-row items-center justify-between`}>
{/* Sort By */}
<SortByMenu />
{/* <SortByMenu /> */}
<AnimatePresence>
{explorerStore.toggleMenu && (
<MotiView
from={{ translateY: -70 }}
animate={{ translateY: 0 }}
transition={{ type: 'timing', duration: 300 }}
exit={{ translateY: -70 }}
>
<ExplorerMenu
changeLayoutMode={(kind: ExplorerLayoutMode) => {
changeLayoutMode(kind);
}}
layoutMode={layoutMode}
/>
</MotiView>
)}
</AnimatePresence>
{/* Layout (Grid/List) */}
{layoutMode === 'grid' ? (
{/* {layoutMode === 'grid' ? (
<Pressable onPress={() => changeLayoutMode('list')}>
<SquaresFour color={tw.color('ink')} size={23} />
</Pressable>
@ -58,7 +75,7 @@ const Explorer = ({ items }: ExplorerProps) => {
<Pressable onPress={() => changeLayoutMode('grid')}>
<Rows color={tw.color('ink')} size={23} />
</Pressable>
)}
)} */}
</View>
{/* Items */}
{items && (
@ -94,4 +111,39 @@ const Explorer = ({ items }: ExplorerProps) => {
);
};
interface ExplorerMenuProps {
layoutMode: ExplorerLayoutMode;
changeLayoutMode: (kind: ExplorerLayoutMode) => void;
}
const ExplorerMenu = ({ layoutMode, changeLayoutMode }: ExplorerMenuProps) => {
return (
<View
style={tw`w-screen flex-row justify-between border-b border-app-line/50 bg-mobile-header px-7 py-4`}
>
<View style={tw`flex-row gap-3`}>
<Pressable onPress={() => changeLayoutMode('grid')}>
<SquaresFour
color={tw.color(layoutMode === 'grid' ? 'text-accent' : 'text-ink-dull')}
size={23}
/>
</Pressable>
<Pressable onPress={() => changeLayoutMode('list')}>
<Rows
color={tw.color(layoutMode === 'list' ? 'text-accent' : 'text-ink-dull')}
size={23}
/>
</Pressable>
<Pressable onPress={() => changeLayoutMode('media')}>
<MonitorPlay
color={tw.color(layoutMode === 'media' ? 'text-accent' : 'text-ink-dull')}
size={23}
/>
</Pressable>
</View>
<SlidersHorizontal style={tw`text-ink-dull`} />
</View>
);
};
export default Explorer;

View file

@ -1,24 +1,48 @@
import { useNavigation } from '@react-navigation/native';
import { ArrowLeft, MagnifyingGlass } from 'phosphor-react-native';
import { StackHeaderProps } from '@react-navigation/stack';
import { ArrowLeft, DotsThreeOutline, MagnifyingGlass } from 'phosphor-react-native';
import { lazy } from 'react';
import { Pressable, Text, View } from 'react-native';
import { tw } from '~/lib/tailwind';
import { tw, twStyle } from '~/lib/tailwind';
import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
import { Icon } from '../icons/Icon';
//Not all pages use these components - so we lazy load for performance
const BrowseLibraryManager = lazy(() => import('../browse/DrawerLibraryManager'));
const Search = lazy(() => import('../inputs/Search'));
interface Props {
type HeaderProps = {
title?: string; //title of the page
showLibrary?: boolean; //show the library manager
searchType?: 'explorer' | 'location'; //Temporary
navBack?: boolean; //navigate back to the previous screen
}
headerKind?: 'default' | 'location' | 'tag'; //kind of header
route?: never;
routeTitle?: never;
};
//you can pass in a routeTitle only if route is passed in
type Props =
| HeaderProps
| ({
route: StackHeaderProps;
routeTitle?: boolean;
} & Omit<HeaderProps, 'route' | 'routeTitle'>);
// Default header with search bar and button to open drawer
export default function Header({ title, showLibrary, searchType, navBack }: Props) {
export default function Header({
title,
showLibrary,
searchType,
navBack,
route,
routeTitle,
headerKind = 'default'
}: Props) {
const navigation = useNavigation();
const explorerStore = useExplorerStore();
const routeParams = route?.route.params as any;
const SearchType = () => {
switch (searchType) {
case 'explorer':
@ -29,6 +53,22 @@ export default function Header({ title, showLibrary, searchType, navBack }: Prop
return null;
}
};
const HeaderIconKind = () => {
switch (headerKind) {
case 'location':
return <Icon size={32} name="Folder" />;
case 'tag':
return (
<View
style={twStyle('h-6 w-6 rounded-full', {
backgroundColor: routeParams.color
})}
/>
);
default:
return null;
}
};
return (
<View style={tw`relative h-auto w-full border-b border-app-line/50 bg-mobile-header pt-10`}>
@ -44,16 +84,41 @@ export default function Header({ title, showLibrary, searchType, navBack }: Prop
<ArrowLeft size={23} color={tw.color('ink')} />
</Pressable>
)}
<Text style={tw`text-[24px] font-bold text-white`}>{title}</Text>
<View style={tw`flex-row items-center gap-2`}>
<HeaderIconKind />
<Text
numberOfLines={1}
style={tw`max-w-[190px] text-lg font-bold text-white`}
>
{title || (routeTitle && route?.options.title)}
</Text>
</View>
</View>
<View style={tw`flex-row items-center gap-3`}>
<Pressable onPress={() => navigation.navigate('Search')}>
<MagnifyingGlass
size={20}
weight="bold"
color={tw.color('text-zinc-300')}
/>
</Pressable>
{(headerKind === 'location' || headerKind === 'tag') && (
<Pressable
onPress={() => {
getExplorerStore().toggleMenu = !explorerStore.toggleMenu;
}}
>
<DotsThreeOutline
size={24}
color={tw.color(
explorerStore.toggleMenu ? 'text-accent' : 'text-zinc-300'
)}
/>
</Pressable>
)}
</View>
<Pressable onPress={() => navigation.navigate('Search')}>
<MagnifyingGlass
size={20}
weight="bold"
color={tw.color('text-zinc-300')}
/>
</Pressable>
</View>
{showLibrary && <BrowseLibraryManager style="mt-4" />}
{searchType && <SearchType />}
</View>

View file

@ -34,8 +34,10 @@ export function SettingsItem(props: SettingsItemProps) {
)}
<View
style={twStyle(
`flex-1 flex-row items-center justify-between py-4`,
borderRounded !== 'rounded-b-md' && 'border-b border-app-input'
`flex-1 flex-row items-center justify-between border-b py-4`,
borderRounded !== 'rounded-b-md'
? 'border-app-input'
: 'border-transparent'
)}
>
<Text style={tw`text-sm font-medium text-ink`}>{props.title}</Text>

View file

@ -1,6 +1,5 @@
import { CompositeScreenProps } from '@react-navigation/native';
import { createStackNavigator, StackScreenProps } from '@react-navigation/stack';
import { ArrowLeft } from 'phosphor-react-native';
import Header from '~/components/header/Header';
import { tw } from '~/lib/tailwind';
import BrowseScreen from '~/screens/browse';
@ -32,8 +31,8 @@ export default function BrowseStack() {
name="Location"
component={LocationScreen}
options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
header: (route) => (
<Header route={route} headerKind="location" routeTitle navBack />
)
}}
/>
@ -48,9 +47,7 @@ export default function BrowseStack() {
name="Tag"
component={TagScreen}
options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
header: (route) => <Header routeTitle route={route} headerKind="tag" navBack />
}}
/>
</Stack.Navigator>
@ -61,7 +58,7 @@ export type BrowseStackParamList = {
Browse: undefined;
Location: { id: number; path?: string };
Locations: undefined;
Tag: { id: number };
Tag: { id: number; color: string };
};
export type BrowseStackScreenProps<Screen extends keyof BrowseStackParamList> =

View file

@ -105,7 +105,7 @@ const LocationItem: React.FC<LocationItemProps> = ({
/>
</View>
<Text
style={tw`w-auto max-w-[160px] truncate text-sm font-bold text-white`}
style={tw`w-auto max-w-[160px] text-sm font-bold text-white`}
numberOfLines={1}
>
{location.name}
@ -114,7 +114,7 @@ const LocationItem: React.FC<LocationItemProps> = ({
<View style={tw`flex-row items-center gap-3`}>
<View style={tw`rounded-md bg-app-input p-1.5`}>
<Text
style={tw`truncate text-left text-xs font-bold text-ink-dull`}
style={tw`text-left text-xs font-bold text-ink-dull`}
numberOfLines={1}
>
{`${byteSize(location.size_in_bytes)}`}

View file

@ -23,7 +23,7 @@ export default function BrowseScreen() {
const height = useBottomTabBarHeight();
return (
<ScrollView style={twStyle('flex-1 bg-mobile-screen', { marginBottom: height })}>
<View style={twStyle('justify-between gap-3 py-5')}>
<View style={twStyle('justify-between gap-6 py-5')}>
{/*Categories*/}
<Categories />
{/* Locations */}

View file

@ -1,9 +1,9 @@
import { resetStore } from '@sd/client';
import { proxy, useSnapshot } from 'valtio';
import { proxySet } from 'valtio/utils';
import { resetStore } from '@sd/client';
// TODO: Add "media"
export type ExplorerLayoutMode = 'list' | 'grid';
export type ExplorerLayoutMode = 'list' | 'grid' | "media"
export type ExplorerKind = 'Location' | 'Tag' | 'Space';
@ -11,6 +11,7 @@ const state = {
locationId: null as number | null,
path: '',
layoutMode: 'grid' as ExplorerLayoutMode,
toggleMenu: false as boolean,
// Using gridNumColumns instead of fixed size. We dynamically calculate the item size.
gridNumColumns: 3,
listItemSize: 65,