-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[MOB-106] Cloud Sync for Mobile (#2549)
* wip + working backfill * Finished BackfillWaiting page + initial auth setup Also, setting up the cloud sync page. * mobile auth * Import Cloud Library Currently, you can import a cloud library, however it seems that the data, such as locations, is not transferring correctly. * Working Mobile Cloud Sync Cloud Sync works for Mobile, and the mobile app can sync files from a cloud library, and other clients can access the data from the phone's cloud library. * Cloud Sync Done * Formatting * Fix new library button * New device type passing to auth * Improve design of cloud settings and import modal * ui adjustments and code cleanup * Update styling if there's only 1 instance * code cleanup, design tweaks * empty state & simple indicator animation * lint * loading indicator and cleanup * Fix to Sync Subscription * Update Cargo.lock * Async logout for debug * tweaks * Update SettingsStack.tsx * cleanups and cloud desktop design improvements * more cleanups and ui improvements * ts * i18n * Cloud Sync Docs * styling * Delete library-sync.mdx Moving docs to a separate branch --------- Co-authored-by: ameer2468 <[email protected]>
- Loading branch information
1 parent
e3202b3
commit 18235c6
Showing
42 changed files
with
1,277 additions
and
75 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
apps/mobile/src/components/modal/ImportLibraryModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { BottomSheetFlatList } from '@gorhom/bottom-sheet'; | ||
import { NavigationProp, useNavigation } from '@react-navigation/native'; | ||
import { forwardRef } from 'react'; | ||
import { ActivityIndicator, Text, View } from 'react-native'; | ||
import { | ||
CloudLibrary, | ||
useBridgeMutation, | ||
useBridgeQuery, | ||
useClientContext, | ||
useRspcContext | ||
} from '@sd/client'; | ||
import { Modal, ModalRef } from '~/components/layout/Modal'; | ||
import { Button } from '~/components/primitive/Button'; | ||
import useForwardedRef from '~/hooks/useForwardedRef'; | ||
import { tw } from '~/lib/tailwind'; | ||
import { RootStackParamList } from '~/navigation'; | ||
import { currentLibraryStore } from '~/utils/nav'; | ||
|
||
import Empty from '../layout/Empty'; | ||
import Fade from '../layout/Fade'; | ||
|
||
const ImportModalLibrary = forwardRef<ModalRef, unknown>((_, ref) => { | ||
const navigation = useNavigation<NavigationProp<RootStackParamList>>(); | ||
const modalRef = useForwardedRef(ref); | ||
|
||
const { libraries } = useClientContext(); | ||
|
||
const cloudLibraries = useBridgeQuery(['cloud.library.list']); | ||
const cloudLibrariesData = cloudLibraries.data?.filter( | ||
(cloudLibrary) => !libraries.data?.find((l) => l.uuid === cloudLibrary.uuid) | ||
); | ||
|
||
return ( | ||
<Modal | ||
ref={modalRef} | ||
snapPoints={cloudLibrariesData?.length !== 0 ? ['30', '50'] : ['30']} | ||
title="Join a Cloud Library" | ||
showCloseButton | ||
> | ||
<View style={tw`relative flex-1`}> | ||
{cloudLibraries.isLoading ? ( | ||
<View style={tw`mt-10 items-center justify-center`}> | ||
<ActivityIndicator size="small" /> | ||
</View> | ||
) : ( | ||
<Fade | ||
width={20} | ||
height="100%" | ||
fadeSides="top-bottom" | ||
orientation="vertical" | ||
color="bg-app-modal" | ||
> | ||
<BottomSheetFlatList | ||
data={cloudLibrariesData} | ||
contentContainerStyle={tw`px-4 pb-6 pt-5`} | ||
ItemSeparatorComponent={() => <View style={tw`h-2`} />} | ||
ListEmptyComponent={ | ||
<Empty | ||
icon="Drive" | ||
style={tw`mt-2 border-0`} | ||
iconSize={46} | ||
description="You don't have any cloud libraries" | ||
/> | ||
} | ||
keyExtractor={(item) => item.uuid} | ||
showsVerticalScrollIndicator={false} | ||
renderItem={({ item }) => ( | ||
<CloudLibraryCard | ||
data={item} | ||
navigation={navigation} | ||
modalRef={modalRef} | ||
/> | ||
)} | ||
/> | ||
</Fade> | ||
)} | ||
</View> | ||
</Modal> | ||
); | ||
}); | ||
|
||
interface Props { | ||
data: CloudLibrary; | ||
modalRef: React.RefObject<ModalRef>; | ||
navigation: NavigationProp<RootStackParamList>; | ||
} | ||
|
||
const CloudLibraryCard = ({ data, modalRef, navigation }: Props) => { | ||
const rspc = useRspcContext().queryClient; | ||
const joinLibrary = useBridgeMutation(['cloud.library.join']); | ||
return ( | ||
<View | ||
key={data.uuid} | ||
style={tw`flex flex-row items-center justify-between gap-2 rounded-md border border-app-box bg-app p-2`} | ||
> | ||
<Text numberOfLines={1} style={tw`max-w-[80%] text-sm font-bold text-ink`}> | ||
{data.name} | ||
</Text> | ||
<Button | ||
size="sm" | ||
variant="accent" | ||
disabled={joinLibrary.isLoading} | ||
onPress={async () => { | ||
const library = await joinLibrary.mutateAsync(data.uuid); | ||
|
||
rspc.setQueryData(['library.list'], (libraries: any) => { | ||
// The invalidation system beat us to it | ||
if ((libraries || []).find((l: any) => l.uuid === library.uuid)) | ||
return libraries; | ||
|
||
return [...(libraries || []), library]; | ||
}); | ||
|
||
currentLibraryStore.id = library.uuid; | ||
|
||
navigation.navigate('Root', { | ||
screen: 'Home', | ||
params: { | ||
screen: 'OverviewStack', | ||
params: { | ||
screen: 'Overview' | ||
} | ||
} | ||
}); | ||
|
||
modalRef.current?.dismiss(); | ||
}} | ||
> | ||
<Text style={tw`text-sm font-medium text-white`}> | ||
{joinLibrary.isLoading && joinLibrary.variables === data.uuid | ||
? 'Joining...' | ||
: 'Join'} | ||
</Text> | ||
</Button> | ||
</View> | ||
); | ||
}; | ||
|
||
export default ImportModalLibrary; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack'; | ||
import React from 'react'; | ||
import BackfillWaiting from '~/screens/BackfillWaiting'; | ||
|
||
const Stack = createNativeStackNavigator<BackfillWaitingStackParamList>(); | ||
|
||
export default function BackfillWaitingStack() { | ||
return ( | ||
<Stack.Navigator initialRouteName="BackfillWaiting"> | ||
<Stack.Screen | ||
name="BackfillWaiting" | ||
component={BackfillWaiting} | ||
options={{ | ||
headerShown: false | ||
}} | ||
/> | ||
</Stack.Navigator> | ||
); | ||
} | ||
|
||
export type BackfillWaitingStackParamList = { | ||
BackfillWaiting: undefined; | ||
}; | ||
|
||
export type BackfillWaitingStackScreenProps<Screen extends keyof BackfillWaitingStackParamList> = | ||
NativeStackScreenProps<BackfillWaitingStackParamList, Screen>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { useNavigation } from '@react-navigation/native'; | ||
import { AppLogo } from '@sd/assets/images'; | ||
import { useLibraryMutation, useLibraryQuery } from '@sd/client'; | ||
import { Image } from 'expo-image'; | ||
import React, { useEffect } from 'react'; | ||
import { Dimensions, Text, View } from 'react-native'; | ||
import Animated, { | ||
Easing, | ||
useAnimatedStyle, | ||
useSharedValue, | ||
withRepeat, | ||
withTiming | ||
} from 'react-native-reanimated'; | ||
import { Circle, Defs, RadialGradient, Stop, Svg } from 'react-native-svg'; | ||
import { tw, twStyle } from '~/lib/tailwind'; | ||
|
||
const { width } = Dimensions.get('window'); | ||
|
||
const BackfillWaiting = () => { | ||
const animation = useSharedValue(0); | ||
const navigation = useNavigation(); | ||
|
||
useEffect(() => { | ||
animation.value = withRepeat( | ||
withTiming(1, { duration: 5000, easing: Easing.inOut(Easing.ease) }), | ||
-1, | ||
true | ||
); | ||
}, [animation]); | ||
|
||
const animatedStyle = useAnimatedStyle(() => { | ||
return { | ||
opacity: animation.value | ||
}; | ||
}); | ||
|
||
const enableSync = useLibraryMutation(['sync.backfill'], { | ||
onSuccess: () => { | ||
syncEnabled.refetch(); | ||
navigation.navigate('Root', { | ||
screen: 'Home', | ||
params: { | ||
screen: 'SettingsStack', | ||
params: { | ||
screen: 'SyncSettings' | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
|
||
const syncEnabled = useLibraryQuery(['sync.enabled']); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
await enableSync.mutateAsync(null); | ||
})(); | ||
}, []); | ||
|
||
return ( | ||
<View style={tw`flex-1 items-center justify-center bg-black`}> | ||
<Animated.View style={[twStyle(`absolute items-center justify-center`, { | ||
width: width * 2, | ||
height: width * 2, | ||
borderRadius: (width * 0.8) / 2, | ||
}), animatedStyle]}> | ||
<Svg height="100%" width="100%" viewBox="0 0 100 100"> | ||
<Defs> | ||
<RadialGradient id="grad" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"> | ||
<Stop offset="0%" stopColor="#4B0082" stopOpacity="1" /> | ||
<Stop offset="100%" stopColor="#000000" stopOpacity="0" /> | ||
</RadialGradient> | ||
</Defs> | ||
<Circle cx="50" cy="50" r="50" fill="url(#grad)" /> | ||
</Svg> | ||
</Animated.View> | ||
<Image source={AppLogo} style={tw`mb-4 h-[100px] w-[100px]`} /> | ||
<Text style={tw`mx-10 mb-4 text-center text-md leading-6 text-ink`}> | ||
Library is being backfilled right now for Sync! | ||
<Text style={tw`font-bold`}> Please hold </Text> | ||
while this process takes place. | ||
</Text> | ||
</View> | ||
); | ||
}; | ||
|
||
export default BackfillWaiting; |
Oops, something went wrong.