Fix adding locations to mobile application (#1681)

* Fix for android location adding fails

Added a fix for the file path error when creating locations on Android.

* Check for read-only folders

Well, it's a hacky way, and I'm sure rust may have a better solution, but for now, this checks if the app can make a file called "text.txt". If it works, it's deleted and marked as readable.

* UI Fixes for Navigation

On Android, the back button would not render for some reason. Had to go around and add to the navigation options for each screen the left arrow to go back.

Also, built the about page from the desktop builds of Spacedrive in `about.tsx`.

* Tailwind + Navigation refactor

Navigation has been fixed to not use useEffect, but now exists in the `<Stack.Screen />` code.

Also, refactored `about.tsx` to now use Tailwind instead of pure styles.

* Update AppearanceSettings.tsx

* Update AppearanceSettings.tsx

* Clean up of unused import calls

* Quick Fixes

About now has build info showing up.

Had to remove the read-only check because it was breaking adding new folders.

* Update ImportModal.tsx

Delete unused code.

* Update Settings.tsx

Remove unused imports.
This commit is contained in:
Arnab Chakraborty 2023-11-01 10:28:23 -04:00 committed by GitHub
parent c91ccff37d
commit 576464573a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 255 additions and 38 deletions

View file

@ -24,3 +24,6 @@ interface/components/TextViewer/prism.tsx
.next/
.contentlayer/
# Stops from constant package.json changes showing up in commits
package*.json

View file

@ -1,13 +1,12 @@
import { forwardRef, useCallback } from 'react';
import { Alert, Text, View } from 'react-native';
import { Alert, Text, View, Platform } from 'react-native';
import DocumentPicker from 'react-native-document-picker';
import { useLibraryMutation } 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 RFS from 'react-native-fs';
import RNFS from 'react-native-fs';
// import * as ML from 'expo-media-library';
// WIP component
@ -44,11 +43,37 @@ const ImportModal = forwardRef<ModalRef, unknown>((_, ref) => {
if (!response) return;
createLocation.mutate({
path: decodeURIComponent(response.uri.replace('file://', '')),
dry_run: false,
indexer_rules_ids: []
});
const uri = response.uri;
if (Platform.OS === 'android') {
// The following code turns this: content://com.android.externalstorage.documents/tree/[filePath] into this: /storage/emulated/0/[directoryName]
// Example: content://com.android.externalstorage.documents/tree/primary%3ADownload%2Ftest into /storage/emulated/0/Download/test
const dirName = decodeURIComponent(uri).split('/');
// Remove all elements before 'tree'
dirName.splice(0, dirName.indexOf('tree') + 1);
const parsedDirName = dirName.join('/').split(':')[1]
const dirPath = RNFS.ExternalStorageDirectoryPath + '/' + parsedDirName;
//Verify that the directory exists
const dirExists = await RNFS.exists(dirPath);
if (!dirExists) {
console.error('Directory does not exist'); //TODO: Make this a UI error
return;
}
createLocation.mutate({
path: dirPath,
dry_run: false,
indexer_rules_ids: []
});
} else {
// iOS
createLocation.mutate({
path: decodeURIComponent(uri.replace('file://', '')),
dry_run: false,
indexer_rules_ids: []
});
}
} catch (err) {
console.error(err);
}

View file

@ -1,4 +1,5 @@
import { createStackNavigator, StackScreenProps } from '@react-navigation/stack';
import { ArrowLeft } from 'phosphor-react-native';
import { tw } from '~/lib/tailwind';
import AppearanceSettingsScreen from '~/screens/settings/client/AppearanceSettings';
import ExtensionsSettingsScreen from '~/screens/settings/client/ExtensionsSettings';
@ -35,59 +36,114 @@ export default function SettingsNavigator() {
<SettingsStack.Screen
name="Home"
component={SettingsScreen}
options={{ headerTitle: 'Settings' }}
options={{
headerTitle: 'Settings',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
{/* Client */}
<SettingsStack.Screen
name="GeneralSettings"
component={GeneralSettingsScreen}
options={{ headerTitle: 'General Settings' }}
options={{
headerTitle: 'General Settings',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="LibrarySettings"
component={LibrarySettingsScreen}
options={{ headerTitle: 'Libraries' }}
options={{
headerTitle: 'Libraries',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="AppearanceSettings"
component={AppearanceSettingsScreen}
options={{ headerTitle: 'Appearance' }}
options={{
headerTitle: 'Appearance',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="PrivacySettings"
component={PrivacySettingsScreen}
options={{ headerTitle: 'Privacy' }}
options={{
headerTitle: 'Privacy',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="ExtensionsSettings"
component={ExtensionsSettingsScreen}
options={{ headerTitle: 'Extensions' }}
options={{
headerTitle: 'Extensions',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
{/* Library */}
<SettingsStack.Screen
name="LibraryGeneralSettings"
component={LibraryGeneralSettingsScreen}
options={{ headerTitle: 'Library Settings' }}
options={{
headerTitle: 'Library Settings',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="LocationSettings"
component={LocationSettingsScreen}
options={{ headerTitle: 'Locations' }}
options={{
headerTitle: 'Locations',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="EditLocationSettings"
component={EditLocationSettingsScreen}
options={{ headerTitle: 'Edit Location' }}
options={{
headerTitle: 'Edit Location',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="NodesSettings"
component={NodesSettingsScreen}
options={{ headerTitle: 'Nodes' }}
options={{
headerTitle: 'Nodes',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="TagsSettings"
component={TagsSettingsScreen}
options={{ headerTitle: 'Tags' }}
options={{
headerTitle: 'Tags',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
{/* <SettingsStack.Screen
name="KeysSettings"
@ -98,17 +154,32 @@ export default function SettingsNavigator() {
<SettingsStack.Screen
name="About"
component={AboutScreen}
options={{ headerTitle: 'About' }}
options={{
headerTitle: 'About',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="Support"
component={SupportScreen}
options={{ headerTitle: 'Support' }}
options={{
headerTitle: 'Support',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<SettingsStack.Screen
name="Debug"
component={DebugScreen}
options={{ headerTitle: 'Debug' }}
options={{
headerTitle: 'Debug',
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
</SettingsStack.Navigator>
);

View file

@ -4,6 +4,8 @@ import {
StackNavigationOptions,
StackScreenProps
} from '@react-navigation/stack';
import { ArrowLeft } from 'phosphor-react-native';
import { tw } from '~/lib/tailwind';
import LocationScreen from '~/screens/Location';
import TagScreen from '~/screens/Tag';
@ -19,8 +21,17 @@ export function SharedScreens(
) {
return (
<>
<Stack.Screen name="Location" component={LocationScreen} />
<Stack.Screen name="Tag" component={TagScreen} />
<Stack.Screen name="Location" component={LocationScreen} options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<Stack.Screen name="Tag" component={TagScreen} options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}} />
</>
);
}

View file

@ -1,4 +1,4 @@
import { useEffect } from 'react';
import React, { useEffect } from 'react';
import { useLibraryQuery } from '@sd/client';
import Explorer from '~/components/explorer/Explorer';
import { SharedScreenProps } from '~/navigation/SharedScreens';
@ -32,7 +32,7 @@ export default function LocationScreen({ navigation, route }: SharedScreenProps<
});
} else {
navigation.setOptions({
title: location.data?.name ?? 'Location'
title: location.data?.name ?? 'Location',
});
}
}, [location.data?.name, navigation, path]);

View file

@ -104,12 +104,12 @@ const sections: (debugState: DebugState) => SectionType[] = (debugState) => [
},
...(debugState.enabled
? ([
{
icon: Gear,
navigateTo: 'Debug',
title: 'Debug'
}
] as const)
{
icon: Gear,
navigateTo: 'Debug',
title: 'Debug'
}
] as const)
: [])
]
}

View file

@ -1,14 +1,118 @@
import React from 'react';
import { Text, View } from 'react-native';
import { ArrowLeft, Globe } from 'phosphor-react-native';
import React, { useEffect } from 'react';
import { Image, Linking, Platform, Text, View } from 'react-native';
import { Divider } from '~/components/primitive/Divider';
import { tw } from '~/lib/tailwind';
import { Button } from '~/components/primitive/Button';
import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator';
import Svg, { Path, SvgProps } from "react-native-svg"
import { useBridgeQuery } from '@sd/client';
const AboutScreen = ({ navigation }: SettingsStackScreenProps<'About'>) => {
const buildInfo = useBridgeQuery(['buildInfo']);
return (
<View>
<Text style={tw`text-ink`}>TODO</Text>
<View style={tw.style('flex-1 p-5')}>
<View style={tw.style('flex flex-row items-center')}>
<Image
source={require('../../../../assets/icon.png')}
style={tw.style('mr-8 h-[88px] w-[88px] rounded-3xl')}
resizeMode="contain"
/>
<View style={tw.style('flex flex-col')}>
<Text style={tw.style('text-2xl font-bold text-white')}>
Spacedrive {`for ${Platform.OS === 'android' ? Platform.OS[0]?.toUpperCase() + Platform.OS.slice(1) : Platform.OS[0] + Platform.OS.slice(1).toUpperCase()}`}
</Text>
<Text style={tw.style('mt-1 text-sm text-ink-dull')}>
The file manager from the future.
</Text>
<Text style={tw.style('mt-1 text-xs text-ink-faint/80')}>
v{buildInfo.data?.version || '-.-.-'} - {buildInfo.data?.commit || 'dev'}
</Text>
</View>
</View>
{/* iOS has buttons falling out of the screen for some reason. So, I made the buttons veritical instead */}
<View style={tw.style('my-5 flex-col justify-between gap-2')}>
{/* Discord Button */}
<Button
onPress={() => Linking.openURL('https://discord.gg/ukRnWSnAbG')}
style={tw.style('flex-row items-center')}
variant='gray'
>
<View style={tw.style('h-4 w-4')}>
<DiscordRN fill="white" />
</View>
<Text style={tw.style('ml-2 text-white')}>Join Discord</Text>
</Button>
{/* GitHub Button */}
<Button
onPress={() => Linking.openURL('https://github.com/spacedriveapp/spacedrive')}
style={tw.style('flex-row items-center')}
variant="accent"
>
<View style={tw.style('h-4 w-4')}>
<GitHubRN fill="white" />
</View>
<Text style={tw.style('ml-2 text-white')}>Star on GitHub</Text>
</Button>
{/* Website Button */}
<Button
onPress={() => Linking.openURL('https://spacedrive.app')}
style={tw.style('flex-row items-center')}
variant="accent"
>
<View style={tw.style('h-4 w-4')}>
<Globe size={16} color="white" />
</View>
<Text style={tw.style('ml-2 text-white')}>Website</Text>
</Button>
</View>
<Divider />
<View style={tw.style('my-5')}>
<Text style={tw.style('mb-3 text-lg font-bold text-ink')}>Vision</Text>
<Text style={tw.style('w-full text-sm text-ink-faint')}>
Many of us have multiple cloud accounts, drives that arent backed up and data
at risk of loss. We depend on cloud services like Google Photos and iCloud, but
are locked in with limited capacity and almost zero interoperability between
services and operating systems. Photo albums shouldnt be stuck in a device
ecosystem, or harvested for advertising data. They should be OS agnostic,
permanent and personally owned. Data we create is our legacy, that will long
outlive usopen source technology is the only way to ensure we retain absolute
control over the data that defines our lives, at unlimited scale.
</Text>
</View>
<Divider />
<View>
<Text style={tw.style('my-5 text-lg font-bold text-ink')}>
Meet the contributors behind Spacedrive
</Text>
{/* For some reason, it won't load. ¯\_(ツ)_/¯ */}
<Image
source={{
uri:
'https://contrib.rocks/image?repo=spacedriveapp/spacedrive&columns=12&anon=1',
}}
style={{ height: 200, width: '100%' }}
resizeMode="contain"
/>
</View>
</View>
);
};
export default AboutScreen;
// React Native doesn't allow for SVGs to be imported, so we have to use react-native-svg.
const DiscordRN = (props: SvgProps) => (
<Svg viewBox="0 0 24 24" {...props}>
<Path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z" />
</Svg>
)
const GitHubRN = (props: SvgProps) => (
<Svg viewBox="0 0 24 24" {...props}>
<Path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</Svg>
)

View file

@ -1,4 +1,4 @@
import { CaretRight, Pen, Trash } from 'phosphor-react-native';
import { ArrowLeft, CaretRight, Pen, Trash } from 'phosphor-react-native';
import { useEffect, useRef } from 'react';
import { Animated, FlatList, Text, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
@ -83,7 +83,10 @@ const TagsSettingsScreen = ({ navigation }: SettingsStackScreenProps<'TagsSettin
>
<Text style={tw`text-white`}>New</Text>
</AnimatedButton>
)
),
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
),
});
}, [navigation]);