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 (
-
+
-
+
+
+
+
+
+ setTextDeviceMacAddressHidden(!textDeviceMacAddressHidden)
+ }
+ iconName={textDeviceMacAddressHidden ? "eye-off" : "eye"}
+ iconSize={18}
+ style={{ padding: 4 }}
+ />
+
+
+
+
+
+
+
+
+
+ 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",