Update Storage URI settings

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-07-09 18:13:05 +03:00
parent a93630e1e9
commit 9ecc326008
14 changed files with 125 additions and 157 deletions

View File

@@ -118,8 +118,6 @@ dependencies {
} else { } else {
implementation jscFlavor implementation jscFlavor
} }
implementation project(':react-native-fs')
} }
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

View File

@@ -13,7 +13,7 @@ public class MainActivity extends ReactActivity {
*/ */
@Override @Override
protected String getMainComponentName() { protected String getMainComponentName() {
return "TerminallyOnline"; return "Terminally Online";
} }
/** /**

View File

@@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">TerminallyOnline</string> <string name="app_name">Terminally Online</string>
</resources> </resources>

View File

@@ -1,7 +1,4 @@
rootProject.name = 'TerminallyOnline' rootProject.name = 'Terminally Online'
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app' include ':app'
includeBuild('../node_modules/@react-native/gradle-plugin') includeBuild('../node_modules/@react-native/gradle-plugin')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android')

View File

@@ -1,4 +1,4 @@
{ {
"name": "TerminallyOnline", "name": "Terminally Online",
"displayName": "Terminally Online" "displayName": "Terminally Online"
} }

61
package-lock.json generated
View File

@@ -17,7 +17,8 @@
"@realm/react": "^0.5.1", "@realm/react": "^0.5.1",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.1", "react-native": "0.72.1",
"react-native-fs": "^2.20.0", "react-native-document-picker": "^9.0.1",
"react-native-file-access": "^3.0.4",
"react-native-gesture-handler": "^2.12.0", "react-native-gesture-handler": "^2.12.0",
"react-native-paper": "^5.9.1", "react-native-paper": "^5.9.1",
"react-native-reanimated": "2.2.4", "react-native-reanimated": "2.2.4",
@@ -5320,11 +5321,6 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
}, },
"node_modules/base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -12759,15 +12755,15 @@
"react": "18.2.0" "react": "18.2.0"
} }
}, },
"node_modules/react-native-fs": { "node_modules/react-native-document-picker": {
"version": "2.20.0", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz", "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-9.0.1.tgz",
"integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==", "integrity": "sha512-l2c2xChwsdjzZIV9QJc85buC3vXkM5ZuY4943yMDj3TiszJp1spmHNaRMZKYIh3yVwdD2jENm0DBU5AWa+jhLg==",
"dependencies": { "dependencies": {
"base-64": "^0.1.0", "invariant": "^2.2.4"
"utf8": "^3.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "*",
"react-native": "*", "react-native": "*",
"react-native-windows": "*" "react-native-windows": "*"
}, },
@@ -12777,6 +12773,15 @@
} }
} }
}, },
"node_modules/react-native-file-access": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.0.4.tgz",
"integrity": "sha512-sDmGeIIiSlLSrdtvEtADQ1uMoF+Zoegvu/Wr0z0Ej9lU8tHX0C1q+fJnCEBJSUjQ31rSzlabjWgQDKbpOEBdWg==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-gesture-handler": { "node_modules/react-native-gesture-handler": {
"version": "2.12.0", "version": "2.12.0",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.0.tgz", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.0.tgz",
@@ -14731,11 +14736,6 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/utf8": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
"integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -18881,11 +18881,6 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
}, },
"base-64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"base64-js": { "base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -24444,15 +24439,20 @@
} }
} }
}, },
"react-native-fs": { "react-native-document-picker": {
"version": "2.20.0", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz", "resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-9.0.1.tgz",
"integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==", "integrity": "sha512-l2c2xChwsdjzZIV9QJc85buC3vXkM5ZuY4943yMDj3TiszJp1spmHNaRMZKYIh3yVwdD2jENm0DBU5AWa+jhLg==",
"requires": { "requires": {
"base-64": "^0.1.0", "invariant": "^2.2.4"
"utf8": "^3.0.0"
} }
}, },
"react-native-file-access": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.0.4.tgz",
"integrity": "sha512-sDmGeIIiSlLSrdtvEtADQ1uMoF+Zoegvu/Wr0z0Ej9lU8tHX0C1q+fJnCEBJSUjQ31rSzlabjWgQDKbpOEBdWg==",
"requires": {}
},
"react-native-gesture-handler": { "react-native-gesture-handler": {
"version": "2.12.0", "version": "2.12.0",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.0.tgz", "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.12.0.tgz",
@@ -25852,11 +25852,6 @@
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {} "requires": {}
}, },
"utf8": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz",
"integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ=="
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@@ -21,7 +21,8 @@
"@realm/react": "^0.5.1", "@realm/react": "^0.5.1",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.1", "react-native": "0.72.1",
"react-native-fs": "^2.20.0", "react-native-document-picker": "^9.0.1",
"react-native-file-access": "^3.0.4",
"react-native-gesture-handler": "^2.12.0", "react-native-gesture-handler": "^2.12.0",
"react-native-paper": "^5.9.1", "react-native-paper": "^5.9.1",
"react-native-reanimated": "2.2.4", "react-native-reanimated": "2.2.4",

8
src/constants.ts Normal file
View File

@@ -0,0 +1,8 @@
const packageName = 'com.terminallyonline';
const appName = 'Terminally Online';
const escapedAppName = appName.replaceAll(' ', '%20');
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noOp = () => {};
export { packageName, appName, escapedAppName, noOp };

View File

@@ -1,8 +1,14 @@
import React, { createContext, useContext, useState, useEffect } from 'react'; import React, { createContext, useContext, useState, useEffect } from 'react';
import { Settings } from '../types'; import { Settings } from '../types';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import { DocumentDirectoryPath } from 'react-native-fs';
import { LoadingView } from '../components'; import { LoadingView } from '../components';
import WelcomeScreen from '../screens/welcome';
import { FileSystem } from 'react-native-file-access';
import {
getPersistedUriPermissions,
openDocumentTree,
releasePersistableUriPermission,
} from 'react-native-scoped-storage';
interface SettingsContextType { interface SettingsContextType {
settings: Settings; settings: Settings;
@@ -17,28 +23,44 @@ const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({
children, children,
}) => { }) => {
const [settings, setSettings] = useState<Settings>({ const [settings, setSettings] = useState<Settings>({
useInternalStorage: true,
storageUri: '', storageUri: '',
addNoMedia: false,
}); });
const [hasLoaded, setHasLoaded] = useState(false); const [hasLoaded, setHasLoaded] = useState(false);
const updateSettings = (newSettings: Partial<Settings>) => {
const updatedSettings = { ...settings, ...newSettings };
if (settings.storageUri !== updatedSettings.storageUri) {
void getPersistedUriPermissions().then(permissions => {
if (permissions.includes(settings.storageUri)) {
void releasePersistableUriPermission(settings.storageUri);
}
});
void AsyncStorage.setItem('storageUri', updatedSettings.storageUri);
}
setSettings(updatedSettings);
};
useEffect(() => { useEffect(() => {
const loadSettings = async () => { const loadSettings = async () => {
const useInternalStorageValue = await AsyncStorage.getItem(
'useInternalStorage',
);
const storageUriValue = await AsyncStorage.getItem('storageUri'); const storageUriValue = await AsyncStorage.getItem('storageUri');
const addNoMediaValue = await AsyncStorage.getItem('addNoMedia'); let storageUri = storageUriValue ?? '';
if (storageUri !== '') {
const permissions = await getPersistedUriPermissions();
if (!permissions.includes(storageUri)) {
storageUri = '';
}
const exists = await FileSystem.exists(storageUri);
if (!exists) {
storageUri = '';
}
}
setSettings({ setSettings({
useInternalStorage: useInternalStorageValue storageUri,
? (JSON.parse(useInternalStorageValue) as boolean)
: true,
storageUri: storageUriValue ?? DocumentDirectoryPath,
addNoMedia: addNoMediaValue
? (JSON.parse(addNoMediaValue) as boolean)
: false,
}); });
setHasLoaded(true); setHasLoaded(true);
@@ -47,25 +69,23 @@ const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({
void loadSettings(); void loadSettings();
}, []); }, []);
const updateSettings = (newSettings: Partial<Settings>) => {
const updatedSettings = { ...settings, ...newSettings };
void AsyncStorage.setItem(
'useInternalStorage',
JSON.stringify(updatedSettings.useInternalStorage),
);
void AsyncStorage.setItem('storageUri', updatedSettings.storageUri);
void AsyncStorage.setItem(
'addNoMedia',
JSON.stringify(updatedSettings.addNoMedia),
);
setSettings(updatedSettings);
};
return ( return (
<SettingsContext.Provider value={{ settings, setSettings: updateSettings }}> <SettingsContext.Provider value={{ settings, setSettings: updateSettings }}>
{hasLoaded ? children : <LoadingView />} {hasLoaded ? (
settings.storageUri === '' ? (
<WelcomeScreen
selectStorageLocation={() => {
void openDocumentTree().then(uri => {
updateSettings({ storageUri: uri.uri });
});
}}
/>
) : (
children
)
) : (
<LoadingView />
)}
</SettingsContext.Provider> </SettingsContext.Provider>
); );
}; };

View File

@@ -4,7 +4,7 @@ import { Text } from 'react-native-paper';
import { PaddedView } from '../components'; import { PaddedView } from '../components';
import { useSettings } from '../contexts'; import { useSettings } from '../contexts';
function Home(): JSX.Element { const Home = () => {
const { settings } = useSettings(); const { settings } = useSettings();
return ( return (
@@ -13,6 +13,6 @@ function Home(): JSX.Element {
<Text>Settings: {JSON.stringify(settings)}</Text> <Text>Settings: {JSON.stringify(settings)}</Text>
</PaddedView> </PaddedView>
); );
} };
export default Home; export default Home;

View File

@@ -1,16 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import { import { Button, List, Snackbar } from 'react-native-paper';
Button,
Switch,
SegmentedButtons,
Text,
List,
Snackbar,
} from 'react-native-paper';
import { useRealm } from '@realm/react'; import { useRealm } from '@realm/react';
import { openDocumentTree } from 'react-native-scoped-storage'; import { openDocumentTree } from 'react-native-scoped-storage';
import { DocumentDirectoryPath } from 'react-native-fs';
import { PaddedView } from '../components'; import { PaddedView } from '../components';
import styles from '../styles'; import styles from '../styles';
import { Meme } from '../database'; import { Meme } from '../database';
@@ -22,48 +14,7 @@ const SettingsScreen = () => {
const [snackbarVisible, setSnackbarVisible] = useState(false); const [snackbarVisible, setSnackbarVisible] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState(''); const [snackbarMessage, setSnackbarMessage] = useState('');
const { settings, setSettings } = useSettings(); const { setSettings } = useSettings();
const setUseInternalStorage = (use: boolean) => {
if (settings.useInternalStorage === use) {
return;
}
if (use) {
setSettings({
useInternalStorage: use,
storageUri: DocumentDirectoryPath,
});
} else {
openDocumentTree(true)
.then(uri => {
setSettings({
useInternalStorage: use,
storageUri: uri.uri,
});
})
.catch(() => {
setSnackbarMessage('Failed to select storage path!');
setSnackbarVisible(true);
});
}
};
const setStorageUri = (uri: string) => {
if (settings.storageUri === uri) {
return;
}
setSettings({ storageUri: uri });
};
const setAddNoMedia = (add: boolean) => {
if (settings.addNoMedia === add) {
return;
}
setSettings({ addNoMedia: add });
};
const realm = useRealm(); const realm = useRealm();
@@ -105,41 +56,16 @@ const SettingsScreen = () => {
</List.Section> </List.Section>
<List.Section> <List.Section>
<List.Subheader>Media Storage</List.Subheader> <List.Subheader>Media Storage</List.Subheader>
<SegmentedButtons
style={styles.marginBottom}
buttons={[
{ label: 'Internal', value: 'interneal' },
{ label: 'External', value: 'external' },
]}
value={settings.useInternalStorage ? 'interneal' : 'external'}
onValueChange={value =>
setUseInternalStorage(value === 'interneal')
}
/>
<Button <Button
mode="elevated" mode="elevated"
style={styles.marginBottom} style={styles.marginBottom}
disabled={settings.useInternalStorage}
onPress={() => { onPress={() => {
openDocumentTree(true) void openDocumentTree().then(uri => {
.then(uri => { setSettings({ storageUri: uri.uri });
setStorageUri(uri.uri); });
})
.catch(() => {
setSnackbarMessage('Failed to select storage path!');
setSnackbarVisible(true);
});
}}> }}>
Change External Storage Path Change External Storage Path
</Button> </Button>
<View style={styles.spaceBetweenHorizontal}>
<Text>Hide media from Gallery</Text>
<Switch
value={settings.addNoMedia}
onValueChange={setAddNoMedia}
disabled={settings.useInternalStorage}
/>
</View>
</List.Section> </List.Section>
</View> </View>
</PaddedView> </PaddedView>

22
src/screens/welcome.tsx Normal file
View File

@@ -0,0 +1,22 @@
import React from 'react';
import { Button, Text } from 'react-native-paper';
import { PaddedView } from '../components';
import styles from '../styles';
const WelcomeScreen = (properties: { selectStorageLocation: () => void }) => {
return (
<PaddedView centered>
<Text
variant="displayMedium"
style={[styles.marginBottom, styles.centerText]}>
Welcome to Terminally Online!
</Text>
<Button mode="contained" onPress={properties.selectStorageLocation}>
Select Storage Location
</Button>
</PaddedView>
);
};
export default WelcomeScreen;

View File

@@ -9,6 +9,9 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
}, },
centerText: {
textAlign: 'center',
},
}); });
export default styles; export default styles;

View File

@@ -1,7 +1,5 @@
interface Settings { interface Settings {
useInternalStorage: boolean;
storageUri: string; storageUri: string;
addNoMedia: boolean;
} }
export default Settings; export default Settings;