From 2c5bd7cda26c1ee3c2207b3d90eaf3f9afac3559 Mon Sep 17 00:00:00 2001 From: Nikolaos Karaolidis Date: Sun, 9 Jul 2023 00:34:31 +0300 Subject: [PATCH] Add settings page Signed-off-by: Nikolaos Karaolidis --- .eslintrc.json | 2 +- android/app/build.gradle | 2 + android/settings.gradle | 3 + package-lock.json | 115 ++++++++++++++++++++++++ package.json | 3 + src/app.tsx | 48 ++++------ src/components/index.ts | 2 + src/components/loadingView.tsx | 23 +++++ src/components/paddedView.tsx | 31 +++++++ src/contexts/index.ts | 1 + src/contexts/settings.tsx | 81 +++++++++++++++++ src/navigation.tsx | 86 ++++++++++++++++++ src/screens/home.tsx | 18 ++-- src/screens/index.ts | 2 + src/screens/settings.tsx | 159 +++++++++++++++++++++++++++++++++ src/styles.tsx | 14 +++ src/types/index.ts | 1 + src/types/settings.ts | 7 ++ 18 files changed, 554 insertions(+), 44 deletions(-) create mode 100644 src/components/index.ts create mode 100644 src/components/loadingView.tsx create mode 100644 src/components/paddedView.tsx create mode 100644 src/contexts/index.ts create mode 100644 src/contexts/settings.tsx create mode 100644 src/navigation.tsx create mode 100644 src/screens/index.ts create mode 100644 src/screens/settings.tsx create mode 100644 src/styles.tsx create mode 100644 src/types/index.ts create mode 100644 src/types/settings.ts diff --git a/.eslintrc.json b/.eslintrc.json index 84d6caa..ac80492 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -58,7 +58,7 @@ "react-native/no-unused-styles": "error", "react-native/no-inline-styles": "error", "react-native/no-color-literals": "error", - "react-native/no-raw-text": "error", + "react-native/no-raw-text": "off", "no-shadow": "off", "@typescript-eslint/no-shadow": ["error"], "no-unused-vars": "off", diff --git a/android/app/build.gradle b/android/app/build.gradle index 8a275b8..297b0a8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -118,6 +118,8 @@ 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) diff --git a/android/settings.gradle b/android/settings.gradle index 9929224..3704eb3 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -2,3 +2,6 @@ rootProject.name = 'TerminallyOnline' 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') diff --git a/package-lock.json b/package-lock.json index b604f2f..bdcf05e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "hasInstallScript": true, "dependencies": { + "@react-native-async-storage/async-storage": "^1.19.0", "@react-navigation/bottom-tabs": "^6.5.8", "@react-navigation/drawer": "^6.6.3", "@react-navigation/native": "^6.1.7", @@ -16,10 +17,12 @@ "@realm/react": "^0.5.1", "react": "18.2.0", "react-native": "0.72.1", + "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^2.12.0", "react-native-paper": "^5.9.1", "react-native-reanimated": "2.2.4", "react-native-safe-area-context": "^4.6.4", + "react-native-scoped-storage": "^1.9.3", "react-native-screens": "^3.22.1", "react-native-vector-icons": "^9.2.0", "realm": "^11.10.1" @@ -3013,6 +3016,17 @@ "node": ">= 8" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.19.0.tgz", + "integrity": "sha512-xOFkz/FaQctD6yNJDur+WnHdSTigOs3pTz6HmfC8X8PYwcnnN3R9UxuWiwsfK8vvT2WioAxUkQt3lB7GySNA2w==", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || 0.60 - 0.72 || 1000.0.0" + } + }, "node_modules/@react-native-community/cli": { "version": "11.3.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.3.tgz", @@ -5306,6 +5320,11 @@ "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", @@ -8377,6 +8396,14 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -10719,6 +10746,17 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -12721,6 +12759,24 @@ "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==", + "dependencies": { + "base-64": "^0.1.0", + "utf8": "^3.0.0" + }, + "peerDependencies": { + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, "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", @@ -12780,6 +12836,14 @@ "react-native": "*" } }, + "node_modules/react-native-scoped-storage": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/react-native-scoped-storage/-/react-native-scoped-storage-1.9.3.tgz", + "integrity": "sha512-hGqj+2I6pIziIh1CmWXXlgm59wCQLIikETqbMCaFQRHC3yQGI4HdYN6QvM3rteUuR69PNjso+Qd3TJfQnFhlMA==", + "peerDependencies": { + "react-native": "*" + } + }, "node_modules/react-native-screens": { "version": "3.22.1", "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.22.1.tgz", @@ -14667,6 +14731,11 @@ "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", @@ -17051,6 +17120,14 @@ "fastq": "^1.6.0" } }, + "@react-native-async-storage/async-storage": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.19.0.tgz", + "integrity": "sha512-xOFkz/FaQctD6yNJDur+WnHdSTigOs3pTz6HmfC8X8PYwcnnN3R9UxuWiwsfK8vvT2WioAxUkQt3lB7GySNA2w==", + "requires": { + "merge-options": "^3.0.4" + } + }, "@react-native-community/cli": { "version": "11.3.3", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-11.3.3.tgz", @@ -18804,6 +18881,11 @@ "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", @@ -21034,6 +21116,11 @@ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true }, + "is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -22790,6 +22877,14 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, + "merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "requires": { + "is-plain-obj": "^2.1.0" + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -24349,6 +24444,15 @@ } } }, + "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==", + "requires": { + "base-64": "^0.1.0", + "utf8": "^3.0.0" + } + }, "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", @@ -24389,6 +24493,12 @@ "integrity": "sha512-UWYsokTLZmj8g0cluzoUeGUjQrCTW4slKr2xKmuwQCurAuvSJq/QvfhCrqyea++XrXo46+1Q3wSoP50YXG24jA==", "requires": {} }, + "react-native-scoped-storage": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/react-native-scoped-storage/-/react-native-scoped-storage-1.9.3.tgz", + "integrity": "sha512-hGqj+2I6pIziIh1CmWXXlgm59wCQLIikETqbMCaFQRHC3yQGI4HdYN6QvM3rteUuR69PNjso+Qd3TJfQnFhlMA==", + "requires": {} + }, "react-native-screens": { "version": "3.22.1", "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.22.1.tgz", @@ -25742,6 +25852,11 @@ "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", diff --git a/package.json b/package.json index 6026637..91b6400 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "compile": "export $(cat .env | xargs) && cd android && ./gradlew assembleRelease && cd .." }, "dependencies": { + "@react-native-async-storage/async-storage": "^1.19.0", "@react-navigation/bottom-tabs": "^6.5.8", "@react-navigation/drawer": "^6.6.3", "@react-navigation/native": "^6.1.7", @@ -20,10 +21,12 @@ "@realm/react": "^0.5.1", "react": "18.2.0", "react-native": "0.72.1", + "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^2.12.0", "react-native-paper": "^5.9.1", "react-native-reanimated": "2.2.4", "react-native-safe-area-context": "^4.6.4", + "react-native-scoped-storage": "^1.9.3", "react-native-screens": "^3.22.1", "react-native-vector-icons": "^9.2.0", "realm": "^11.10.1" diff --git a/src/app.tsx b/src/app.tsx index b3faf85..2fd862e 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,47 +1,31 @@ -import React from 'react'; +import React, { JSX } from 'react'; import { Appearance, StatusBar } from 'react-native'; -import { NavigationContainer } from '@react-navigation/native'; -import { createMaterialBottomTabNavigator } from 'react-native-paper/react-navigation'; import { PaperProvider } from 'react-native-paper'; import { SafeAreaProvider } from 'react-native-safe-area-context'; -import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; -import Home from './screens/home'; +import { RealmProvider } from '@realm/react'; import { lightTheme, darkTheme } from './theme'; import { Meme, Tag } from './database'; -import { createRealmContext } from '@realm/react'; - -const TabNavigator = createMaterialBottomTabNavigator(); - -const { RealmProvider } = createRealmContext({ - schema: [Meme, Tag], -}); +import NavigationContainer from './navigation'; +import { SettingsProvider } from './contexts/settings'; function App(): JSX.Element { const colorScheme = Appearance.getColorScheme(); const theme = colorScheme === 'dark' ? lightTheme : darkTheme; return ( - + - - - - - ( - - ), - }} - /> - - - + + + + + + ); diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..7d00a61 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,2 @@ +export { default as LoadingView } from './loadingView'; +export { default as PaddedView } from './paddedView'; diff --git a/src/components/loadingView.tsx b/src/components/loadingView.tsx new file mode 100644 index 0000000..3b21fe5 --- /dev/null +++ b/src/components/loadingView.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { ActivityIndicator, StyleSheet, View } from 'react-native'; +import { useTheme } from 'react-native-paper'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}); + +const LoadingView = () => { + const { colors } = useTheme(); + + return ( + + + + ); +}; + +export default LoadingView; diff --git a/src/components/paddedView.tsx b/src/components/paddedView.tsx new file mode 100644 index 0000000..ee6b12d --- /dev/null +++ b/src/components/paddedView.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: '5%', + }, + centered: { + justifyContent: 'center', + alignItems: 'center', + }, +}); + +const PaddedView = ({ + children, + style, + centered, +}: { + children: React.ReactNode; + style?: StyleProp; + centered?: boolean; +}): React.JSX.Element => { + return ( + + {children} + + ); +}; + +export default PaddedView; diff --git a/src/contexts/index.ts b/src/contexts/index.ts new file mode 100644 index 0000000..f0cda84 --- /dev/null +++ b/src/contexts/index.ts @@ -0,0 +1 @@ +export { useSettings, SettingsProvider } from './settings'; diff --git a/src/contexts/settings.tsx b/src/contexts/settings.tsx new file mode 100644 index 0000000..13c3be1 --- /dev/null +++ b/src/contexts/settings.tsx @@ -0,0 +1,81 @@ +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'; + +interface SettingsContextType { + settings: Settings; + setSettings: (newSettings: Partial) => void; +} + +const SettingsContext = createContext( + undefined, +); + +const SettingsProvider: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => { + const [settings, setSettings] = useState({ + useInternalStorage: true, + storageUri: '', + addNoMedia: false, + }); + const [hasLoaded, setHasLoaded] = useState(false); + + useEffect(() => { + const loadSettings = async () => { + const useInternalStorageValue = await AsyncStorage.getItem( + 'useInternalStorage', + ); + const storageUriValue = await AsyncStorage.getItem('storageUri'); + const addNoMediaValue = await AsyncStorage.getItem('addNoMedia'); + + setSettings({ + useInternalStorage: useInternalStorageValue + ? (JSON.parse(useInternalStorageValue) as boolean) + : true, + storageUri: storageUriValue ?? DocumentDirectoryPath, + addNoMedia: addNoMediaValue + ? (JSON.parse(addNoMediaValue) as boolean) + : false, + }); + + setHasLoaded(true); + }; + + void loadSettings(); + }, []); + + const updateSettings = (newSettings: Partial) => { + 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 ( + + {hasLoaded ? children : } + + ); +}; + +const useSettings = (): SettingsContextType => { + const context = useContext(SettingsContext); + if (!context) { + throw new Error('useSettings must be used within a SettingsProvider'); + } + return context; +}; + +export { SettingsProvider, useSettings }; diff --git a/src/navigation.tsx b/src/navigation.tsx new file mode 100644 index 0000000..6323235 --- /dev/null +++ b/src/navigation.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { + CommonActions, + NavigationContainer as NavigationContainerBase, +} from '@react-navigation/native'; +import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { BottomNavigation, useTheme } from 'react-native-paper'; +import { Home, Settings } from './screens'; + +function NavigationContainer() { + const TabNavigator = createBottomTabNavigator(); + const theme = useTheme(); + + return ( + + ( + { + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + if (event.defaultPrevented) { + preventDefault(); + } else { + navigation.dispatch({ + ...CommonActions.navigate(route.name, route.params), + target: state.key, + }); + } + }} + renderIcon={({ route, focused, color }) => { + const { options } = descriptors[route.key]; + if (options.tabBarIcon) { + return options.tabBarIcon({ focused, color, size: 24 }); + } + }} + getLabelText={({ route }) => { + const { options } = descriptors[route.key]; + return options.title ?? route.name; + }} + /> + )}> + ( + + ), + }} + /> + ( + + ), + }} + /> + + + ); +} + +export default NavigationContainer; diff --git a/src/screens/home.tsx b/src/screens/home.tsx index ac156da..ffd26b8 100644 --- a/src/screens/home.tsx +++ b/src/screens/home.tsx @@ -1,21 +1,17 @@ import React from 'react'; -import { View, StyleSheet } from 'react-native'; import { Text } from 'react-native-paper'; - -const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - }, -}); +import { PaddedView } from '../components'; +import { useSettings } from '../contexts'; function Home(): JSX.Element { + const { settings } = useSettings(); + return ( - + Home - + Settings: {JSON.stringify(settings)} + ); } diff --git a/src/screens/index.ts b/src/screens/index.ts new file mode 100644 index 0000000..c838203 --- /dev/null +++ b/src/screens/index.ts @@ -0,0 +1,2 @@ +export { default as Home } from './home'; +export { default as Settings } from './settings'; diff --git a/src/screens/settings.tsx b/src/screens/settings.tsx new file mode 100644 index 0000000..8cc1171 --- /dev/null +++ b/src/screens/settings.tsx @@ -0,0 +1,159 @@ +import React, { useState } from 'react'; +import { View } from 'react-native'; +import { + Button, + Switch, + SegmentedButtons, + Text, + 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'; +import { useSettings } from '../contexts'; + +const SettingsScreen = () => { + const [optimizingDatabase, setOptimizingDatabase] = useState(false); + + 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 realm = useRealm(); + + const optimizeDatabase = () => { + setOptimizingDatabase(true); + + const memes = realm.objects('Meme'); + realm.write(() => { + for (let index = memes.length - 1; index >= 0; index--) { + // TODO: stat the uri to see if it exists and remove entry if it doesn't + } + }); + + const success = realm.compact(); + + if (success) { + setSnackbarMessage('Database optimized!'); + setSnackbarVisible(true); + } else { + setSnackbarMessage('Database optimization failed!'); + setSnackbarVisible(true); + } + setOptimizingDatabase(false); + }; + + return ( + <> + + + + Database + + + + Media Storage + + setUseInternalStorage(value === 'interneal') + } + /> + + + Hide media from Gallery + + + + + + setSnackbarVisible(false)} + action={{ + label: 'Dismiss', + onPress: () => setSnackbarVisible(false), + }}> + {snackbarMessage} + + + ); +}; + +export default SettingsScreen; diff --git a/src/styles.tsx b/src/styles.tsx new file mode 100644 index 0000000..84c0210 --- /dev/null +++ b/src/styles.tsx @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; + +const styles = StyleSheet.create({ + marginBottom: { + marginBottom: 15, + }, + spaceBetweenHorizontal: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, +}); + +export default styles; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..23038ec --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1 @@ +export type { default as Settings } from './settings'; diff --git a/src/types/settings.ts b/src/types/settings.ts new file mode 100644 index 0000000..58adf23 --- /dev/null +++ b/src/types/settings.ts @@ -0,0 +1,7 @@ +interface Settings { + useInternalStorage: boolean; + storageUri: string; + addNoMedia: boolean; +} + +export default Settings;