Update Storage URI settings
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -118,8 +118,6 @@ dependencies {
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
||||
implementation project(':react-native-fs')
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
|
@@ -13,7 +13,7 @@ public class MainActivity extends ReactActivity {
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "TerminallyOnline";
|
||||
return "Terminally Online";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,3 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">TerminallyOnline</string>
|
||||
<string name="app_name">Terminally Online</string>
|
||||
</resources>
|
||||
|
@@ -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)
|
||||
include ':app'
|
||||
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')
|
||||
|
2
app.json
2
app.json
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "TerminallyOnline",
|
||||
"name": "Terminally Online",
|
||||
"displayName": "Terminally Online"
|
||||
}
|
||||
|
61
package-lock.json
generated
61
package-lock.json
generated
@@ -17,7 +17,8 @@
|
||||
"@realm/react": "^0.5.1",
|
||||
"react": "18.2.0",
|
||||
"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-paper": "^5.9.1",
|
||||
"react-native-reanimated": "2.2.4",
|
||||
@@ -5320,11 +5321,6 @@
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"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": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -12759,15 +12755,15 @@
|
||||
"react": "18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-fs": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz",
|
||||
"integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==",
|
||||
"node_modules/react-native-document-picker": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-9.0.1.tgz",
|
||||
"integrity": "sha512-l2c2xChwsdjzZIV9QJc85buC3vXkM5ZuY4943yMDj3TiszJp1spmHNaRMZKYIh3yVwdD2jENm0DBU5AWa+jhLg==",
|
||||
"dependencies": {
|
||||
"base-64": "^0.1.0",
|
||||
"utf8": "^3.0.0"
|
||||
"invariant": "^2.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"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": {
|
||||
"version": "2.12.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
@@ -24444,15 +24439,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-native-fs": {
|
||||
"version": "2.20.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.20.0.tgz",
|
||||
"integrity": "sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==",
|
||||
"react-native-document-picker": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-document-picker/-/react-native-document-picker-9.0.1.tgz",
|
||||
"integrity": "sha512-l2c2xChwsdjzZIV9QJc85buC3vXkM5ZuY4943yMDj3TiszJp1spmHNaRMZKYIh3yVwdD2jENm0DBU5AWa+jhLg==",
|
||||
"requires": {
|
||||
"base-64": "^0.1.0",
|
||||
"utf8": "^3.0.0"
|
||||
"invariant": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "2.12.0",
|
||||
"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==",
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
@@ -21,7 +21,8 @@
|
||||
"@realm/react": "^0.5.1",
|
||||
"react": "18.2.0",
|
||||
"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-paper": "^5.9.1",
|
||||
"react-native-reanimated": "2.2.4",
|
||||
|
8
src/constants.ts
Normal file
8
src/constants.ts
Normal 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 };
|
@@ -1,8 +1,14 @@
|
||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||
import { Settings } from '../types';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import { DocumentDirectoryPath } from 'react-native-fs';
|
||||
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 {
|
||||
settings: Settings;
|
||||
@@ -17,28 +23,44 @@ const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [settings, setSettings] = useState<Settings>({
|
||||
useInternalStorage: true,
|
||||
storageUri: '',
|
||||
addNoMedia: 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(() => {
|
||||
const loadSettings = async () => {
|
||||
const useInternalStorageValue = await AsyncStorage.getItem(
|
||||
'useInternalStorage',
|
||||
);
|
||||
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({
|
||||
useInternalStorage: useInternalStorageValue
|
||||
? (JSON.parse(useInternalStorageValue) as boolean)
|
||||
: true,
|
||||
storageUri: storageUriValue ?? DocumentDirectoryPath,
|
||||
addNoMedia: addNoMediaValue
|
||||
? (JSON.parse(addNoMediaValue) as boolean)
|
||||
: false,
|
||||
storageUri,
|
||||
});
|
||||
|
||||
setHasLoaded(true);
|
||||
@@ -47,25 +69,23 @@ const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||
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 (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@ import { Text } from 'react-native-paper';
|
||||
import { PaddedView } from '../components';
|
||||
import { useSettings } from '../contexts';
|
||||
|
||||
function Home(): JSX.Element {
|
||||
const Home = () => {
|
||||
const { settings } = useSettings();
|
||||
|
||||
return (
|
||||
@@ -13,6 +13,6 @@ function Home(): JSX.Element {
|
||||
<Text>Settings: {JSON.stringify(settings)}</Text>
|
||||
</PaddedView>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Home;
|
||||
|
@@ -1,16 +1,8 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import {
|
||||
Button,
|
||||
Switch,
|
||||
SegmentedButtons,
|
||||
Text,
|
||||
List,
|
||||
Snackbar,
|
||||
} from 'react-native-paper';
|
||||
import { Button, List, Snackbar } from 'react-native-paper';
|
||||
import { useRealm } from '@realm/react';
|
||||
import { openDocumentTree } from 'react-native-scoped-storage';
|
||||
import { DocumentDirectoryPath } from 'react-native-fs';
|
||||
import { PaddedView } from '../components';
|
||||
import styles from '../styles';
|
||||
import { Meme } from '../database';
|
||||
@@ -22,48 +14,7 @@ const SettingsScreen = () => {
|
||||
const [snackbarVisible, setSnackbarVisible] = useState(false);
|
||||
const [snackbarMessage, setSnackbarMessage] = useState('');
|
||||
|
||||
const { settings, 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 { setSettings } = useSettings();
|
||||
|
||||
const realm = useRealm();
|
||||
|
||||
@@ -105,41 +56,16 @@ const SettingsScreen = () => {
|
||||
</List.Section>
|
||||
<List.Section>
|
||||
<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
|
||||
mode="elevated"
|
||||
style={styles.marginBottom}
|
||||
disabled={settings.useInternalStorage}
|
||||
onPress={() => {
|
||||
openDocumentTree(true)
|
||||
.then(uri => {
|
||||
setStorageUri(uri.uri);
|
||||
})
|
||||
.catch(() => {
|
||||
setSnackbarMessage('Failed to select storage path!');
|
||||
setSnackbarVisible(true);
|
||||
});
|
||||
void openDocumentTree().then(uri => {
|
||||
setSettings({ storageUri: uri.uri });
|
||||
});
|
||||
}}>
|
||||
Change External Storage Path
|
||||
</Button>
|
||||
<View style={styles.spaceBetweenHorizontal}>
|
||||
<Text>Hide media from Gallery</Text>
|
||||
<Switch
|
||||
value={settings.addNoMedia}
|
||||
onValueChange={setAddNoMedia}
|
||||
disabled={settings.useInternalStorage}
|
||||
/>
|
||||
</View>
|
||||
</List.Section>
|
||||
</View>
|
||||
</PaddedView>
|
||||
|
22
src/screens/welcome.tsx
Normal file
22
src/screens/welcome.tsx
Normal 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;
|
@@ -9,6 +9,9 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
centerText: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default styles;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
interface Settings {
|
||||
useInternalStorage: boolean;
|
||||
storageUri: string;
|
||||
addNoMedia: boolean;
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
|
Reference in New Issue
Block a user