diff --git a/README.md b/README.md index 3e8ecc1..79404ee 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,10 @@ https://aboutreact.com/switch-screen-out-of-the-navigation-drawer-in-react-nativ https://stackoverflow.com/questions/66804691/include-android-permission-in-expo-react-native +# mDNS + https://expo.canny.io/feature-requests/p/zeroconf-protocol-support + +https://docs.expo.dev/develop/development-builds/development-workflows/#build-locally-with-android-studio-and-xcode + +https://forum.arduino.cc/t/help-understanding-mdns-on-esp32/1026655/5 diff --git a/locales/de.json b/locales/de.json index b537a9b..86c2228 100644 --- a/locales/de.json +++ b/locales/de.json @@ -178,7 +178,23 @@ "expertModeDescription": "Durch das Einschalten werden zusätzliche Funktionen in der App freigeschaltet, wie beispielsweise die Möglichkeit, benutzerdefinierte Farbcodes anzugeben." }, "modalSearchForNewDevices": { - "pageTitle": "Suche nach Geräten" + "pageTitle": "Suche nach Geräten", + "searchButtonText": { + "scanning": "Suche abbrechen", + "afterScan": "Erneut suchen" + }, + "searchDescription": { + "scanning": "Es wird nach neuen Geräten gesucht. Dies kann einen Moment dauern. Bitte schalten Sie das neue Gerät in der Zwischenzeit nicht aus.", + "afterScanDevicesFound": "Neue Geräte wurden gefunden. Bitte wählen Sie ein Gerät aus, um es mit der App zu verbinden.", + "afterScanNoDevicesFound": "Keine Geräte gefunden. Bitte stellen Sie sicher, dass sich das Gerät im selben Netzwerk befindet." + }, + "textFoundDevices": "Gefundene Geräte", + "textButtonBackToOverview": "Zurück zur Übersicht", + "foundDeviceConnectButton": { + "connecting": "Verbinde", + "connected": "Verbunden", + "connect": "Verbinden" + } } } } diff --git a/locales/en.json b/locales/en.json index 12b629a..3ea8a61 100644 --- a/locales/en.json +++ b/locales/en.json @@ -178,7 +178,23 @@ "expertModeDescription": "Turning it on unlocks additional features in the app, such as the ability to specify custom color codes." }, "modalSearchForNewDevices": { - "pageTitle": "Search for devices" + "pageTitle": "Search for devices", + "searchButtonText": { + "scanning": "Cancel Search", + "afterScan": "Search Again" + }, + "searchDescription": { + "scanning": "Searching for new devices. This might take a moment. Please do not turn off the new device during this time.", + "afterScanDevicesFound": "New devices have been found. Please select a device to connect it with the app.", + "afterScanNoDevicesFound": "No devices found. Please make sure the device is on the same network." + }, + "textFoundDevices": "Found Devices", + "textButtonBackToOverview": "Back to Overview", + "foundDeviceConnectButton": { + "connecting": "Connecting", + "connected": "Connected", + "connect": "Connect" + } } } } diff --git a/src/Components/Button/index.js b/src/Components/Button/index.js index bf19190..5f9996d 100644 --- a/src/Components/Button/index.js +++ b/src/Components/Button/index.js @@ -1,9 +1,21 @@ import { useContext } from "react"; -import { Text, TouchableHighlight, TouchableOpacity, View } from "react-native"; +import { + ActivityIndicator, + Text, + TouchableHighlight, + TouchableOpacity, + View, +} from "react-native"; import { AppContext, AppStyles } from "../../utils"; import MyIcon from "../Icon"; -export default function MyButton({ title, style, disabled, onPress }) { +export default function MyButton({ + title, + style, + disabled, + onPress, + buttonLeftComponent, +}) { const appContext = useContext(AppContext); return ( @@ -21,8 +33,10 @@ export default function MyButton({ title, style, disabled, onPress }) { }, AppStyles.Shadow, disabled && { opacity: 0.6 }, + buttonLeftComponent && { flexDirection: "row", alignItems: "center" }, ]} > + {buttonLeftComponent} {title} diff --git a/src/Components/Card/index.js b/src/Components/Card/index.js index 5dc5448..a79474c 100644 --- a/src/Components/Card/index.js +++ b/src/Components/Card/index.js @@ -2,7 +2,12 @@ import { useContext } from "react"; import { Text, View } from "react-native"; import { AppContext, AppStyles } from "../../utils"; -export default function Card({ children, cardTopicText, cardBackgroundColor }) { +export default function Card({ + children, + cardTopicText, + cardBackgroundColor, + disablePaddingBottom, +}) { const appContext = useContext(AppContext); const backgroundColor = @@ -12,12 +17,14 @@ export default function Card({ children, cardTopicText, cardBackgroundColor }) { return ( {cardTopicText !== undefined && ( props.navigation.navigate("modalSearchForNewDevices")} /> diff --git a/src/Screens/SearchForNewDevices/index.js b/src/Screens/SearchForNewDevices/index.js index e000fff..aacbdf7 100644 --- a/src/Screens/SearchForNewDevices/index.js +++ b/src/Screens/SearchForNewDevices/index.js @@ -1,17 +1,102 @@ -import { useContext, useEffect } from "react"; -import { AppContext } from "../../utils"; -import { View } from "react-native"; -import MyButton from "../../Components/Button"; +import { useContext, useEffect, useState } from "react"; +import { + AddNewDevice, + AppContext, + AppStyles, + ModalContainer, +} from "../../utils"; +import { + ActivityIndicator, + FlatList, + ScrollView, + Text, + View, +} from "react-native"; +import MyButton, { MyTextButton } from "../../Components/Button"; import Zeroconf from "react-native-zeroconf"; +import Card from "../../Components/Card"; +import MyIcon from "../../Components/Icon"; +import { useTranslation } from "react-i18next"; const zeroconf = new Zeroconf(); -export default function SearchForNewDevicesModalContent() { +// TODO: stop scanning on unfocs +// TODO: manual input for ip address +// TODO: link to faq? + +const devFoundDevices = [ + { + deviceModel: "Aurora", + deviceIp: "127.0.0.1", + }, + { + deviceModel: "Aurora", + deviceIp: "127.0.0.1", + }, + { + deviceModel: "Aurora", + deviceIp: "127.0.0.1", + }, + { + deviceModel: "Aurora", + deviceIp: "127.0.0.1", + }, + { + deviceModel: "Aurora", + deviceIp: "127.0.0.1", + }, + { + deviceModel: "Aurora", + deviceIp: "127.0.0.1", + }, + { + deviceModel: "Aurora", + deviceIp: "127.0.0.1", + }, +]; + +const AppSearchState = { + init: 0, // appears when user opens the screen + scanning: 1, + afterScan: 2, +}; + +export default function SearchForNewDevicesModalContent({ navigation }) { const appContext = useContext(AppContext); + const { t } = useTranslation(); + + const [searchState, setSearchState] = useState(AppSearchState.init); + const [foundDevices, setFoundDevices] = useState([]); + + const startScanning = () => { + //zeroconf.scan(); + + setSearchState(AppSearchState.scanning); + setFoundDevices([]); + + let chance = Math.random() < 0.5; + + if (chance) { + // only for testing + setTimeout(() => { + setFoundDevices((prev) => [...prev, devFoundDevices[0]]); + }, 2000); + } + + setTimeout(() => stopScanning(), 4000); + }; + + const stopScanning = () => { + console.log("stop scanning"); + setSearchState(AppSearchState.afterScan); + // zeroconf.stop(); + }; useEffect(() => { console.log("useEffect"); + startScanning(); + /* zeroconf.on("start", () => { console.log("start"); @@ -28,19 +113,220 @@ export default function SearchForNewDevicesModalContent() { zeroconf.on("error", (err) => { console.log("err", err); }); */ + + setSearchState(AppSearchState.scanning); }, []); - return ( - - { - console.log("start device search"); + let texts = { searchDescription: "", searchButton: "" }; - zeroconf.scan("http", "tcp", "local."); - }} - /> - + switch (searchState) { + case AppSearchState.init: + break; + case AppSearchState.scanning: + texts.searchDescription = t( + "screens.modalSearchForNewDevices.searchDescription.scanning" + ); + texts.searchButton = t( + "screens.modalSearchForNewDevices.searchButtonText.scanning" + ); + break; + case AppSearchState.afterScan: + if (foundDevices.length > 0) { + texts.searchDescription = t( + "screens.modalSearchForNewDevices.searchDescription.afterScanDevicesFound" + ); + } else { + texts.searchDescription = t( + "screens.modalSearchForNewDevices.searchDescription.afterScanNoDevicesFound" + ); + } + + texts.searchButton = t( + "screens.modalSearchForNewDevices.searchButtonText.afterScan" + ); + break; + default: + console.log(`Search state ${searchState} not found`); + break; + } + + return ( + + + + {searchState === AppSearchState.scanning && ( + + )} + + + {texts.searchDescription} + + + + { + if (searchState !== AppSearchState.scanning) { + startScanning(); + } else if (searchState === AppSearchState.scanning) { + stopScanning(); + } + }} + /> + + + + {foundDevices.length > 0 && ( + + + {t("screens.modalSearchForNewDevices.textFoundDevices")} + + + )} + + } + ListFooterComponent={ + + {foundDevices.length > 0 && ( + navigation.goBack()} + /> + )} + + } + scrollEnabled={false} + data={foundDevices} + extraData={foundDevices} + renderItem={({ item }) => ( + + )} + /> + + + + ); +} + +const FoundDeviceState = { + notConnected: 0, + connecting: 1, + connected: 2, +}; + +function FoundDevice({ item, appContext, t }) { + const [connectingState, setConnectingState] = useState( + FoundDeviceState.notConnected + ); + // TODO: loading the image of the new found device + + return ( + + + + + + + + New Device + + + {item.deviceModel} + + + {item.deviceIp} + + + + + + { + console.log("connect"); + // TODO: connect with esp api + setConnectingState(FoundDeviceState.connecting); + + setTimeout(() => { + setConnectingState(FoundDeviceState.connected); + + // generate random two digit number code + let code = Math.floor(Math.random() * 100); + + // TODO: add device and fill up with information provided by the esp + appContext.setDevices((prev) => [ + ...prev, + AddNewDevice( + "New Device " + code, + item.deviceModel, + item.deviceIp + ), + ]); + }, 2000); + }} + buttonLeftComponent={ + connectingState === FoundDeviceState.connecting ? ( + + ) : connectingState === FoundDeviceState.connected ? ( + + ) : null + } + /> + + + ); } diff --git a/src/utils.js b/src/utils.js index 97a8078..c3eda97 100644 --- a/src/utils.js +++ b/src/utils.js @@ -535,6 +535,20 @@ const devDevices = [ }, ]; +export function AddNewDevice(displayName, deviceModel, deviceIp) { + return { + id: GetUuid(), + displayName: displayName, + deviceModel: deviceModel, + deviceIp: deviceIp, + firmware: { + version: "1.0.1", + lastUpdated: "11.07.2023 um 20:33 Uhr", + }, + selectedScene: "", + }; +} + // preview const devDeviceScenes = [ {