searching for new devices

main
alex 2023-08-12 00:11:22 +00:00
parent c8d9e1bdd3
commit 22d4a8bae7
8 changed files with 387 additions and 27 deletions

View File

@ -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

View File

@ -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"
}
}
}
}

View File

@ -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"
}
}
}
}

View File

@ -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}
<Text style={{ textAlign: "center", color: appContext.appTheme.text }}>
{title}
</Text>

View File

@ -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 (
<View
style={{
paddingTop: 10,
paddingLeft: 10,
paddingRight: 10,
paddingBottom: 10,
}}
style={[
{
paddingTop: 10,
paddingLeft: 10,
paddingRight: 10,
},
!disablePaddingBottom && { paddingBottom: 10 },
]}
>
{cardTopicText !== undefined && (
<Text

View File

@ -146,6 +146,7 @@ export default function Sidebar(props) {
<MyIconButton
iconName="plus"
iconSize={24}
style={{ padding: 2 }}
onPress={() => props.navigation.navigate("modalSearchForNewDevices")}
/>
</View>

View File

@ -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 (
<View style={{ marginTop: 40, alignItems: "center" }}>
<MyButton
style={{ width: 200 }}
title={"Gerätesuche starten"}
onPress={() => {
console.log("start device search");
let texts = { searchDescription: "", searchButton: "" };
zeroconf.scan("http", "tcp", "local.");
}}
/>
</View>
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 (
<ScrollView>
<View style={{ flex: 1, marginTop: 40, alignItems: "center" }}>
<View style={{ margin: 40 }}>
{searchState === AppSearchState.scanning && (
<ActivityIndicator
style={{ marginBottom: 20 }}
size="large"
color={appContext.appTheme.colors.secondary}
/>
)}
<Text
style={[
{ textAlign: "center", color: appContext.appTheme.text },
AppStyles.typography14,
]}
>
{texts.searchDescription}
</Text>
</View>
<MyButton
style={{ width: 200 }}
title={texts.searchButton}
onPress={() => {
if (searchState !== AppSearchState.scanning) {
startScanning();
} else if (searchState === AppSearchState.scanning) {
stopScanning();
}
}}
/>
<View style={{ width: "100%" }}>
<FlatList
ListHeaderComponent={
<>
{foundDevices.length > 0 && (
<View style={{ marginLeft: 20 }}>
<Text
style={[
AppStyles.typography16,
{
fontWeight: "bold",
marginTop: 20,
color: appContext.appTheme.text,
},
]}
>
{t("screens.modalSearchForNewDevices.textFoundDevices")}
</Text>
</View>
)}
</>
}
ListFooterComponent={
<View style={{ paddingBottom: 10 }}>
{foundDevices.length > 0 && (
<MyTextButton
style={{ marginTop: 20 }}
title={t(
"screens.modalSearchForNewDevices.textButtonBackToOverview"
)}
onPress={() => navigation.goBack()}
/>
)}
</View>
}
scrollEnabled={false}
data={foundDevices}
extraData={foundDevices}
renderItem={({ item }) => (
<FoundDevice item={item} appContext={appContext} t={t} />
)}
/>
</View>
</View>
</ScrollView>
);
}
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 (
<Card disablePaddingBottom>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<View style={{ flexDirection: "row", gap: 10 }}>
<View style={{ height: 60, width: 60, backgroundColor: "gray" }} />
<View>
<Text
style={{
fontWeight: "bold",
color: appContext.appTheme.text,
}}
>
New Device
</Text>
<Text style={{ color: appContext.appTheme.textSecondary }}>
{item.deviceModel}
</Text>
<Text style={{ color: appContext.appTheme.textSecondary }}>
{item.deviceIp}
</Text>
</View>
</View>
<View>
<MyButton
title={
connectingState === FoundDeviceState.connecting
? t(
"screens.modalSearchForNewDevices.foundDeviceConnectButton.connecting"
)
: connectingState === FoundDeviceState.connected
? t(
"screens.modalSearchForNewDevices.foundDeviceConnectButton.connected"
)
: t(
"screens.modalSearchForNewDevices.foundDeviceConnectButton.connect"
)
}
disabled={connectingState !== FoundDeviceState.notConnected}
onPress={() => {
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 ? (
<ActivityIndicator
size="small"
color={appContext.appTheme.colors.gray}
style={{ marginRight: 6 }}
/>
) : connectingState === FoundDeviceState.connected ? (
<MyIcon name="check" size={12} style={{ marginRight: 6 }} />
) : null
}
/>
</View>
</View>
</Card>
);
}

View File

@ -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 = [
{