[MOB-3] Small fixes and improvements (#813)

* add location button

* add tag button

* library manager arrow points right when open

* wip create lib modal

* handle .spacedrive file in location

* fix location screen title

* remove create lib dialog and use a modal instead

* clean tsconfig.tsbuildinfo too

* update some packages

* modal paddings

* fix onboarding animations
This commit is contained in:
Utku 2023-06-08 19:06:17 +03:00 committed by GitHub
parent e2dec80a51
commit cc8d6a3d24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 2182 additions and 2428 deletions

View file

@ -52,9 +52,9 @@
// Hiding these folders bcs they create a lot of noise in the search results
"**/*.contentlayer": true,
"**/*.next": true,
"apps/mobile/ios/Pods": true
// "apps/mobile/android": true
// "apps/mobile/ios": true
"apps/mobile/ios/Pods": true,
"apps/mobile/android": true,
"apps/mobile/ios": true
},
"eslint.workingDirectories": [
"apps/desktop",

View file

@ -12,15 +12,15 @@ PODS:
- EXMediaLibrary (15.2.3):
- ExpoModulesCore
- React-Core
- Expo (48.0.10):
- Expo (48.0.19):
- ExpoModulesCore
- ExpoKeepAwake (12.0.1):
- ExpoModulesCore
- ExpoModulesCore (1.2.6):
- ExpoModulesCore (1.2.7):
- React-Core
- React-RCTAppDelegate
- ReactCommon/turbomodule/core
- EXSplashScreen (0.18.1):
- EXSplashScreen (0.18.2):
- ExpoModulesCore
- React-Core
- FBLazyVector (0.71.3)
@ -292,7 +292,7 @@ PODS:
- React-jsinspector (0.71.3)
- React-logger (0.71.3):
- glog
- react-native-document-picker (8.2.0):
- react-native-document-picker (8.2.1):
- React-Core
- react-native-safe-area-context (4.5.1):
- RCT-Folly
@ -610,10 +610,10 @@ SPEC CHECKSUMS:
EXFileSystem: 844e86ca9b5375486ecc4ef06d3838d5597d895d
EXFont: 6ea3800df746be7233208d80fe379b8ed74f4272
EXMediaLibrary: 587cd8aad27a6fc8d7c38b950bc75bc1845a7480
Expo: b04d142a2b477a391b47c62d179c1a9e1140e6ad
Expo: 8448e3a2aa1b295f029c81551e1ab6d986517fdb
ExpoKeepAwake: 69f5f627670d62318410392d03e0b5db0f85759a
ExpoModulesCore: 6e0259511f4c4341b6b8357db393624df2280828
EXSplashScreen: cd7fb052dff5ba8311d5c2455ecbebffe1b7a8ca
ExpoModulesCore: 653958063a301098b541ae4dfed1ac0b98db607b
EXSplashScreen: 0e0a9ba0cf7553094e93213099bd7b42e6e237e9
FBLazyVector: 60195509584153283780abdac5569feffb8f08cc
FBReactNativeSpec: c5a5c4f1b95ae42a17cd22c8c89c482a7b327fe3
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
@ -636,7 +636,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: 515b703d23ffadeac7687bc2d12fb08b90f0aaa1
React-jsinspector: 9f7c9137605e72ca0343db4cea88006cb94856dd
React-logger: 957e5dc96d9dbffc6e0f15e0ee4d2b42829ff207
react-native-document-picker: 495c444c0c773c6e83a5d91165890ecb1c0a399a
react-native-document-picker: 69ca2094d8780cfc1e7e613894d15290fdc54bba
react-native-safe-area-context: f5549f36508b1b7497434baa0cd97d7e470920d4
React-perflogger: af8a3d31546077f42d729b949925cc4549f14def
React-RCTActionSheet: 57cc5adfefbaaf0aae2cf7e10bccd746f2903673

View file

@ -16,7 +16,7 @@
"clean:ios": "cd ios && xcodebuild clean && cd ../"
},
"dependencies": {
"@gorhom/bottom-sheet": "^4.4.5",
"@gorhom/bottom-sheet": "^4.4.7",
"@hookform/resolvers": "^3.1.0",
"@react-native-async-storage/async-storage": "~1.17.12",
"@react-native-masked-view/masked-view": "0.2.8",
@ -31,12 +31,12 @@
"@shopify/flash-list": "1.4.2",
"@tanstack/react-query": "^4.29.1",
"byte-size": "^8.1.0",
"class-variance-authority": "^0.5.2",
"dayjs": "^1.11.5",
"expo": "~48.0.10",
"class-variance-authority": "^0.5.3",
"dayjs": "^1.11.8",
"expo": "~48.0.19",
"expo-linking": "~4.0.1",
"expo-media-library": "~15.2.3",
"expo-splash-screen": "~0.18.1",
"expo-splash-screen": "~0.18.2",
"expo-status-bar": "~1.4.4",
"intl": "^1.2.5",
"lottie-react-native": "5.1.4",
@ -45,7 +45,7 @@
"react": "18.2.0",
"react-hook-form": "^7.43.9",
"react-native": "0.71.3",
"react-native-document-picker": "^8.2.0",
"react-native-document-picker": "^8.2.1",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.9.0",
"react-native-popup-menu": "^0.16.1",
@ -54,20 +54,20 @@
"react-native-screens": "~3.20.0",
"react-native-svg": "13.4.0",
"react-native-wheel-color-picker": "^1.2.0",
"twrnc": "^3.6.0",
"twrnc": "^3.6.1",
"use-count-up": "^3.0.1",
"use-debounce": "^9.0.4",
"valtio": "^1.10.4",
"zod": "^3.21.4"
},
"devDependencies": {
"@babel/core": "^7.21.4",
"@rnx-kit/metro-config": "^1.3.5",
"@babel/core": "^7.22.1",
"@rnx-kit/metro-config": "^1.3.6",
"@sd/config": "workspace:*",
"@types/react": "~18.0.27",
"@types/react": "~18.0.38",
"babel-plugin-module-resolver": "^5.0.0",
"eslint-plugin-react-native": "^4.0.0",
"react-native-svg-transformer": "^1.0.0",
"typescript": "^5.0.4"
"typescript": "^5.1.3"
}
}

View file

@ -1,71 +0,0 @@
import { useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { useBridgeMutation, usePlausibleEvent } from '@sd/client';
import { Input } from '~/components/form/Input';
import Dialog from '~/components/layout/Dialog';
import { currentLibraryStore } from '~/utils/nav';
type Props = {
onSubmit?: () => void;
disableBackdropClose?: boolean;
children: React.ReactNode;
};
// TODO: Move to a Modal component
const CreateLibraryDialog = ({ children, onSubmit, disableBackdropClose }: Props) => {
const queryClient = useQueryClient();
const [libName, setLibName] = useState('');
const [isOpen, setIsOpen] = useState(false);
const submitPlausibleEvent = usePlausibleEvent();
const { mutate: createLibrary, isLoading: createLibLoading } = useBridgeMutation(
'library.create',
{
onSuccess: (lib) => {
// Reset form
setLibName('');
// We do this instead of invalidating the query because it triggers a full app re-render??
queryClient.setQueryData(['library.list'], (libraries: any) => [
...(libraries || []),
lib
]);
// Switch to the new library
currentLibraryStore.id = lib.uuid;
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
onSubmit?.();
},
onSettled: () => {
// Close create lib dialog
setIsOpen(false);
}
}
);
return (
<Dialog
isVisible={isOpen}
setIsVisible={setIsOpen}
title="Create New Library"
description="Choose a name for your new library, you can configure this and more settings from the library settings later on."
ctaLabel="Create"
ctaAction={() => createLibrary({ name: libName })}
loading={createLibLoading}
ctaDisabled={libName.length === 0}
trigger={children}
disableBackdropClose={disableBackdropClose}
onClose={() => setLibName('')} // Resets form onClose
>
<Input
value={libName}
onChangeText={(text) => setLibName(text)}
placeholder="My Cool Library"
/>
</Dialog>
);
};
export default CreateLibraryDialog;

View file

@ -1,14 +1,15 @@
import { useDrawerStatus } from '@react-navigation/drawer';
import { useNavigation } from '@react-navigation/native';
import { MotiView } from 'moti';
import { CaretDown, Gear, Lock, Plus } from 'phosphor-react-native';
import { useEffect, useState } from 'react';
import { CaretRight, Gear, Lock, Plus } from 'phosphor-react-native';
import { useEffect, useRef, useState } from 'react';
import { Alert, Pressable, Text, View } from 'react-native';
import { useClientContext } from '@sd/client';
import { tw, twStyle } from '~/lib/tailwind';
import { currentLibraryStore } from '~/utils/nav';
import { AnimatedHeight } from '../animation/layout';
import CreateLibraryDialog from '../dialog/CreateLibraryDialog';
import { ModalRef } from '../layout/Modal';
import CreateLibraryModal from '../modal/CreateLibraryModal';
import { Divider } from '../primitive/Divider';
const DrawerLibraryManager = () => {
@ -24,6 +25,8 @@ const DrawerLibraryManager = () => {
const navigation = useNavigation();
const modalRef = useRef<ModalRef>(null);
return (
<View>
<Pressable onPress={() => setDropdownClosed((v) => !v)}>
@ -39,13 +42,10 @@ const DrawerLibraryManager = () => {
{currentLibrary?.config.name}
</Text>
<MotiView
animate={{
rotate: dropdownClosed ? '0deg' : '180deg',
translateX: dropdownClosed ? 0 : -9
}}
animate={{ rotateZ: dropdownClosed ? '0deg' : '90deg' }}
transition={{ type: 'timing', duration: 100 }}
>
<CaretDown color="white" size={18} weight="bold" style={tw`ml-2`} />
<CaretRight color="white" size={18} weight="bold" />
</MotiView>
</View>
</Pressable>
@ -80,12 +80,14 @@ const DrawerLibraryManager = () => {
<Divider style={tw`my-2`} />
{/* Menu */}
{/* Create Library */}
<CreateLibraryDialog>
<View style={tw`flex flex-row items-center px-1.5 py-[8px]`}>
<Plus size={18} weight="bold" color="white" style={tw`mr-2`} />
<Text style={tw`text-sm font-semibold text-white`}>New Library</Text>
</View>
</CreateLibraryDialog>
<Pressable
style={tw`flex flex-row items-center px-1.5 py-[8px]`}
onPress={() => modalRef.current?.present()}
>
<Plus size={18} weight="bold" color="white" style={tw`mr-2`} />
<Text style={tw`text-sm font-semibold text-white`}>New Library</Text>
</Pressable>
<CreateLibraryModal ref={modalRef} />
{/* Manage Library */}
<Pressable
onPress={() =>

View file

@ -36,7 +36,7 @@ type DrawerLocationsProp = {
const DrawerLocations = ({ stackName }: DrawerLocationsProp) => {
const navigation = useNavigation<DrawerNavigationHelpers>();
const importModalRef = useRef<ModalRef>(null);
const modalRef = useRef<ModalRef>(null);
const { data: locations } = useLibraryQuery(['locations.list'], { keepPreviousData: true });
@ -62,7 +62,7 @@ const DrawerLocations = ({ stackName }: DrawerLocationsProp) => {
))}
</View>
{/* Add Location */}
<Pressable onPress={() => importModalRef.current?.present()}>
<Pressable onPress={() => modalRef.current?.present()}>
<View style={tw`mt-1 rounded border border-dashed border-app-line/80`}>
<Text style={tw`p-2 text-center text-xs font-bold text-gray-400`}>
Add Location
@ -70,7 +70,7 @@ const DrawerLocations = ({ stackName }: DrawerLocationsProp) => {
</View>
</Pressable>
</CollapsibleView>
<ImportModal ref={importModalRef} />
<ImportModal ref={modalRef} />
</>
);
};

View file

@ -37,7 +37,7 @@ const DrawerTags = ({ stackName }: DrawerTagsProp) => {
const { data: tags } = useLibraryQuery(['tags.list'], { keepPreviousData: true });
const createTagModalRef = useRef<ModalRef>(null);
const modalRef = useRef<ModalRef>(null);
return (
<CollapsibleView
@ -61,12 +61,12 @@ const DrawerTags = ({ stackName }: DrawerTagsProp) => {
))}
</View>
{/* Add Tag */}
<Pressable onPress={() => createTagModalRef.current?.present()}>
<Pressable onPress={() => modalRef.current?.present()}>
<View style={tw`mt-1 rounded border border-dashed border-app-line/80`}>
<Text style={tw`p-2 text-center text-xs font-bold text-gray-400`}>Add Tag</Text>
</View>
</Pressable>
<CreateTagModal ref={createTagModalRef} />
<CreateTagModal ref={modalRef} />
</CollapsibleView>
);
};

View file

@ -16,19 +16,15 @@ const CollapsibleView = ({ title, titleStyle, containerStyle, children }: Collap
return (
<View style={containerStyle}>
<Pressable onPress={toggle} style={tw`flex flex-row items-center justify-between`}>
<Pressable onPress={toggle} style={tw`flex flex-row items-center justify-between pr-3`}>
<Text style={titleStyle} selectable={false}>
{title}
</Text>
<MotiView
animate={{
rotateZ: hide ? '0deg' : '90deg',
translateX: hide ? 0 : 5,
translateY: hide ? 0 : 5
}}
animate={{ rotateZ: hide ? '0deg' : '90deg' }}
transition={{ type: 'timing', duration: 150 }}
>
<CaretRight color="white" weight="bold" size={16} style={tw`mr-3`} />
<CaretRight color="white" weight="bold" size={16} />
</MotiView>
</Pressable>
<AnimatedHeight hide={hide}>{children}</AnimatedHeight>

View file

@ -1,125 +0,0 @@
import { MotiView } from 'moti';
import { ReactNode, useState } from 'react';
import { KeyboardAvoidingView, Modal, Platform, Pressable, Text, View } from 'react-native';
import { tw } from '~/lib/tailwind';
import { PulseAnimation } from '../animation/lottie';
import { Button } from '../primitive/Button';
type DialogProps = {
title: string;
description?: string;
trigger?: ReactNode;
/**
* if `true`, dialog will be visible when mounted.
* It can be used when trigger is not provided and/or you need to open the dialog programmatically
*/
isVisible?: boolean;
/**
* Like above, it will override the default dialog state for opening/closing the dialog.
* It can be used to control dialog state from outside
*/
setIsVisible?: (v: boolean) => void;
children?: ReactNode;
ctaAction?: () => void;
ctaLabel?: string;
ctaDanger?: boolean;
ctaDisabled?: boolean;
loading?: boolean;
/**
* Disables backdrop press to close the modal.
*/
disableBackdropClose?: boolean;
/**
* Triggered when the dialog is closed (either by backdrop or the close button)
*/
onClose?: () => void;
};
const Dialog = (props: DialogProps) => {
const [visible, setVisible] = useState(props.isVisible ?? false);
function handleCloseDialog() {
props.setIsVisible ? props.setIsVisible(false) : setVisible(false);
// Cool undefined check
props.onClose?.();
}
return (
<View>
{props.trigger && (
<Pressable
onPress={() =>
props.setIsVisible ? props.setIsVisible(true) : setVisible(true)
}
>
{props.trigger}
</Pressable>
)}
<Modal renderToHardwareTextureAndroid transparent visible={props.isVisible ?? visible}>
{/* Backdrop */}
<Pressable
style={tw`absolute inset-0 bg-app-box/40`}
onPress={handleCloseDialog}
disabled={props.disableBackdropClose || props.loading}
/>
{/* Content */}
<KeyboardAvoidingView
pointerEvents="box-none"
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={Platform.OS === 'ios' ? 40 : undefined}
style={tw`flex-1 items-center justify-center`}
>
<MotiView
from={{ translateY: 40 }}
animate={{ translateY: 0 }}
transition={{ type: 'timing', duration: 200 }}
>
{/* TODO: Blur may look cool here */}
<View
style={tw`min-w-[360px] max-w-[380px] overflow-hidden rounded-md border border-app-line bg-app shadow shadow-app-shade`}
>
<View style={tw`p-5`}>
{/* Title */}
<Text style={tw`text-base font-bold text-ink`}>{props.title}</Text>
{/* Description */}
{props.description && (
<Text style={tw`mt-2 text-sm leading-normal text-ink-dull`}>
{props.description}
</Text>
)}
{/* Children */}
<View style={tw`mt-3`}>{props.children}</View>
</View>
{/* Actions */}
<View
style={tw`flex flex-row items-center border-t border-app-line bg-app-highlight p-3`}
>
{props.loading && <PulseAnimation style={tw`h-7`} />}
<View style={tw`grow`} />
<Button
variant="darkGray"
disabled={props.loading} // Disables Close button if loading
onPress={handleCloseDialog}
>
<Text style={tw`text-sm text-ink`}>Close</Text>
</Button>
{props.ctaAction && (
<Button
style={tw`ml-2`}
variant={props.ctaDanger ? 'danger' : 'accent'}
onPress={props.ctaAction}
disabled={props.ctaDisabled || props.loading}
>
<Text style={tw`text-sm text-ink`}>{props.ctaLabel}</Text>
</Button>
)}
</View>
</View>
</MotiView>
</KeyboardAvoidingView>
</Modal>
</View>
);
};
export default Dialog;

View file

@ -45,11 +45,12 @@ export type ModalRef = BottomSheetModal;
interface ModalProps extends BottomSheetModalProps {
children: React.ReactNode;
title?: string;
description?: string;
showCloseButton?: boolean;
}
export const Modal = forwardRef<ModalRef, ModalProps>((props, ref) => {
const { children, title, showCloseButton = false, ...otherProps } = props;
const { children, title, description, showCloseButton = false, ...otherProps } = props;
const modalRef = useForwardedRef(ref);
@ -62,6 +63,9 @@ export const Modal = forwardRef<ModalRef, ModalProps>((props, ref) => {
{...otherProps}
>
{title && <Text style={tw`text-center text-base font-medium text-ink`}>{title}</Text>}
{props.description && (
<Text style={tw`px-4 py-3 text-sm text-ink-dull`}>{props.description}</Text>
)}
{children}
</BottomSheetModal>
);

View file

@ -0,0 +1,79 @@
import { useQueryClient } from '@tanstack/react-query';
import { forwardRef, useState } from 'react';
import { Text, View } from 'react-native';
import { useBridgeMutation, usePlausibleEvent } from '@sd/client';
import { ModalInput } from '~/components/form/Input';
import { Modal, ModalRef } from '~/components/layout/Modal';
import { Button } from '~/components/primitive/Button';
import useForwardedRef from '~/hooks/useForwardedRef';
import { tw } from '~/lib/tailwind';
import { currentLibraryStore } from '~/utils/nav';
const CreateLibraryModal = forwardRef<ModalRef, unknown>((_, ref) => {
const modalRef = useForwardedRef(ref);
const queryClient = useQueryClient();
const [libName, setLibName] = useState('');
const submitPlausibleEvent = usePlausibleEvent();
const { mutate: createLibrary, isLoading: createLibLoading } = useBridgeMutation(
'library.create',
{
onSuccess: (lib) => {
// Reset form
setLibName('');
// We do this instead of invalidating the query because it triggers a full app re-render??
queryClient.setQueryData(['library.list'], (libraries: any) => [
...(libraries || []),
lib
]);
// Switch to the new library
currentLibraryStore.id = lib.uuid;
submitPlausibleEvent({ event: { type: 'libraryCreate' } });
},
onSettled: () => {
modalRef.current?.dismiss();
}
}
);
return (
<Modal
ref={modalRef}
snapPoints={['30']}
title="Create New Library"
description="Choose a name for your new library, you can configure this and more settings
from the library settings later on."
onDismiss={() => {
// Resets form onDismiss
setLibName('');
}}
showCloseButton
// Disable panning gestures
enableHandlePanningGesture={false}
enableContentPanningGesture={false}
>
<View style={tw`px-4`}>
<ModalInput
value={libName}
onChangeText={(text) => setLibName(text)}
placeholder="My Cool Library"
/>
<Button
variant="accent"
onPress={() => createLibrary({ name: libName })}
style={tw`mt-4`}
disabled={libName.length === 0 || createLibLoading}
>
<Text style={tw`text-sm font-medium text-white`}>Create</Text>
</Button>
</View>
</Modal>
);
});
export default CreateLibraryModal;

View file

@ -14,9 +14,21 @@ import { tw } from '~/lib/tailwind';
const ImportModal = forwardRef<ModalRef, unknown>((_, ref) => {
const modalRef = useForwardedRef(ref);
const { mutate: createLocation } = useLibraryMutation('locations.create', {
onError: (error) => {
console.error(error);
const addLocationToLibrary = useLibraryMutation('locations.addLibrary');
const relinkLocation = useLibraryMutation('locations.relink');
const createLocation = useLibraryMutation('locations.create', {
onError: (error, variables) => {
switch (error.message) {
case 'NEED_RELINK':
if (!variables.dry_run) relinkLocation.mutate(variables.path);
break;
case 'ADD_LIBRARY':
addLocationToLibrary.mutate(variables);
break;
default:
throw new Error('Unimplemented custom remote error handling');
}
},
onSettled: () => {
// Close the modal
@ -32,7 +44,7 @@ const ImportModal = forwardRef<ModalRef, unknown>((_, ref) => {
if (!response) return;
createLocation({
createLocation.mutate({
path: decodeURIComponent(response.uri.replace('file://', '')),
dry_run: false,
indexer_rules_ids: []

View file

@ -18,14 +18,13 @@ const DeleteLibraryModal = ({ trigger, onSubmit, libraryUuid }: Props) => {
const { mutate: deleteLibrary, isLoading: deleteLibLoading } = useBridgeMutation(
'library.delete',
{
onMutate: () => {
console.log('Deleting library');
},
onSuccess: () => {
queryClient.invalidateQueries(['library.list']);
onSubmit?.();
submitPlausibleEvent({
event: {
type: 'libraryDelete'
}
});
submitPlausibleEvent({ event: { type: 'libraryDelete' } });
},
onSettled: () => {
modalRef.current?.close();

View file

@ -24,6 +24,9 @@ const CreateTagModal = forwardRef<ModalRef, unknown>((_, ref) => {
const submitPlausibleEvent = usePlausibleEvent();
const { mutate: createTag } = useLibraryMutation('tags.create', {
onMutate: () => {
console.log('Creating tag');
},
onSuccess: () => {
// Reset form
setTagName('');
@ -67,7 +70,7 @@ const CreateTagModal = forwardRef<ModalRef, unknown>((_, ref) => {
enableContentPanningGesture={false}
>
<View style={tw`p-4`}>
<View style={tw`mt-4 flex flex-row items-center`}>
<View style={tw`mt-2 flex flex-row items-center`}>
<Pressable
onPress={() => setShowPicker(true)}
style={twStyle({ backgroundColor: tagColor }, 'h-6 w-6 rounded-full')}

View file

@ -24,6 +24,9 @@ const UpdateTagModal = forwardRef<ModalRef, Props>((props, ref) => {
const [showPicker, setShowPicker] = useState(false);
const { mutate: updateTag, isLoading } = useLibraryMutation('tags.update', {
onMutate: () => {
console.log('Updating tag');
},
onSuccess: () => {
// Reset form
setShowPicker(false);
@ -82,7 +85,7 @@ const UpdateTagModal = forwardRef<ModalRef, Props>((props, ref) => {
variant="accent"
onPress={() => updateTag({ id: props.tag.id, color: tagColor, name: tagName })}
style={tw`mt-6`}
disabled={tagName.length === 0}
disabled={tagName.length === 0 || tagColor.length === 0 || isLoading}
>
<Text style={tw`text-sm font-medium text-white`}>Save</Text>
</Button>

View file

@ -24,7 +24,10 @@ export default function LocationScreen({ navigation, route }: SharedScreenProps<
if (path && path !== '') {
// Nested location.
navigation.setOptions({
title: path.split('/')[0]
title: path
.split('/')
.filter((x) => x !== '')
.pop()
});
} else {
navigation.setOptions({

View file

@ -1,7 +1,9 @@
import { AppLogo, BloomOne } from '@sd/assets/images';
import { useNavigation, useRoute } from '@react-navigation/native';
import { MotiView } from 'moti';
import { CaretLeft } from 'phosphor-react-native';
import { Image, KeyboardAvoidingView, Platform, Pressable, Text, View } from 'react-native';
import Animated from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { FadeInUpAnimation, LogoAnimation } from '~/components/animation/layout';
import { AnimatedButton } from '~/components/primitive/Button';
@ -28,9 +30,11 @@ export function OnboardingContainer({ children }: React.PropsWithChildren) {
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={bottom}
style={tw`w-full flex-1 items-center justify-center px-4`}
style={tw`w-full flex-1 items-center justify-center`}
>
{children}
<MotiView style={tw`w-full items-center justify-center px-4`}>
{children}
</MotiView>
</KeyboardAvoidingView>
<Text style={tw`absolute bottom-8 text-xs text-ink-dull/50`}>
&copy; 2022 Spacedrive Technology Inc.
@ -43,7 +47,7 @@ export function OnboardingContainer({ children }: React.PropsWithChildren) {
}
export const OnboardingTitle = styled(
Text,
Animated.Text,
'text-ink text-center text-4xl font-extrabold leading-tight'
);

View file

@ -1,8 +1,9 @@
import { CaretRight, Pen, Trash } from 'phosphor-react-native';
import React from 'react';
import React, { useEffect, useRef } from 'react';
import { Animated, FlatList, Text, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
import { LibraryConfigWrapped, useBridgeQuery } from '@sd/client';
import { ModalRef } from '~/components/layout/Modal';
import DeleteLibraryModal from '~/components/modal/confirm-modals/DeleteLibraryModal';
import { AnimatedButton, FakeButton } from '~/components/primitive/Button';
import { tw, twStyle } from '~/lib/tailwind';
@ -69,6 +70,23 @@ function LibraryItem({
const LibrarySettingsScreen = ({ navigation }: SettingsStackScreenProps<'LibrarySettings'>) => {
const { data: libraries } = useBridgeQuery(['library.list']);
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<AnimatedButton
variant="accent"
style={tw`mr-2`}
size="sm"
onPress={() => modalRef.current?.present()}
>
<Text style={tw`text-white`}>New</Text>
</AnimatedButton>
)
});
}, [navigation]);
const modalRef = useRef<ModalRef>(null);
return (
<View style={tw`flex-1 px-3 py-4`}>
<FlatList

View file

@ -1,4 +1,5 @@
import { CaretRight, Pen, Repeat, Trash } from 'phosphor-react-native';
import { useEffect, useRef } from 'react';
import { Animated, FlatList, Pressable, Text, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
import {
@ -10,7 +11,10 @@ import {
useOnlineLocations
} from '@sd/client';
import FolderIcon from '~/components/icons/FolderIcon';
import { ModalRef } from '~/components/layout/Modal';
import ImportModal from '~/components/modal/ImportModal';
import DeleteLocationModal from '~/components/modal/confirm-modals/DeleteLocationModal';
import { AnimatedButton } from '~/components/primitive/Button';
import { tw, twStyle } from '~/lib/tailwind';
import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator';
@ -121,11 +125,26 @@ function LocationItem({ location, index, navigation }: LocationItemProps) {
);
}
// TODO: Add new location from here (ImportModal)
const LocationSettingsScreen = ({ navigation }: SettingsStackScreenProps<'LocationSettings'>) => {
const { data: locations } = useLibraryQuery(['locations.list']);
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<AnimatedButton
variant="accent"
style={tw`mr-2`}
size="sm"
onPress={() => modalRef.current?.present()}
>
<Text style={tw`text-white`}>New</Text>
</AnimatedButton>
)
});
}, [navigation]);
const modalRef = useRef<ModalRef>(null);
return (
<View style={tw`flex-1 px-3 py-4`}>
<FlatList
@ -135,6 +154,7 @@ const LocationSettingsScreen = ({ navigation }: SettingsStackScreenProps<'Locati
<LocationItem navigation={navigation} location={item} index={index} />
)}
/>
<ImportModal ref={modalRef} />
</View>
);
};

View file

@ -1,17 +1,18 @@
import { CaretRight, Pen, Trash } from 'phosphor-react-native';
import { useRef } from 'react';
import { useEffect, useRef } from 'react';
import { Animated, FlatList, Text, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
import { Tag, useLibraryQuery } from '@sd/client';
import { ModalRef } from '~/components/layout/Modal';
import DeleteTagModal from '~/components/modal/confirm-modals/DeleteTagModal';
import CreateTagModal from '~/components/modal/tag/CreateTagModal';
import UpdateTagModal from '~/components/modal/tag/UpdateTagModal';
import { AnimatedButton, FakeButton } from '~/components/primitive/Button';
import { tw, twStyle } from '~/lib/tailwind';
import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator';
function TagItem({ tag, index }: { tag: Tag; index: number }) {
const updateTagModalRef = useRef<ModalRef>(null);
const modalRef = useRef<ModalRef>(null);
const renderRightActions = (
progress: Animated.AnimatedInterpolation<number>,
@ -28,12 +29,8 @@ function TagItem({ tag, index }: { tag: Tag; index: number }) {
<Animated.View
style={[tw`flex flex-row items-center`, { transform: [{ translateX: translate }] }]}
>
<UpdateTagModal
tag={tag}
ref={updateTagModalRef}
onSubmit={() => swipeable.close()}
/>
<AnimatedButton onPress={() => updateTagModalRef.current?.present()}>
<UpdateTagModal tag={tag} ref={modalRef} onSubmit={() => swipeable.close()} />
<AnimatedButton onPress={() => modalRef.current?.present()}>
<Pen size={18} color="white" />
</AnimatedButton>
<DeleteTagModal
@ -75,6 +72,23 @@ function TagItem({ tag, index }: { tag: Tag; index: number }) {
const TagsSettingsScreen = ({ navigation }: SettingsStackScreenProps<'TagsSettings'>) => {
const { data: tags } = useLibraryQuery(['tags.list']);
useEffect(() => {
navigation.setOptions({
headerRight: () => (
<AnimatedButton
variant="accent"
style={tw`mr-2`}
size="sm"
onPress={() => modalRef.current?.present()}
>
<Text style={tw`text-white`}>New</Text>
</AnimatedButton>
)
});
}, [navigation]);
const modalRef = useRef<ModalRef>(null);
return (
<View style={tw`flex-1 px-3 py-4`}>
<FlatList
@ -82,6 +96,7 @@ const TagsSettingsScreen = ({ navigation }: SettingsStackScreenProps<'TagsSettin
keyExtractor={(item) => item.id.toString()}
renderItem={({ item, index }) => <TagItem tag={item} index={index} />}
/>
<CreateTagModal ref={modalRef} />
</View>
);
};

View file

@ -40,10 +40,10 @@
"@vitejs/plugin-react": "^2.1.0",
"autoprefixer": "^10.4.12",
"byte-size": "^8.1.0",
"class-variance-authority": "^0.4.0",
"class-variance-authority": "^0.5.3",
"clsx": "^1.2.1",
"crypto-random-string": "^5.0.0",
"dayjs": "^1.11.5",
"dayjs": "^1.11.8",
"dragselect": "^2.7.4",
"framer-motion": "^10.11.5",
"phosphor-react": "^1.4.1",

View file

@ -23,7 +23,7 @@
"typecheck": "pnpm -r typecheck",
"lint": "turbo run lint",
"lint:fix": "turbo run lint -- --fix",
"clean": "rimraf -g \"node_modules/\" \"**/node_modules/\" \"target/\" \"**/.build/\" \"**/.next/\" \"**/.contentlayer/\" \"**/dist/!(.gitignore)**\""
"clean": "rimraf -g \"node_modules/\" \"**/node_modules/\" \"target/\" \"**/.build/\" \"**/.next/\" \"**/dist/!(.gitignore)**\""
},
"pnpm": {
"overrides": {

View file

@ -34,7 +34,7 @@
"@react-spring/web": "9.6.0",
"@sd/assets": "workspace:*",
"@tailwindcss/forms": "^0.5.3",
"class-variance-authority": "^0.4.0",
"class-variance-authority": "^0.5.3",
"clsx": "^1.2.1",
"phosphor-react": "^1.4.1",
"postcss": "^8.4.17",

File diff suppressed because it is too large Load diff