From 83781be27a614a30004e4bb30da28d7b2a9bcf60 Mon Sep 17 00:00:00 2001 From: Utku Bakir <74243531+utkubakir@users.noreply.github.com> Date: Thu, 1 Sep 2022 13:45:25 +0300 Subject: [PATCH] Init libraries with rspc + fix feedback stuff --- apps/mobile/package.json | 1 + apps/mobile/pnpm-lock.yaml | Bin 327018 -> 327263 bytes apps/mobile/src/App.tsx | 79 ++++++++++++------ .../src/components/drawer/DrawerContent.tsx | 22 +++-- .../mobile/src/components/primitive/Input.tsx | 32 +++++++ .../mobile/src/navigation/DrawerNavigator.tsx | 22 ++--- .../src/navigation/OnboardingNavigator.tsx | 3 + apps/mobile/src/navigation/index.tsx | 6 +- .../src/navigation/tabs/BrowseStack.tsx | 11 +-- .../src/navigation/tabs/OverviewStack.tsx | 3 +- .../src/navigation/tabs/PhotosStack.tsx | 3 +- .../src/navigation/tabs/SpacesStack.tsx | 3 +- apps/mobile/src/screens/Overview.tsx | 8 +- apps/mobile/src/screens/modals/Search.tsx | 4 +- .../src/screens/onboarding/CreateLibrary.tsx | 31 +++++++ .../src/screens/onboarding/Onboarding.tsx | 14 +--- apps/mobile/src/stores/useLibraryStore.ts | 72 ++++++++++++++-- 17 files changed, 233 insertions(+), 81 deletions(-) create mode 100644 apps/mobile/src/screens/onboarding/CreateLibrary.tsx diff --git a/apps/mobile/package.json b/apps/mobile/package.json index f915311a1..9b431525d 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -29,6 +29,7 @@ "expo-linking": "~3.2.2", "expo-splash-screen": "~0.16.2", "expo-status-bar": "~1.4.0", + "immer": "^9.0.15", "intl": "^1.2.5", "moti": "^0.18.0", "phosphor-react-native": "^1.1.2", diff --git a/apps/mobile/pnpm-lock.yaml b/apps/mobile/pnpm-lock.yaml index 5fce41495b89a2a9fac6c79745341866bd445de2..f13f916a483a21ba8ce3e146e98dfedde6432108 100644 GIT binary patch delta 236 zcmaF$OZfgD;SG$eyt%olMOF%NmU;$yhNhc;vixD?fe1lG-f}Hu;>`pJIY30ToBMd% z`*<0*_wh2hyyFB)=z}Gu7g{r^g&L(g=@%E5=N4N!x`qa4`{$YjCxw|tnuq0<=qGsx zl{;0Ym>OlJ`DBNi`1?erCWZtUySiq2Sth0zh8p{1fW>6R-gVvla#P1+yLtnF52v4Tr=H0k^~r0z~eIru+i8ru+j(|CjPG V0Wr5L`~)=tmyjR?6qkto1lM~@8)*Ol diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 14794ffd7..04e70cb9f 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -8,12 +8,19 @@ import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useDeviceContext } from 'twrnc'; import { GlobalModals } from './components/modals/GlobalModals'; -import { ReactNativeTransport, queryClient, rspc, useInvalidateQuery } from './hooks/rspc'; +import { + ReactNativeTransport, + queryClient, + rspc, + useBridgeQuery, + useInvalidateQuery +} from './hooks/rspc'; import useCachedResources from './hooks/useCachedResources'; import { getItemFromStorage } from './lib/storage'; import tw from './lib/tailwind'; import RootNavigator from './navigation'; import OnboardingNavigator from './navigation/OnboardingNavigator'; +import { useLibraryStore } from './stores/useLibraryStore'; import { useOnboardingStore } from './stores/useOnboardingStore'; import type { Operations } from './types/bindings'; @@ -29,7 +36,7 @@ const NavigatorTheme: Theme = { } }; -export default function App() { +function AppContainer() { // Enables dark mode, and screen size breakpoints, etc. for tailwind useDeviceContext(tw, { withDeviceColorScheme: false }); @@ -37,38 +44,62 @@ export default function App() { const { showOnboarding, hideOnboarding } = useOnboardingStore(); + const { data: libraries } = useBridgeQuery(['library.get']); + + const { switchLibrary, _hasHydrated } = useLibraryStore(); + // Runs when the app is launched useEffect(() => { - getItemFromStorage('@onboarding').then((value) => { - value && hideOnboarding(); - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + async function appLaunch() { + // Check if the user went through onboarding + const didOnboarding = await getItemFromStorage('@onboarding'); + // If user did do onboarding, that means they've already have a library - if (!isLoadingComplete) { + // Temporarly set the first library to be the current library + if (libraries && libraries.length > 0) { + switchLibrary(libraries[0].uuid); + } + + if (didOnboarding) { + hideOnboarding(); + } + } + + appLaunch(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [libraries]); + + // Might need to move _hasHydrated to useCacheResources hook. + if (!isLoadingComplete && !_hasHydrated) { return null; } else { return ( - - <> - - - - - - - {showOnboarding ? : } - - - - - - - + + + + + + {showOnboarding ? : } + + + + + ); } } +export default function App() { + return ( + + <> + + + + + ); +} + function InvalidateQuery() { useInvalidateQuery(); return null; diff --git a/apps/mobile/src/components/drawer/DrawerContent.tsx b/apps/mobile/src/components/drawer/DrawerContent.tsx index 5b7ac971f..f60f0cc64 100644 --- a/apps/mobile/src/components/drawer/DrawerContent.tsx +++ b/apps/mobile/src/components/drawer/DrawerContent.tsx @@ -1,14 +1,14 @@ import Layout from '@app/constants/Layout'; import tw from '@app/lib/tailwind'; +import { useCurrentLibrary, useLibraryStore } from '@app/stores/useLibraryStore'; import { DrawerContentScrollView } from '@react-navigation/drawer'; import { DrawerContentComponentProps } from '@react-navigation/drawer/lib/typescript/src/types'; import { getFocusedRouteNameFromRoute } from '@react-navigation/native'; -import React from 'react'; +import React, { useEffect } from 'react'; import { ColorValue, Platform, Pressable, Text, View } from 'react-native'; import { CogIcon } from 'react-native-heroicons/solid'; import CollapsibleView from '../layout/CollapsibleView'; -import { Tooltip } from '../tooltip/Tooltip'; import DrawerLocationItem from './DrawerLocationItem'; import DrawerLogo from './DrawerLogo'; import DrawerTagItem from './DrawerTagItem'; @@ -57,12 +57,22 @@ const getActiveRouteState = function (state: any) { const DrawerContent = ({ navigation, state }: DrawerContentComponentProps) => { const stackName = getFocusedRouteNameFromRoute(getActiveRouteState(state)) ?? 'OverviewStack'; + // initialize libraries + const { init: initLibraries, switchLibrary } = useLibraryStore(); + const { currentLibrary, libraries, currentLibraryUuid } = useCurrentLibrary(); + + useEffect(() => { + if (libraries && !currentLibraryUuid) initLibraries(libraries); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [libraries, currentLibraryUuid]); + return ( TODO: Library Selection + {libraries[0].config.name} {/* Locations */} { {/* Settings */} - - navigation.navigate('Settings')}> - - - + navigation.navigate('Settings')}> + + ); diff --git a/apps/mobile/src/components/primitive/Input.tsx b/apps/mobile/src/components/primitive/Input.tsx index e69de29bb..4b92cb74e 100644 --- a/apps/mobile/src/components/primitive/Input.tsx +++ b/apps/mobile/src/components/primitive/Input.tsx @@ -0,0 +1,32 @@ +import tw from '@app/lib/tailwind'; +import { VariantProps, cva } from 'class-variance-authority'; +import React from 'react'; +import { TextInput as RNTextInput, TextInputProps as RNTextInputProps } from 'react-native'; + +const input = cva(['text-sm rounded-md border shadow-sm'], { + variants: { + variant: { + default: 'bg-gray-550 border-gray-500 text-white' + }, + size: { + default: ['py-2', 'px-3'] + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } +}); + +type InputProps = VariantProps & RNTextInputProps; + +export const TextInput: React.FC = ({ variant, ...props }) => { + const { style, ...otherProps } = props; + return ( + + ); +}; diff --git a/apps/mobile/src/navigation/DrawerNavigator.tsx b/apps/mobile/src/navigation/DrawerNavigator.tsx index 30e298b36..621a75222 100644 --- a/apps/mobile/src/navigation/DrawerNavigator.tsx +++ b/apps/mobile/src/navigation/DrawerNavigator.tsx @@ -13,19 +13,15 @@ export default function DrawerNavigator() { return ( { - return { - headerShown: false, - drawerStyle: { - backgroundColor: '#08090D', - width: '75%' - }, - overlayColor: 'transparent', - drawerType: 'slide' - // swipeEnabled: false - // drawerHideStatusBarOnOpen: true, - // drawerStatusBarAnimation: 'slide' - }; + screenOptions={{ + headerShown: false, + drawerStyle: { + backgroundColor: '#08090D', + width: '75%' + }, + overlayColor: 'transparent', + drawerType: 'slide', + swipeEdgeWidth: 50 }} drawerContent={(props) => } > diff --git a/apps/mobile/src/navigation/OnboardingNavigator.tsx b/apps/mobile/src/navigation/OnboardingNavigator.tsx index 0a2b07924..96b7f1a04 100644 --- a/apps/mobile/src/navigation/OnboardingNavigator.tsx +++ b/apps/mobile/src/navigation/OnboardingNavigator.tsx @@ -1,3 +1,4 @@ +import CreateLibraryScreen from '@app/screens/onboarding/CreateLibrary'; import OnboardingScreen from '@app/screens/onboarding/Onboarding'; import { StackScreenProps, createStackNavigator } from '@react-navigation/stack'; @@ -7,12 +8,14 @@ export default function OnboardingNavigator() { return ( + ); } export type OnboardingStackParamList = { Onboarding: undefined; + CreateLibrary: undefined; }; export type OnboardingStackScreenProps = diff --git a/apps/mobile/src/navigation/index.tsx b/apps/mobile/src/navigation/index.tsx index 2dabc5711..f36c514eb 100644 --- a/apps/mobile/src/navigation/index.tsx +++ b/apps/mobile/src/navigation/index.tsx @@ -15,7 +15,11 @@ export default function RootNavigator() { - + {/* Modals */} diff --git a/apps/mobile/src/navigation/tabs/OverviewStack.tsx b/apps/mobile/src/navigation/tabs/OverviewStack.tsx index 2245402eb..e1ed17a9d 100644 --- a/apps/mobile/src/navigation/tabs/OverviewStack.tsx +++ b/apps/mobile/src/navigation/tabs/OverviewStack.tsx @@ -14,8 +14,7 @@ export default function OverviewStack() { initialRouteName="Overview" screenOptions={{ headerStyle: { backgroundColor: '#08090D' }, - headerTintColor: '#fff', - ...TransitionPresets.ModalFadeTransition + headerTintColor: '#fff' }} > diff --git a/apps/mobile/src/navigation/tabs/PhotosStack.tsx b/apps/mobile/src/navigation/tabs/PhotosStack.tsx index f97e49889..c417dfc45 100644 --- a/apps/mobile/src/navigation/tabs/PhotosStack.tsx +++ b/apps/mobile/src/navigation/tabs/PhotosStack.tsx @@ -14,8 +14,7 @@ export default function PhotosStack() { initialRouteName="Photos" screenOptions={{ headerStyle: { backgroundColor: '#08090D' }, - headerTintColor: '#fff', - ...TransitionPresets.ModalFadeTransition + headerTintColor: '#fff' }} > diff --git a/apps/mobile/src/navigation/tabs/SpacesStack.tsx b/apps/mobile/src/navigation/tabs/SpacesStack.tsx index ff9fe6f03..1eb9c9f13 100644 --- a/apps/mobile/src/navigation/tabs/SpacesStack.tsx +++ b/apps/mobile/src/navigation/tabs/SpacesStack.tsx @@ -14,8 +14,7 @@ export default function SpacesStack() { initialRouteName="Spaces" screenOptions={{ headerStyle: { backgroundColor: '#08090D' }, - headerTintColor: '#fff', - ...TransitionPresets.ModalFadeTransition + headerTintColor: '#fff' }} > diff --git a/apps/mobile/src/screens/Overview.tsx b/apps/mobile/src/screens/Overview.tsx index f9f7f2747..9ef10e968 100644 --- a/apps/mobile/src/screens/Overview.tsx +++ b/apps/mobile/src/screens/Overview.tsx @@ -1,7 +1,9 @@ import Device from '@app/components/device/Device'; import Dialog from '@app/components/layout/Dialog'; import VirtualizedListWrapper from '@app/components/layout/VirtualizedListWrapper'; +import { TextInput } from '@app/components/primitive/Input'; import OverviewStats from '@app/containers/OverviewStats'; +import { useLibraryQuery } from '@app/hooks/rspc'; import tw from '@app/lib/tailwind'; import { OverviewStackScreenProps } from '@app/navigation/tabs/OverviewStack'; import React from 'react'; @@ -61,11 +63,11 @@ export default function OverviewScreen({ navigation }: OverviewStackScreenProps< Dialog } - /> + > + + {/* Stats */} - {/* Spacing */} - {/* Devices */} ) => { const [loading, setLoading] = useState(false); + // TODO: Animations! + return ( {/* Header */} {/* Search Input */} - + {loading ? ( diff --git a/apps/mobile/src/screens/onboarding/CreateLibrary.tsx b/apps/mobile/src/screens/onboarding/CreateLibrary.tsx new file mode 100644 index 000000000..00fb93cd7 --- /dev/null +++ b/apps/mobile/src/screens/onboarding/CreateLibrary.tsx @@ -0,0 +1,31 @@ +import { AnimatedButton } from '@app/components/primitive/Button'; +import { setItemToStorage } from '@app/lib/storage'; +import tw from '@app/lib/tailwind'; +import { OnboardingStackScreenProps } from '@app/navigation/OnboardingNavigator'; +import { useOnboardingStore } from '@app/stores/useOnboardingStore'; +import React from 'react'; +import { Text, View } from 'react-native'; + +const CreateLibraryScreen = ({ navigation }: OnboardingStackScreenProps<'CreateLibrary'>) => { + const { hideOnboarding } = useOnboardingStore(); + + function onButtonPress() { + setItemToStorage('@onboarding', '1'); + // TODO: Add a loading indicator to button as this takes a second or so. + hideOnboarding(); + } + return ( + + + Onboarding screen for users to create their first library + + + + Create Library + + + + ); +}; + +export default CreateLibraryScreen; diff --git a/apps/mobile/src/screens/onboarding/Onboarding.tsx b/apps/mobile/src/screens/onboarding/Onboarding.tsx index f8ca2f43b..dba1dd8fb 100644 --- a/apps/mobile/src/screens/onboarding/Onboarding.tsx +++ b/apps/mobile/src/screens/onboarding/Onboarding.tsx @@ -1,21 +1,11 @@ import { FadeInUpAnimation, LogoAnimation } from '@app/components/animation/layout'; import { AnimatedButton } from '@app/components/primitive/Button'; -import { setItemToStorage } from '@app/lib/storage'; import tw from '@app/lib/tailwind'; import { OnboardingStackScreenProps } from '@app/navigation/OnboardingNavigator'; -import { useOnboardingStore } from '@app/stores/useOnboardingStore'; import React from 'react'; import { Image, Text, View } from 'react-native'; const OnboardingScreen = ({ navigation }: OnboardingStackScreenProps<'Onboarding'>) => { - const { hideOnboarding } = useOnboardingStore(); - - function onButtonPress() { - setItemToStorage('@onboarding', '1'); - // TODO: Add a loading indicator to button as this takes a second or so. - hideOnboarding(); - } - return ( {/* Logo */} @@ -25,7 +15,7 @@ const OnboardingScreen = ({ navigation }: OnboardingStackScreenProps<'Onboarding {/* Text */} - + A file explorer from the future. @@ -40,7 +30,7 @@ const OnboardingScreen = ({ navigation }: OnboardingStackScreenProps<'Onboarding {/* Get Started Button */} - + navigation.navigate('CreateLibrary')}> Get Started diff --git a/apps/mobile/src/stores/useLibraryStore.ts b/apps/mobile/src/stores/useLibraryStore.ts index 77294c4cc..e5be837af 100644 --- a/apps/mobile/src/stores/useLibraryStore.ts +++ b/apps/mobile/src/stores/useLibraryStore.ts @@ -1,13 +1,73 @@ +import { useBridgeQuery } from '@app/hooks/rspc'; +import { LibraryConfigWrapped } from '@app/types/bindings'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import produce from 'immer'; +import { useMemo } from 'react'; import create from 'zustand'; +import { persist } from 'zustand/middleware'; interface LibraryStore { + _hasHydrated: boolean; + setHasHydrated: (hasHydrated: boolean) => void; currentLibraryUuid: string | null; switchLibrary: (id: string) => void; + init: (libraries: LibraryConfigWrapped[]) => Promise; } -export const useLibraryStore = create()((set) => ({ - currentLibraryUuid: null, - switchLibrary: (uuid) => { - set((state) => ({ currentLibraryUuid: uuid })); - } -})); +export const useLibraryStore = create()( + persist( + (set) => ({ + _hasHydrated: false, + setHasHydrated: (state) => { + set({ _hasHydrated: state }); + }, + currentLibraryUuid: null, + switchLibrary: (uuid) => { + set((state) => + produce(state, (draft) => { + draft.currentLibraryUuid = uuid; + }) + ); + // reset other stores + }, + init: async (libraries) => { + set((state) => + produce(state, (draft) => { + // use first library default if none set + if (!state.currentLibraryUuid) { + draft.currentLibraryUuid = libraries[0].uuid; + } + }) + ); + } + }), + { + name: 'sd-library-store', + getStorage: () => AsyncStorage, + // Since storage is async, app needs to stay in loading state until hydrated! + onRehydrateStorage: () => (state) => { + state.setHasHydrated(true); + } + } + ) +); + +// this must be used at least once in the app to correct the initial state +// is memorized and can be used safely in any component +export const useCurrentLibrary = () => { + const { currentLibraryUuid, switchLibrary } = useLibraryStore(); + const { data: libraries } = useBridgeQuery(['library.get']); + + // memorize library to avoid re-running find function + const currentLibrary = useMemo(() => { + const current = libraries?.find((l) => l.uuid === currentLibraryUuid); + // switch to first library if none set + if (Array.isArray(libraries) && !current && libraries[0]?.uuid) { + switchLibrary(libraries[0]?.uuid); + } + return current; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [libraries, currentLibraryUuid]); + + return { currentLibrary, libraries, currentLibraryUuid }; +};