From aa46586b983e4ff85c965c7cab8f45d766053093 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 13 Aug 2023 10:45:23 +0000 Subject: [PATCH] no devices found and buttons for factory reset and disconnect from device --- App.js | 22 +- locales/de.json | 28 +- locales/en.json | 26 +- package-lock.json | 10 + package.json | 1 + src/Components/Button/index.js | 15 +- src/Components/Modal/index.js | 94 ++++-- src/Components/Result/index.js | 6 +- src/Components/SideBar/index.js | 47 ++- .../Device/modals/EditActions/Wait/index.js | 2 + src/Screens/Device/scene.js | 15 - src/Screens/Device/settings.js | 269 ++++++++++++++++-- src/Screens/NoDevicesConnected/index.js | 26 ++ src/utils.js | 8 + 14 files changed, 486 insertions(+), 83 deletions(-) create mode 100644 src/Screens/NoDevicesConnected/index.js diff --git a/App.js b/App.js index 18f08a6..0cbb3b8 100644 --- a/App.js +++ b/App.js @@ -40,7 +40,6 @@ import MotorEditActionModalContent, { } from "./src/Screens/Device/modals/EditActions/Motor"; import WaitXSecondsEditActionModalContent from "./src/Screens/Device/modals/EditActions/Wait"; import StopEditActionModalContent from "./src/Screens/Device/modals/EditActions/Stop"; -import AsyncStorage from "@react-native-async-storage/async-storage"; import SearchForNewDevicesModalContent from "./src/Screens/SearchForNewDevices"; const Drawer = createDrawerNavigator(); @@ -48,6 +47,9 @@ const Stack = createStackNavigator(); const SettingsScreen = lazy(() => import("./src/Screens/Settings")); const HelpScreen = lazy(() => import("./src/Screens/Help")); +const NoDevicesConnectedScreen = lazy(() => + import("./src/Screens/NoDevicesConnected") +); export function MyApp() { const appContext = useContext(AppContext); @@ -363,6 +365,11 @@ function MyDrawer() { + + ); } @@ -379,6 +386,19 @@ const DeviceScreenStack = (props) => { ); }; +const NoDevicesConnectedStack = (props) => { + const { t } = useTranslation(); + + return ( + + ); +}; + const HelpScreenStack = (props) => { const { t } = useTranslation(); diff --git a/locales/de.json b/locales/de.json index 86c2228..166c26d 100644 --- a/locales/de.json +++ b/locales/de.json @@ -7,7 +7,9 @@ "sideBar": { "devicesTitle": "Geräte", "settings": "Einstellungen", - "help": "Hilfe" + "help": "Hilfe", + "noDevicesConnected": "Keine Geräte verbunden", + "textButtonSearchForNewDevices": "Suche nach neuen Geräten" }, "screens": { "device": { @@ -20,11 +22,30 @@ "deviceModelText": "Gerätemodell", "deviceFirmwareVersionText": "Firmware Version", "deviceLastUpdatedText": "Letzte Aktualisierung", + "deviceMacAddressText": "MAC-Adresse", "deviceIPAddressText": "IP-Adresse", "modalSettingsChangeDeviceDisplayName": { "pageTitle": "Gerätename ändern", "textTitle": "Gerätename", "textDescription": "Der Name wird lokal auf dem Gerät gespeichert und dient dazu, die verbundenen Geräte in der App voneinander zu unterscheiden." + }, + "closeConnectionTitle": "Verbindung zum Gerät trennen", + "closeConnectionDescription": "Die Verbindung zum Gerät wird getrennt. Um das Gerät wieder zu verwenden, müssen Sie es erneut mit der App verbinden.", + "closeConnectionButton": "Verbindung trennen", + "resetToFactorySettingsTitle": "Auf Werkseinstellungen zurücksetzen", + "resetToFactorySettingsDescription": "Alle Einstellungen werden auf die Werkseinstellungen zurückgesetzt. Dies kann nicht rückgängig gemacht werden.", + "resetToFactorySettingsButton": "Zurücksetzen", + "bottomSheetModalCloseConnection": { + "headerTitle": "Verbindung trennen", + "description": "Die Verbindung zum Gerät wird getrennt. Um das Gerät wieder zu verwenden, müssen Sie es erneut mit der App verbinden.", + "cancelButton": "Abbrechen", + "confirmButton": "Verbindung trennen" + }, + "bottomSheetModalResetToFactorySettings": { + "headerTitle": "Auf Werkseinstellungen zurücksetzen", + "description": "Alle Einstellungen werden auf die Werkseinstellungen zurückgesetzt. Dies kann nicht rückgängig gemacht werden.", + "cancelButton": "Abbrechen", + "confirmButton": "Zurücksetzen" } }, "scenes": { @@ -159,6 +180,11 @@ } } }, + "noDevicesConnected": { + "pageTitle": "Keine Geräte verbunden", + "textDescription": "Es sind keine Geräte mit der App verbunden. Verbinden Sie ein Gerät, um es zu verwenden.", + "buttonSearchForNewDevices": "Nach neuen Geräten suchen" + }, "help": { "pageTitle": "Hilfe" }, diff --git a/locales/en.json b/locales/en.json index 3ea8a61..1c98b72 100644 --- a/locales/en.json +++ b/locales/en.json @@ -7,7 +7,9 @@ "sideBar": { "devicesTitle": "Devices", "settings": "Settings", - "help": "Help" + "help": "Help", + "noDevicesConnected": "No devices connected", + "textButtonSearchForNewDevices": "Search for new devices" }, "screens": { "device": { @@ -20,11 +22,30 @@ "deviceModelText": "Device model", "deviceFirmwareVersionText": "Firmware Version", "deviceLastUpdatedText": "Last updated", + "deviceMacAddressText": "MAC address", "deviceIPAddressText": "IP address", "modalSettingsChangeDeviceDisplayName": { "pageTitle": "Customize device name", "textTitle": "Device name", "textDescription": "The name of the device is stored locally on the device and serves you to distinguish the connected devices from each other in the app." + }, + "closeConnectionTitle": "Disconnect from device", + "closeConnectionDescription": "The connection to the device will be disconnected. To use the device again, you'll need to reconnect it with the app.", + "closeConnectionButton": "Disconnect", + "resetToFactorySettingsTitle": "Reset to Factory Settings", + "resetToFactorySettingsDescription": "All settings will be reset to their factory defaults. This action cannot be undone.", + "resetToFactorySettingsButton": "Reset", + "bottomSheetModalCloseConnection": { + "headerTitle": "Disconnect Connection", + "description": "The connection to the device is being disconnected. To use the device again, you will need to reconnect it with the app.", + "cancelButton": "Cancel", + "confirmButton": "Disconnect" + }, + "bottomSheetModalResetToFactorySettings": { + "headerTitle": "Reset to Factory Settings", + "description": "All settings will be reset to their factory defaults. This cannot be undone.", + "cancelButton": "Cancel", + "confirmButton": "Reset" } }, "scenes": { @@ -159,6 +180,9 @@ } } }, + "noDevicesConnected": { + "pageTitle": "No devices connected" + }, "help": { "pageTitle": "Help" }, diff --git a/package-lock.json b/package-lock.json index 14b59a0..716f1ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "react-native": "0.71.8", "react-native-draggable-flatlist": "^4.0.1", "react-native-gesture-handler": "~2.9.0", + "react-native-linear-gradient": "^2.8.2", "react-native-pager-view": "6.1.2", "react-native-reanimated": "~2.14.4", "react-native-safe-area-context": "4.5.0", @@ -11939,6 +11940,15 @@ "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.71.19.tgz", "integrity": "sha512-1dVk9NwhoyKHCSxcrM6vY6cxmojeATsBobDicX0ZKr7DgUF2cBQRTKsimQFvzH8XhOVXyH8p4HyDSZNIFI8OlQ==" }, + "node_modules/react-native-linear-gradient": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.2.tgz", + "integrity": "sha512-hgmCsgzd58WNcDCyPtKrvxsaoETjb/jLGxis/dmU3Aqm2u4ICIduj4ECjbil7B7pm9OnuTkmpwXu08XV2mpg8g==", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-pager-view": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.1.2.tgz", diff --git a/package.json b/package.json index af01ed4..05af096 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "react-native": "0.71.8", "react-native-draggable-flatlist": "^4.0.1", "react-native-gesture-handler": "~2.9.0", + "react-native-linear-gradient": "^2.8.2", "react-native-pager-view": "6.1.2", "react-native-reanimated": "~2.14.4", "react-native-safe-area-context": "4.5.0", diff --git a/src/Components/Button/index.js b/src/Components/Button/index.js index 5f9996d..f17e0c8 100644 --- a/src/Components/Button/index.js +++ b/src/Components/Button/index.js @@ -15,9 +15,15 @@ export default function MyButton({ disabled, onPress, buttonLeftComponent, + buttonBackgroundColor, }) { const appContext = useContext(AppContext); + const bBackgroundColor = + buttonBackgroundColor === undefined + ? appContext.appTheme.colors.primary + : buttonBackgroundColor; + return ( {buttonLeftComponent} - + {title} diff --git a/src/Components/Modal/index.js b/src/Components/Modal/index.js index 41fd2bb..591080a 100644 --- a/src/Components/Modal/index.js +++ b/src/Components/Modal/index.js @@ -1,4 +1,4 @@ -import { useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import { AppContext, AppStyles, ModalContainer } from "../../utils"; import { Modal, @@ -7,13 +7,21 @@ import { Text, TouchableWithoutFeedback, View, + FlatList, } from "react-native"; import MyIcon from "../Icon"; import MyTextInput from "../TextInput"; import { MyIconButton } from "../Button"; import { Divider } from "../Divider"; import MyPressable from "../Pressable"; -import { FlatList } from "react-native-gesture-handler"; +import Animated, { + Easing, + useAnimatedStyle, + useSharedValue, + withSpring, + withTiming, +} from "react-native-reanimated"; +import { StatusBar } from "expo-status-bar"; //const modalContentStyle = { margin: 10, paddingTop: 10 }; @@ -333,34 +341,74 @@ export function MyBottomSheetModal({ }) { const appContext = useContext(AppContext); + const translateY = useSharedValue(0); + const opacity = useSharedValue(0); + + translateY.value = withTiming(isOpen ? 0 : modalHeight, { + duration: 250, + easing: Easing.ease, + }); + + opacity.value = withTiming(isOpen ? 1 : 0, { + duration: 250, + easing: Easing.ease, + }); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ translateY: translateY.value }], + opacity: opacity.value, + })); + + const handleCloseModal = () => { + translateY.value = withTiming(modalHeight, { + duration: 100, + easing: Easing.ease, + }); + opacity.value = withTiming(0, { duration: 100, easing: Easing.ease }); + setTimeout(() => closeModal(), 100); + }; + return ( closeModal()} + onRequestClose={() => handleCloseModal()} > - closeModal()}> - - - - + handleCloseModal()}> + + + + closeModal()} + onPress={() => handleCloseModal()} /> {children} - + ); diff --git a/src/Components/Result/index.js b/src/Components/Result/index.js index 496470c..1922678 100644 --- a/src/Components/Result/index.js +++ b/src/Components/Result/index.js @@ -17,7 +17,11 @@ export default function MyResult({ text, iconName }) { {text} diff --git a/src/Components/SideBar/index.js b/src/Components/SideBar/index.js index b6e78ac..10a3b9d 100644 --- a/src/Components/SideBar/index.js +++ b/src/Components/SideBar/index.js @@ -5,7 +5,8 @@ import { AppContext, AppSelectedUserDevice, AppStyles } from "../../utils"; import { Divider } from "../Divider"; import { useTranslation } from "react-i18next"; import MyIcon from "../Icon"; -import { MyIconButton } from "../Button"; +import { MyIconButton, MyTextButton } from "../Button"; +import MyResult from "../Result"; export default function Sidebar(props) { const appContext = useContext(AppContext); @@ -104,6 +105,9 @@ export default function Sidebar(props) { ); }; + const openModalSearchForNewDevices = () => + props.navigation.navigate("modalSearchForNewDevices"); + return ( <> {appContext.isUserDeveloperModeEnabled ? ( @@ -147,23 +151,38 @@ export default function Sidebar(props) { iconName="plus" iconSize={24} style={{ padding: 2 }} - onPress={() => props.navigation.navigate("modalSearchForNewDevices")} + onPress={() => openModalSearchForNewDevices()} /> - {appContext.devices.map((device, i) => ( - props.navigation.navigate(device.displayName)} - iconName={"glass-stange"} - routeName={device.displayName} - /> - ))} + {appContext.devices.length > 0 ? ( + appContext.devices.map((device) => ( + props.navigation.navigate(device.displayName)} + iconName={"glass-stange"} + routeName={device.displayName} + /> + )) + ) : ( + + + + openModalSearchForNewDevices()} + /> + + )} diff --git a/src/Screens/Device/modals/EditActions/Wait/index.js b/src/Screens/Device/modals/EditActions/Wait/index.js index c418f3d..927617c 100644 --- a/src/Screens/Device/modals/EditActions/Wait/index.js +++ b/src/Screens/Device/modals/EditActions/Wait/index.js @@ -64,6 +64,8 @@ const windowHeight = Dimensions.get("window").height; const modalHeight = windowHeight * 0.6; // 60 % of device screen +console.log("modalHe", modalHeight); + export default function WaitEditActionModalContent({ navigation, route }) { const appContext = useContext(AppContext); const { t } = useTranslation(); diff --git a/src/Screens/Device/scene.js b/src/Screens/Device/scene.js index 1e61d29..5b0c2e6 100644 --- a/src/Screens/Device/scene.js +++ b/src/Screens/Device/scene.js @@ -132,21 +132,6 @@ export default function SceneView({ navigation }) { const lastSceneAction = deviceSceneActions[deviceSceneActions.length - 1]; - console.log( - "lastSceneAction", - lastSceneAction?.type === Constants.actionType.stop - ); - - /* -) : ( - - )} - */ - return ( <> {device.selectedScene === "" ? ( diff --git a/src/Screens/Device/settings.js b/src/Screens/Device/settings.js index dbc9381..b543779 100644 --- a/src/Screens/Device/settings.js +++ b/src/Screens/Device/settings.js @@ -1,41 +1,58 @@ -import { ScrollView, Text, View } from "react-native"; +import { Button, ScrollView, Text, View } from "react-native"; import Card from "../../Components/Card"; import { useContext, useEffect, useState } from "react"; -import { AppContext, AppSelectedUserDevice, AppStyles } from "../../utils"; +import { + AppContext, + AppSelectedUserDevice, + AppStyles, + GetDevice, +} from "../../utils"; import { Divider } from "../../Components/Divider"; import { useTranslation } from "react-i18next"; import MySwitch from "../../Components/Switch"; -import { MyIconButton } from "../../Components/Button"; +import MyButton, { MyIconButton, MyTextButton } from "../../Components/Button"; +import { MyBottomSheetModal, MyConfirmModal } from "../../Components/Modal"; export default function SettingsView({ navigation }) { const appContext = useContext(AppContext); const { t } = useTranslation(); const [switchState, setSwitchState] = useState(false); const [textDeviceIdHidden, setTextDeviceIdHidden] = useState(true); + const [textDeviceMacAddressHidden, setTextDeviceMacAddressHidden] = + useState(true); + const [ + isBottomSheetModalCloseConnectionOpen, + setIsBottomSheetModalCloseConnectionOpen, + ] = useState(false); + const [ + isBottomSheetModalResetToFactorySettingsOpen, + setIsBottomSheetModalResetToFactorySettingsOpen, + ] = useState(false); - const device = appContext.devices.find( - (d) => d.id === AppSelectedUserDevice.current.id - ); + const device = GetDevice(appContext.devices); const SettingsText = ({ containerStyle, title, description, hidden }) => { return ( - - - {title} - - - {hidden ? "************" : description} - + + + + {title} + + + + {hidden ? "************" : description} + + ); }; @@ -46,9 +63,39 @@ export default function SettingsView({ navigation }) { }); }, []); + const deleteDeviceFromApp = () => { + console.log("dev", appContext.devices.length); + + appContext.setDevices((devices) => + devices.filter((d) => d.id !== device.id) + ); + + appContext.setDeviceScenes((scenes) => + scenes.filter((s) => s.deviceId !== device.id) + ); + + appContext.setDeviceSceneActions((actions) => + actions.filter((a) => a.deviceId !== device.id) + ); + + console.log("dev", appContext.devices.length); + + // show no devices connected screen if there are no devices left + if (appContext.devices.length < 2) { + navigation.navigate("_noDevicesConnected"); + } + + // TODO: remove device from app async storage if it is not already handled by appContext + }; + + const bottomSheetModalHeight = appContext.appLanguage === "en" ? 150 : 170; + return ( - + - + + + + + + + + + + + + setIsBottomSheetModalCloseConnectionOpen(true)} + /> + + + setIsBottomSheetModalCloseConnectionOpen(false)} + headerTitle={t( + "screens.device.settings.bottomSheetModalCloseConnection.headerTitle" + )} + modalHeight={bottomSheetModalHeight} + > + + + {t( + "screens.device.settings.bottomSheetModalCloseConnection.description" + )} + + + + setIsBottomSheetModalCloseConnectionOpen(false)} + /> + { + // TODO: is a request needed to the esp? or is it enough to just remove the device from the app? + console.log("close connection to device", device.id); + + deleteDeviceFromApp(); + }} + /> + + + + + + + + + + + setIsBottomSheetModalResetToFactorySettingsOpen(true) + } + /> + + + + setIsBottomSheetModalResetToFactorySettingsOpen(false) + } + headerTitle={t( + "screens.device.settings.bottomSheetModalResetToFactorySettings.headerTitle" + )} + modalHeight={bottomSheetModalHeight} + > + + + {t( + "screens.device.settings.bottomSheetModalResetToFactorySettings.description" + )} + + + + + setIsBottomSheetModalResetToFactorySettingsOpen(false) + } + /> + { + // TODO: API request to esp for reset to factory settings + console.log("reseting esp and closing connection", device.id); + + deleteDeviceFromApp(); + }} + /> + + + + ); } diff --git a/src/Screens/NoDevicesConnected/index.js b/src/Screens/NoDevicesConnected/index.js new file mode 100644 index 0000000..ee4f640 --- /dev/null +++ b/src/Screens/NoDevicesConnected/index.js @@ -0,0 +1,26 @@ +import { useTranslation } from "react-i18next"; +import { View } from "react-native"; +import MyResult from "../../Components/Result"; +import { MyTextButton } from "../../Components/Button"; + +// this screen is shown when no devices are connected to the app +export default function NoDevicesConnectedScreen({ navigation }) { + const { t } = useTranslation(); + + // TODO: add ref to faq for help to connect a new device + + return ( + + + + navigation.navigate("modalSearchForNewDevices")} + /> + + ); +} diff --git a/src/utils.js b/src/utils.js index c3eda97..f24ba14 100644 --- a/src/utils.js +++ b/src/utils.js @@ -89,6 +89,8 @@ const DarkAppTheme = { text: "#fff", textSecondary: "#ddd", textDisabled: "#b2bec3", + buttonTextColor: "#fff", + buttonSecondary: "#818081", backgroundColor: "#21252a", card: { backgroundColor: "#2b3139", @@ -128,6 +130,7 @@ const DarkAppTheme = { }, colorPickerDisabled: "rgba(0, 0, 0, 0.3)", modal: { + transparentBackgroundColor: "rgba(0, 0, 0, 0.4)", pressedPickerItemColor: "rgba(0, 0, 0, 0.3)", }, }; @@ -142,6 +145,8 @@ const LightAppTheme = { text: "#000", textSecondary: "#555", textDisabled: "#636e72", + buttonTextColor: "#fff", + buttonSecondary: "#aaa69d", backgroundColor: "#f7f7f7", card: { backgroundColor: "#fff", @@ -180,6 +185,7 @@ const LightAppTheme = { }, colorPickerDisabled: "rgba(0, 0, 0, 0.3)", modal: { + transparentBackgroundColor: "rgba(0, 0, 0, 0.3)", pressedPickerItemColor: "rgba(0, 0, 0, 0.1)", }, }; @@ -515,6 +521,7 @@ const devDevices = [ id: "1f21a12a-0bec-4336-99bb-df3f9fc9f537", // deviceId displayName: "Turtle", deviceModel: "Aurora", + deviceMacAddress: "12:34:56:78:9A:BC", deviceIp: "127.0.0.1", firmware: { version: "1.0.1", @@ -526,6 +533,7 @@ const devDevices = [ id: "5b331a12a-0bec-4336-99bb-df3f9fc9f537", // deviceId displayName: "Elona", deviceModel: "Aurora", + deviceMacAddress: "AB:CD:EF:12:34:56", deviceIp: "192.168.0.1", firmware: { version: "1.0.1",