Mobile cleanup & improvements (#2362)

clean up & improvements
This commit is contained in:
Utku 2024-04-19 03:55:50 -04:00 committed by GitHub
parent 1a438c630e
commit 5624054f1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 151 additions and 171 deletions

View file

@ -18,7 +18,7 @@ import { Button } from '../primitive/Button';
import LibraryItem from './LibraryItem';
const iconStyle = tw`text-ink-faint`;
const iconSize = 28;
const iconSize = 24;
export const CATEGORIES_LIST = [
{ name: 'Albums', icon: <Images size={iconSize} style={iconStyle} /> },
{ name: 'Places', icon: <MapPin size={iconSize} style={iconStyle} /> },

View file

@ -12,7 +12,7 @@ const Jobs = () => {
return (
<View style={tw`gap-3`}>
<View style={tw`w-full flex-row items-center justify-between px-5`}>
<Text style={tw`text-lg font-bold text-white`}>Jobs</Text>
<Text style={tw`text-lg font-bold text-white`}>Active Jobs</Text>
</View>
<Fade color="black" height="100%" width={30}>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>

View file

@ -12,9 +12,9 @@ interface CategoryProps {
const ListLibraryItem = ({ name, icon }: CategoryProps) => {
return (
<Card style={tw`flex-row items-center justify-between gap-2 py-3`}>
<View style={tw`flex-row items-center gap-2`}>
<View style={tw`flex-row items-center gap-2 px-2`}>
{icon}
<Text style={twStyle(`mt-0 text-sm text-white`)}>{name}</Text>
<Text style={twStyle(`text-sm text-white`)}>{name}</Text>
</View>
<View
style={tw`h-10 w-10 flex-row items-center justify-center rounded-full border border-app-lightborder/70 px-2`}

View file

@ -109,7 +109,7 @@ const useFormState = () => {
// Switch to the new library
currentLibraryStore.id = library.uuid;
} catch (e) {
toast({ type: 'error', text: 'Failed to create library' });
toast.error('Failed to create library');
resetOnboardingStore();
navigation.navigate('GetStarted');
}

View file

@ -1,20 +1,18 @@
import { useNavigation } from '@react-navigation/native';
import { FlashList } from '@shopify/flash-list';
import { UseInfiniteQueryResult } from '@tanstack/react-query';
import { AnimatePresence, MotiView } from 'moti';
import { useState } from 'react';
import { ActivityIndicator, Pressable } from 'react-native';
import { isPath, SearchData, type ExplorerItem } from '@sd/client';
import Layout from '~/constants/Layout';
import { tw } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { ExplorerLayoutMode, getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
import { useExplorerStore } from '~/stores/explorerStore';
import { useActionsModalStore } from '~/stores/modalStore';
import ScreenContainer from '../layout/ScreenContainer';
import FileItem from './FileItem';
import FileRow from './FileRow';
import Menu from './Menu';
import Menu from './menu/Menu';
type ExplorerProps = {
tabHeight?: boolean;
@ -27,14 +25,8 @@ type ExplorerProps = {
const Explorer = (props: ExplorerProps) => {
const navigation = useNavigation<BrowseStackScreenProps<'Location'>['navigation']>();
const explorerStore = useExplorerStore();
const [layoutMode, setLayoutMode] = useState<ExplorerLayoutMode>(getExplorerStore().layoutMode);
function changeLayoutMode(kind: ExplorerLayoutMode) {
// We need to keep layoutMode as a state to make sure flash-list re-renders.
setLayoutMode(kind);
getExplorerStore().layoutMode = kind;
}
const store = useExplorerStore();
const { modalRef, setData } = useActionsModalStore();
@ -52,45 +44,11 @@ const Explorer = (props: ExplorerProps) => {
return (
<ScreenContainer tabHeight={props.tabHeight} scrollview={false} style={'gap-0 py-0'}>
{/* Header */}
{/* Sort By */}
{/* <SortByMenu /> */}
<AnimatePresence>
{explorerStore.toggleMenu && (
<MotiView
from={{ translateY: -70 }}
animate={{ translateY: 0 }}
transition={{
type: 'timing',
duration: 300,
repeat: 0,
repeatReverse: false
}}
exit={{ translateY: -70 }}
>
<Menu
changeLayoutMode={(kind: ExplorerLayoutMode) => {
changeLayoutMode(kind);
}}
layoutMode={layoutMode}
/>
</MotiView>
)}
</AnimatePresence>
{/* Layout (Grid/List) */}
{/* {layoutMode === 'grid' ? (
<Pressable onPress={() => changeLayoutMode('list')}>
<SquaresFour color={tw.color('ink')} size={23} />
</Pressable>
) : (
<Pressable onPress={() => changeLayoutMode('grid')}>
<Rows color={tw.color('ink')} size={23} />
</Pressable>
)} */}
<Menu />
{/* Items */}
<FlashList
key={layoutMode}
numColumns={layoutMode === 'grid' ? getExplorerStore().gridNumColumns : 1}
key={store.layoutMode}
numColumns={store.layoutMode === 'grid' ? store.gridNumColumns : 1}
data={props.items ?? []}
keyExtractor={(item) =>
item.type === 'NonIndexedPath'
@ -101,15 +59,19 @@ const Explorer = (props: ExplorerProps) => {
}
renderItem={({ item }) => (
<Pressable onPress={() => handlePress(item)}>
{layoutMode === 'grid' ? <FileItem data={item} /> : <FileRow data={item} />}
{store.layoutMode === 'grid' ? (
<FileItem data={item} />
) : (
<FileRow data={item} />
)}
</Pressable>
)}
contentContainerStyle={tw`px-2 py-5`}
extraData={layoutMode}
extraData={store.layoutMode}
estimatedItemSize={
layoutMode === 'grid'
? Layout.window.width / getExplorerStore().gridNumColumns
: getExplorerStore().listItemSize
store.layoutMode === 'grid'
? Layout.window.width / store.gridNumColumns
: store.listItemSize
}
onEndReached={() => props.loadMore?.()}
onEndReachedThreshold={0.6}

View file

@ -1,41 +0,0 @@
import { MonitorPlay, Rows, SlidersHorizontal, SquaresFour } from 'phosphor-react-native';
import { Pressable, View } from 'react-native';
import { tw } from '~/lib/tailwind';
import { ExplorerLayoutMode } from '~/stores/explorerStore';
interface MenuProps {
layoutMode: ExplorerLayoutMode;
changeLayoutMode: (kind: ExplorerLayoutMode) => void;
}
const Menu = ({ layoutMode, changeLayoutMode }: MenuProps) => {
return (
<View
style={tw`w-screen flex-row justify-between border-b border-app-cardborder bg-app-header px-7 py-4`}
>
<View style={tw`flex-row gap-3`}>
<Pressable hitSlop={24} onPress={() => changeLayoutMode('grid')}>
<SquaresFour
color={tw.color(layoutMode === 'grid' ? 'text-accent' : 'text-ink-dull')}
size={23}
/>
</Pressable>
<Pressable hitSlop={24} onPress={() => changeLayoutMode('list')}>
<Rows
color={tw.color(layoutMode === 'list' ? 'text-accent' : 'text-ink-dull')}
size={23}
/>
</Pressable>
<Pressable hitSlop={24} 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 Menu;

View file

@ -0,0 +1,72 @@
import { AnimatePresence, MotiView } from 'moti';
import { MonitorPlay, Rows, SlidersHorizontal, SquaresFour } from 'phosphor-react-native';
import { Pressable, View } from 'react-native';
import { toast } from '~/components/primitive/Toast';
import { tw } from '~/lib/tailwind';
import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
import SortByMenu from './SortByMenu';
const Menu = () => {
const store = useExplorerStore();
return (
<AnimatePresence>
{store.toggleMenu && (
<MotiView
from={{ translateY: -70 }}
animate={{ translateY: 0 }}
transition={{
type: 'timing',
duration: 300,
repeat: 0,
repeatReverse: false
}}
exit={{ translateY: -70 }}
>
<View
style={tw`w-screen flex-row items-center justify-between border-b border-app-cardborder bg-app-header px-7 py-4`}
>
<View style={tw`flex-row gap-3`}>
<Pressable onPress={() => (getExplorerStore().layoutMode = 'grid')}>
<SquaresFour
color={tw.color(
store.layoutMode === 'grid'
? 'text-accent'
: 'text-ink-dull'
)}
size={23}
/>
</Pressable>
<Pressable onPress={() => (getExplorerStore().layoutMode = 'list')}>
<Rows
color={tw.color(
store.layoutMode === 'list'
? 'text-accent'
: 'text-ink-dull'
)}
size={23}
/>
</Pressable>
<Pressable
onPress={() => toast.error('Media view is not available yet...')}
// onPress={() => (getExplorerStore().layoutMode = 'media')}
>
<MonitorPlay
color={tw.color(
store.layoutMode === 'media'
? 'text-accent'
: 'text-ink-dull'
)}
size={23}
/>
</Pressable>
</View>
<SortByMenu />
</View>
</MotiView>
)}
</AnimatePresence>
);
};
export default Menu;

View file

@ -15,8 +15,8 @@ const sortOptions = {
type SortByType = keyof typeof sortOptions;
const ArrowUpIcon = () => <ArrowUp weight="bold" size={16} color={tw.color('ink')} />;
const ArrowDownIcon = () => <ArrowDown weight="bold" size={16} color={tw.color('ink')} />;
const ArrowUpIcon = () => <ArrowUp weight="bold" size={16} color={tw.color('ink-dull')} />;
const ArrowDownIcon = () => <ArrowDown weight="bold" size={16} color={tw.color('ink-dull')} />;
const SortByMenu = () => {
const [sortBy, setSortBy] = useState<SortByType>('name');
@ -26,7 +26,7 @@ const SortByMenu = () => {
<Menu
trigger={
<View style={tw`flex flex-row items-center`}>
<Text style={tw`mr-0.5 font-medium text-ink`}>{sortOptions[sortBy]}</Text>
<Text style={tw`mr-0.5 font-medium text-ink-dull`}>{sortOptions[sortBy]}</Text>
{sortDirection === 'asc' ? <ArrowUpIcon /> : <ArrowDownIcon />}
</View>
}

View file

@ -150,19 +150,10 @@ const toastErrorSuccess = (
) => {
return {
onError: () => {
errorMessage &&
toast({
type: 'error',
text: errorMessage
});
errorMessage && toast.error(errorMessage);
},
onSuccess: () => {
successMessage &&
toast({
type: 'success',
text: successMessage
}),
successCallBack?.();
successMessage && toast.success(successMessage), successCallBack?.();
}
};
};

View file

@ -3,25 +3,25 @@ import { Text, View } from 'react-native';
import Toast, { ToastConfig } from 'react-native-toast-message';
import { tw } from '~/lib/tailwind';
// TODO:
// - Expand toast on press to show full message if it's too long
// - Add a onPress option
// - Add leading icon & trailing icon
const baseStyles = 'w-[340px] flex-row overflow-hidden rounded-md border p-3 shadow-lg';
const toastConfig: ToastConfig = {
success: ({ text1, ...rest }) => (
<View
style={tw`w-[340px] flex-row overflow-hidden rounded-md border border-app-line bg-app-darkBox/90 p-3 shadow-lg`}
>
<View style={tw.style(baseStyles, 'border-app-line bg-app-darkBox/90 ')}>
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
{text1}
</Text>
</View>
),
error: ({ text1, ...rest }) => (
<View
style={tw`border-app-red bg-app-red/90 w-[340px] flex-row overflow-hidden rounded-md border p-3 shadow-lg`}
>
<View style={tw.style(baseStyles, 'border-red-500 bg-red-500/90')}>
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
{text1}
</Text>
</View>
),
info: ({ text1, ...rest }) => (
<View style={tw.style(baseStyles, 'border-app-line bg-app-darkBox/90')}>
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
{text1}
</Text>
@ -29,8 +29,26 @@ const toastConfig: ToastConfig = {
)
};
function toast({ text, type }: { type: 'success' | 'error' | 'info'; text: string }) {
Toast.show({ type, text1: text, visibilityTime: 3000, topOffset: 60 });
function showToast({ text, type }: { type: 'success' | 'error' | 'info'; text: string }) {
const visibilityTime = 3000;
const topOffset = 60;
Toast.show({ type, text1: text, visibilityTime, topOffset });
}
const toast: {
success: (text: string) => void;
error: (text: string) => void;
info: (text: string) => void;
} = {
success: function (text: string): void {
showToast({ text, type: 'success' });
},
error: function (text: string): void {
showToast({ text, type: 'error' });
},
info: function (text: string): void {
showToast({ text, type: 'info' });
}
};
export { Toast, toast, toastConfig };

View file

@ -22,9 +22,7 @@ const ListTag = ({ tag, tagStyle }: ListTagProps) => {
containerStyle={tw`rounded-md border border-app-cardborder bg-app-card p-3`}
enableTrackpadTwoFingerGesture
renderRightActions={(progress, _, swipeable) => (
<>
<RightActions progress={progress} swipeable={swipeable} tag={tag} />
</>
<RightActions progress={progress} swipeable={swipeable} tag={tag} />
)}
>
<View style={twStyle('h-auto flex-row items-center justify-between', tagStyle)}>
@ -41,7 +39,7 @@ const ListTag = ({ tag, tagStyle }: ListTagProps) => {
{tag.name}
</Text>
</View>
<Pressable hitSlop={24} onPress={() => swipeRef.current?.openRight()}>
<Pressable onPress={() => swipeRef.current?.openRight()}>
<DotsThreeOutlineVertical
weight="fill"
size={20}

View file

@ -1,22 +1,7 @@
import { CheckCircle } from 'phosphor-react-native';
import React from 'react';
import { useLibraryQuery } from '@sd/client';
import { PulseAnimation } from '~/components/animation/lottie';
import BrowseCategories from '~/components/browse/BrowseCategories';
import BrowseLocations from '~/components/browse/BrowseLocations';
import BrowseTags from '~/components/browse/BrowseTags';
import Jobs from '~/components/browse/Jobs';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { tw } from '~/lib/tailwind';
function JobIcon() {
const { data: isActive } = useLibraryQuery(['jobs.isActive']);
return isActive ? (
<PulseAnimation style={tw`h-[24px] w-[32px]`} speed={1.5} />
) : (
<CheckCircle color="white" size={24} />
);
}
export default function BrowseScreen() {
return (
@ -24,7 +9,6 @@ export default function BrowseScreen() {
<BrowseCategories />
<BrowseLocations />
<BrowseTags />
<Jobs />
</ScreenContainer>
);
}

View file

@ -18,12 +18,13 @@ interface Props {
}
export default function TagsScreen({ viewStyle = 'list' }: Props) {
const tags = useLibraryQuery(['tags.list']);
const navigation = useNavigation<BrowseStackScreenProps<'Browse'>['navigation']>();
const modalRef = useRef<ModalRef>(null);
const tags = useLibraryQuery(['tags.list']);
useNodes(tags.data?.nodes);
const tagData = useCache(tags.data?.items);
return (
<ScreenContainer scrollview={false} style={tw`relative px-6 py-0`}>
<Pressable

View file

@ -42,7 +42,7 @@ const EditLocationSettingsScreen = ({
onSuccess: () => {
form.reset(form.getValues());
queryClient.invalidateQueries(['locations.list']);
toast({ type: 'success', text: 'Location updated!' });
toast.success('Location updated!');
// TODO: navigate back & reset input focus!
}
});

View file

@ -34,7 +34,7 @@ const LibraryGeneralSettingsScreen = (_: SettingsStackScreenProps<'LibraryGenera
useAutoForm(form, (value) => {
editLibrary({ description: value.description, name: value.name, id: library.uuid });
// console.log('Updated', value);
toast({ type: 'success', text: 'Library updated!' });
toast.success('Library updated!');
});
return (

View file

@ -25,23 +25,20 @@ export function flattenThumbnailKey(thumbKey: string[]) {
return thumbKey.join('/');
}
const explorerStore = proxy({
const store = proxy({
...state,
reset: () => resetStore(explorerStore, state),
reset: () => resetStore(store, state),
addNewThumbnail: (thumbKey: string[]) => {
explorerStore.newThumbnails.add(flattenThumbnailKey(thumbKey));
store.newThumbnails.add(flattenThumbnailKey(thumbKey));
},
// this should be done when the explorer query is refreshed
// prevents memory leak
resetNewThumbnails: () => {
explorerStore.newThumbnails.clear();
store.newThumbnails.clear();
}
});
export function useExplorerStore() {
return useSnapshot(explorerStore);
}
export function getExplorerStore() {
return explorerStore;
}
/** for reading */
export const useExplorerStore = () => useSnapshot(store);
/** for writing */
export const getExplorerStore = () => store;

View file

@ -3,14 +3,15 @@ import { proxy, ref, useSnapshot } from 'valtio';
import { ExplorerItem } from '@sd/client';
import { ModalRef } from '~/components/layout/Modal';
export const actionsModalStore = proxy({
const store = proxy({
modalRef: ref(createRef<ModalRef>()),
data: null as ExplorerItem | null,
setData: (data: ExplorerItem) => {
actionsModalStore.data = data;
store.data = data;
}
});
export function useActionsModalStore() {
return useSnapshot(actionsModalStore);
}
/** for reading */
export const useActionsModalStore = () => useSnapshot(store);
/** for writing */
export const getActionsModalStore = () => store;

View file

@ -161,10 +161,7 @@ const searchStore = proxy<
}
});
export function useSearchStore() {
return useSnapshot(searchStore);
}
export function getSearchStore() {
return searchStore;
}
/** for reading */
export const useSearchStore = () => useSnapshot(searchStore);
/** for writing */
export const getSearchStore = () => searchStore;