searching for new devices
parent
c8d9e1bdd3
commit
22d4a8bae7
|
@ -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
|
https://stackoverflow.com/questions/66804691/include-android-permission-in-expo-react-native
|
||||||
|
|
||||||
|
# mDNS
|
||||||
|
|
||||||
https://expo.canny.io/feature-requests/p/zeroconf-protocol-support
|
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
|
||||||
|
|
|
@ -178,7 +178,23 @@
|
||||||
"expertModeDescription": "Durch das Einschalten werden zusätzliche Funktionen in der App freigeschaltet, wie beispielsweise die Möglichkeit, benutzerdefinierte Farbcodes anzugeben."
|
"expertModeDescription": "Durch das Einschalten werden zusätzliche Funktionen in der App freigeschaltet, wie beispielsweise die Möglichkeit, benutzerdefinierte Farbcodes anzugeben."
|
||||||
},
|
},
|
||||||
"modalSearchForNewDevices": {
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,7 +178,23 @@
|
||||||
"expertModeDescription": "Turning it on unlocks additional features in the app, such as the ability to specify custom color codes."
|
"expertModeDescription": "Turning it on unlocks additional features in the app, such as the ability to specify custom color codes."
|
||||||
},
|
},
|
||||||
"modalSearchForNewDevices": {
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
import { useContext } from "react";
|
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 { AppContext, AppStyles } from "../../utils";
|
||||||
import MyIcon from "../Icon";
|
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);
|
const appContext = useContext(AppContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -21,8 +33,10 @@ export default function MyButton({ title, style, disabled, onPress }) {
|
||||||
},
|
},
|
||||||
AppStyles.Shadow,
|
AppStyles.Shadow,
|
||||||
disabled && { opacity: 0.6 },
|
disabled && { opacity: 0.6 },
|
||||||
|
buttonLeftComponent && { flexDirection: "row", alignItems: "center" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
{buttonLeftComponent}
|
||||||
<Text style={{ textAlign: "center", color: appContext.appTheme.text }}>
|
<Text style={{ textAlign: "center", color: appContext.appTheme.text }}>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -2,7 +2,12 @@ import { useContext } from "react";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import { AppContext, AppStyles } from "../../utils";
|
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 appContext = useContext(AppContext);
|
||||||
|
|
||||||
const backgroundColor =
|
const backgroundColor =
|
||||||
|
@ -12,12 +17,14 @@ export default function Card({ children, cardTopicText, cardBackgroundColor }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={[
|
||||||
paddingTop: 10,
|
{
|
||||||
paddingLeft: 10,
|
paddingTop: 10,
|
||||||
paddingRight: 10,
|
paddingLeft: 10,
|
||||||
paddingBottom: 10,
|
paddingRight: 10,
|
||||||
}}
|
},
|
||||||
|
!disablePaddingBottom && { paddingBottom: 10 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
{cardTopicText !== undefined && (
|
{cardTopicText !== undefined && (
|
||||||
<Text
|
<Text
|
||||||
|
|
|
@ -146,6 +146,7 @@ export default function Sidebar(props) {
|
||||||
<MyIconButton
|
<MyIconButton
|
||||||
iconName="plus"
|
iconName="plus"
|
||||||
iconSize={24}
|
iconSize={24}
|
||||||
|
style={{ padding: 2 }}
|
||||||
onPress={() => props.navigation.navigate("modalSearchForNewDevices")}
|
onPress={() => props.navigation.navigate("modalSearchForNewDevices")}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,17 +1,102 @@
|
||||||
import { useContext, useEffect } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { AppContext } from "../../utils";
|
import {
|
||||||
import { View } from "react-native";
|
AddNewDevice,
|
||||||
import MyButton from "../../Components/Button";
|
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 Zeroconf from "react-native-zeroconf";
|
||||||
|
import Card from "../../Components/Card";
|
||||||
|
import MyIcon from "../../Components/Icon";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const zeroconf = new Zeroconf();
|
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 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(() => {
|
useEffect(() => {
|
||||||
console.log("useEffect");
|
console.log("useEffect");
|
||||||
|
|
||||||
|
startScanning();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
zeroconf.on("start", () => {
|
zeroconf.on("start", () => {
|
||||||
console.log("start");
|
console.log("start");
|
||||||
|
@ -28,19 +113,220 @@ export default function SearchForNewDevicesModalContent() {
|
||||||
zeroconf.on("error", (err) => {
|
zeroconf.on("error", (err) => {
|
||||||
console.log("err", err);
|
console.log("err", err);
|
||||||
}); */
|
}); */
|
||||||
|
|
||||||
|
setSearchState(AppSearchState.scanning);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
let texts = { searchDescription: "", searchButton: "" };
|
||||||
<View style={{ marginTop: 40, alignItems: "center" }}>
|
|
||||||
<MyButton
|
|
||||||
style={{ width: 200 }}
|
|
||||||
title={"Gerätesuche starten"}
|
|
||||||
onPress={() => {
|
|
||||||
console.log("start device search");
|
|
||||||
|
|
||||||
zeroconf.scan("http", "tcp", "local.");
|
switch (searchState) {
|
||||||
}}
|
case AppSearchState.init:
|
||||||
/>
|
break;
|
||||||
</View>
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
14
src/utils.js
14
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
|
// preview
|
||||||
const devDeviceScenes = [
|
const devDeviceScenes = [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue