main
alex 2023-08-13 22:26:15 +00:00
parent a9869cd2eb
commit 8fcfd15c0e
12 changed files with 475 additions and 47 deletions

53
App.js
View File

@ -41,6 +41,10 @@ import MotorEditActionModalContent, {
import WaitXSecondsEditActionModalContent from "./src/Screens/Device/modals/EditActions/Wait";
import StopEditActionModalContent from "./src/Screens/Device/modals/EditActions/Stop";
import SearchForNewDevicesModalContent from "./src/Screens/SearchForNewDevices";
import HelpCenterModalContent from "./src/Screens/Help/modals/HelpCenter";
import GiveFeedbackModalContent from "./src/Screens/Help/modals/Feedback";
import ReportProblemModalContent from "./src/Screens/Help/modals/ReportProblem";
import * as MailComposer from "expo-mail-composer";
const Drawer = createDrawerNavigator();
const Stack = createStackNavigator();
@ -278,6 +282,39 @@ export function MyApp() {
}
/>
<Stack.Screen
name="modalHelpCenter"
component={HelpCenterModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t("screens.help.modalHelpCenter.pageTitle"),
})
}
/>
<Stack.Screen
name="modalGiveFeedback"
component={GiveFeedbackModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t("screens.help.modalGiveFeedback.pageTitle"),
})
}
/>
<Stack.Screen
name="modalReportProblem"
component={ReportProblemModalContent}
options={({ navigation }) =>
options({
navigation: navigation,
pageTitle: t("screens.help.modalReportProblem.pageTitle"),
})
}
/>
<Stack.Screen
name="modalSettingsAppColorScheme"
component={SettingsAppColorSchemeModalContent}
@ -491,3 +528,19 @@ export default function App() {
</Suspense>
);
}
export function OpenMailComposer(recipients, subject, body) {
return new Promise((resolve, reject) => {
MailComposer.composeAsync({
recipients: recipients,
subject: subject,
body: body,
})
.then((result) => {
resolve(result);
})
.catch((error) => {
reject(error);
});
});
}

View File

@ -186,7 +186,16 @@
"buttonSearchForNewDevices": "Nach neuen Geräten suchen"
},
"help": {
"pageTitle": "Hilfe"
"pageTitle": "Hilfe",
"modalHelpCenter": {
"pageTitle": "Hilfezentrum"
},
"modalGiveFeedback": {
"pageTitle": "Feedback geben"
},
"modalReportProblem": {
"pageTitle": "Problem melden"
}
},
"settings": {
"pageTitle": "Einstellungen",

View File

@ -184,7 +184,16 @@
"pageTitle": "No devices connected"
},
"help": {
"pageTitle": "Help"
"pageTitle": "Help",
"modalHelpCenter": {
"pageTitle": "Help Center"
},
"modalGiveFeedback": {
"pageTitle": "Give feedback"
},
"modalReportProblem": {
"pageTitle": "Report problem"
}
},
"settings": {
"pageTitle": "Settings",

29
package-lock.json generated
View File

@ -16,6 +16,7 @@
"expo": "~48.0.18",
"expo-haptics": "~12.2.1",
"expo-linear-gradient": "~12.1.2",
"expo-mail-composer": "~12.1.1",
"expo-status-bar": "~1.4.4",
"i18next": "^23.2.11",
"i18next-browser-languagedetector": "^7.1.0",
@ -7201,6 +7202,34 @@
"expo": "*"
}
},
"node_modules/expo-mail-composer": {
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/expo-mail-composer/-/expo-mail-composer-12.1.1.tgz",
"integrity": "sha512-DJZpxdc6huwg8M9STfzL415xHGEaEXOseK2VyPAKKllWYR6m2gE18sXND9Ak8rcynZGTwyuaHrqJLBXd3I/LcQ==",
"dependencies": {
"query-string": "^6.2.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-mail-composer/node_modules/query-string": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz",
"integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
"dependencies": {
"decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/expo-modules-autolinking": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-1.2.0.tgz",

View File

@ -32,7 +32,8 @@
"react-native-uuid": "^2.0.1",
"react-native-zeroconf": "^0.13.8",
"reanimated-color-picker": "^2.3.1",
"expo-linear-gradient": "~12.1.2"
"expo-linear-gradient": "~12.1.2",
"expo-mail-composer": "~12.1.1"
},
"devDependencies": {
"@babel/core": "^7.20.0"

View File

@ -0,0 +1,107 @@
import { useState } from "react";
import Animated, {
Easing,
interpolate,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import Card from "../Card";
import { Text, TouchableOpacity, View } from "react-native";
import MyIcon from "../Icon";
import { AppStyles } from "../../utils";
export function AccordionItem({
appContext,
title,
description,
cardTopicText,
disablePaddingBottom,
}) {
const shareValue = useSharedValue(0);
const [bodySectionHeight, setBodySectionHeight] = useState(0);
const bodyHeight = useAnimatedStyle(() => ({
height: interpolate(shareValue.value, [0, 1], [0, bodySectionHeight]),
}));
const iconStyle = useAnimatedStyle(() => {
return {
transform: [
{
rotate: `${interpolate(shareValue.value, [0, 1], [0, 180])}deg`,
},
],
};
});
const toggleExpanded = () => {
if (shareValue.value === 0) {
shareValue.value = withTiming(1, {
duration: 500,
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
});
} else {
shareValue.value = withTiming(0, {
duration: 500,
easing: Easing.bezier(0.4, 0.0, 0.2, 1),
});
}
};
return (
<Card
cardTopicText={cardTopicText}
disablePaddingBottom={disablePaddingBottom}
>
<TouchableOpacity activeOpacity={0.7} onPress={toggleExpanded}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Text
style={[
{
color: appContext.appTheme.text,
fontWeight: "bold",
width: "90%",
},
AppStyles.typography16,
]}
>
{title}
</Text>
<Animated.View style={iconStyle}>
<MyIcon name="chevron-down" size={24} />
</Animated.View>
</View>
</TouchableOpacity>
<Animated.View style={[{ overflow: "hidden", marginTop: 6 }, bodyHeight]}>
<View
style={{
position: "absolute",
bottom: 0,
left: 0,
}}
onLayout={(event) =>
setBodySectionHeight(event.nativeEvent.layout.height)
}
>
<Text
style={[
{ color: appContext.appTheme.textSecondary },
AppStyles.typography14,
]}
>
{description}
</Text>
</View>
</Animated.View>
</Card>
);
}

View File

@ -240,9 +240,30 @@ export function MyPickerModalListItem({
itemSelected,
disabled,
pressableBackgroundColor,
leftIconName,
rightIconName,
}) {
const appContext = useContext(AppContext);
const rIconName =
rightIconName === undefined
? "checkbox-blank-circle-outline"
: rightIconName;
const TextComponent = () => {
return (
<Text
style={[
AppStyles.typography16,
{ color: appContext.appTheme.text },
disabled && AppStyles.disabled,
]}
>
{itemName}
</Text>
);
};
return (
<MyPressable
backgroundColor={pressableBackgroundColor}
@ -256,15 +277,24 @@ export function MyPickerModalListItem({
padding: 12,
}}
>
<Text
style={[
AppStyles.typography16,
{ color: appContext.appTheme.text },
disabled && AppStyles.disabled,
]}
{leftIconName !== undefined ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
}}
>
{itemName}
</Text>
<MyIcon
name={leftIconName}
size={24}
color={appContext.appTheme.icon}
/>
<TextComponent />
</View>
) : (
<TextComponent />
)}
{itemSelected ? (
<MyIcon
@ -274,7 +304,7 @@ export function MyPickerModalListItem({
/>
) : (
<MyIcon
name="checkbox-blank-circle-outline"
name={rIconName}
size={24}
color={appContext.appTheme.colors.gray}
style={disabled && AppStyles.disabled}

View File

@ -13,7 +13,7 @@ import MyDropdown from "../../Components/Dropdown";
import MyIcon from "../../Components/Icon";
import { useTranslation } from "react-i18next";
import MyResult from "../../Components/Result";
import { MyIconButton, MyTextButton } from "../../Components/Button";
import { MyTextButton } from "../../Components/Button";
import { useFocusEffect } from "@react-navigation/native";
import { Divider } from "../../Components/Divider";
import { MyColorSwatch } from "../../Components/ColorPicker";
@ -21,7 +21,6 @@ import { MyDotsModal } from "../../Components/Modal";
import DraggableFlatList, {
ScaleDecorator,
} from "react-native-draggable-flatlist";
import { LinearGradient } from "expo-linear-gradient";
export default function SceneView({ navigation }) {
const appContext = useContext(AppContext);

View File

@ -1,46 +1,74 @@
import { useContext } from "react";
import { AppContext } from "../../utils";
import { AppContext, AppStyles } from "../../utils";
import { Text, View } from "react-native";
import { LinearGradient } from "expo-linear-gradient";
import MyButton from "../../Components/Button";
import MyIcon from "../../Components/Icon";
import { MyPickerModalListItem } from "../../Components/Modal";
export default function HelpScreen() {
export default function HelpScreen({ navigation }) {
const appContext = useContext(AppContext);
return (
<View
style={{
backgroundColor: appContext.appTheme.backgroundColor,
height: "100%",
}}
>
<Text style={{ color: appContext.appTheme.text }}>FAQ</Text>
<Text style={{ color: appContext.appTheme.text }}>Give Feedback</Text>
<View>
<View style={{ alignItems: "center" }}>
<MyIcon name="face-agent" size={72} />
<LinearGradient
colors={["#4c669f", "#3b5998", "#192f6a"]}
style={{
width: 150,
height: 150,
paddingLeft: 15,
paddingRight: 15,
borderRadius: 5,
}}
>
<Text
style={{
fontSize: 18,
textAlign: "center",
margin: 10,
color: "#ffffff",
backgroundColor: "transparent",
}}
style={[
{
marginTop: 20,
marginBottom: 40,
color: appContext.appTheme.colors.primary,
},
AppStyles.typography20,
]}
>
Sign in with Facebook
Wie können wir Ihnen helfen?
</Text>
</LinearGradient>
</View>
<MyButton title={"test"} onPress={() => console.log("t")} />
<View style={{ width: "100%" }}>
<MyPickerModalListItem
leftIconName="help-circle"
itemName="Help Center"
rightIconName="chevron-right"
onPress={() => navigation.navigate("modalHelpCenter")}
/>
<MyPickerModalListItem
leftIconName="message-reply-text"
itemName="Feedback geben"
rightIconName="chevron-right"
onPress={() => navigation.navigate("modalGiveFeedback")}
/>
<MyPickerModalListItem
leftIconName="alert"
itemName="Problem melden"
rightIconName="chevron-right"
onPress={() => navigation.navigate("modalReportProblem")}
/>
</View>
</View>
);
}
/*
<ScrollView>
<View
style={{
backgroundColor: appContext.appTheme.backgroundColor,
flex: 1,
}}
>
{Array.from({ length: 20 }).map((item, index) => (
<AccordionItem
key={index}
title={
"Was ist künstliche Intelligenz (KI) und wie wird sie eingesetzt?"
}
description={
"Künstliche Intelligenz (KI) ermöglicht Maschinen, menschenähnliche Intelligenz für Aufgaben wie Spracherkennung, Bildverarbeitung, Mustererkennung, automatisierte Entscheidungsfindung und mehr im Gesundheitswesen (zur Diagnose von Krankheiten), Finanzen (automatisiertes Handeln), autonome Fahrzeuge, Kundenservice (Chatbots) und anderen Bereichen einzusetzen, mit dem Potenzial, viele Aspekte des täglichen Lebens zu verändern und zu verbessern."
}
/>
))}
</View>
</ScrollView>
*/

View File

@ -0,0 +1,3 @@
export default function GiveFeedbackModalContent({ navigation, route }) {
return <></>;
}

View File

@ -0,0 +1,155 @@
import { FlatList, Text, View } from "react-native";
import { AccordionItem } from "../../../../Components/Accordion";
import { AppContext, AppStyles, ModalContainer } from "../../../../utils";
import { useContext } from "react";
import MyIcon from "../../../../Components/Icon";
import { MyTextButton } from "../../../../Components/Button";
import { OpenMailComposer } from "../../../../../App";
const helpData = [
{
topicText: "Allgemeine Fragen",
question:
"Was ist die grundlegende Funktionalität der Handy-App zur Gerätesteuerung?",
answer:
"Die Handy-App ermöglicht es Ihnen, kompatible Geräte über Ihr Smartphone oder Tablet zu steuern. Sie können verschiedene Funktionen aktivieren, Einstellungen ändern und Aktionen aus der Ferne ausführen.",
},
{
question: "Welche Arten von Geräten können über die App gesteuert werden?",
answer:
"Unsere App unterstützt eine Vielzahl von Geräten, darunter Smart-Home-Geräte, Unterhaltungselektronik, Haushaltsgeräte und mehr.",
},
{
question: "Ist die App für Android- und iOS-Geräte verfügbar?",
answer:
"Ja, die App ist sowohl für Android- als auch für iOS-Geräte verfügbar. Sie können sie im Google Play Store bzw. im Apple App Store herunterladen.",
},
{
question: "Wo kann ich die App herunterladen?",
answer:
"Sie können die App direkt aus dem Google Play Store (für Android) oder dem Apple App Store (für iOS) herunterladen und installieren.",
},
{
question:
"Benötige ich eine Internetverbindung, um das Gerät über die App zu steuern?",
answer:
"Ja, eine aktive Internetverbindung (Wi-Fi oder mobile Daten) ist erforderlich, um die App mit dem Gerät zu verbinden und die Steuerungsfunktionen zu nutzen.",
},
{
question: "Unterstützt die App mehrere Geräte gleichzeitig?",
answer:
"Ja, die App ermöglicht es Ihnen, mehrere kompatible Geräte gleichzeitig zu steuern und zwischen ihnen zu wechseln.",
},
{
topicText: "Einrichtung und Verbindung",
question:
"Wie richte ich die Verbindung zwischen der App und meinem Gerät ein?",
answer:
"Die genaue Einrichtung kann je nach Gerät variieren, aber normalerweise müssen Sie die App öffnen, das Gerät auswählen und den Anweisungen auf dem Bildschirm folgen, um die Verbindung herzustellen.",
},
{
question:
"Welche Schritte sind erforderlich, um das Gerät zum ersten Mal mit der App zu verbinden?",
answer:
"Starten Sie die App, erstellen Sie ein Konto (falls erforderlich), fügen Sie das Gerät hinzu, indem Sie es aus der Liste der verfügbaren Geräte auswählen, und folgen Sie den Anweisungen zur Verbindungsherstellung.",
},
{
question:
"Benötige ich spezielle Einstellungen am Gerät, um die Verbindung herzustellen?",
answer:
"Ja, in den meisten Fällen müssen Sie sicherstellen, dass das Gerät eingeschaltet und im Verbindungsmodus ist, bevor Sie versuchen, es über die App zu verbinden.",
},
{
question:
"Welche Arten von Verbindungsmethoden werden von der App unterstützt (z. B. Bluetooth, WLAN, etc.)?",
answer:
"Unsere App unterstützt in der Regel WLAN-Verbindungen, um eine stabile und zuverlässige Steuerung über größere Entfernungen zu ermöglichen.",
},
{
question:
"Was sollte ich tun, wenn die Verbindung zwischen der App und dem Gerät verloren geht?",
answer:
"Überprüfen Sie zuerst Ihre Internetverbindung. Wenn das Problem weiterhin besteht, können Sie versuchen, das Gerät aus der App zu entfernen und erneut hinzuzufügen. Gegebenenfalls kann ein Neustart des Geräts helfen.",
},
];
export default function HelpCenterModalContent({ navigation, route }) {
const appContext = useContext(AppContext);
return (
<ModalContainer withoutPadding>
<FlatList
data={helpData}
ListHeaderComponent={
<View style={{ alignItems: "center" }}>
<MyIcon name="face-agent" size={72} />
<Text
style={[
{
textAlign: "center",
marginTop: 20,
marginBottom: 40,
color: appContext.appTheme.colors.primary,
},
AppStyles.typography20,
]}
>
Hier findest du Antworten.
</Text>
</View>
}
ListFooterComponent={
<View
style={[
{
alignItems: "center",
marginTop: 20,
marginLeft: 20,
marginRight: 20,
},
AppStyles.appBottom,
]}
>
<Text
style={[
{
color: appContext.appTheme.text,
textAlign: "center",
},
AppStyles.typography14,
]}
>
Konnte deine Frage nicht beantwortet werden oder fehlt dir etwas?
</Text>
<MyTextButton
title={"Kontaktiere uns"}
actionColor
onPress={() => {
console.log("contact us");
OpenMailComposer(["support@roese.dev"], "FAQ Help", "")
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
}}
/>
</View>
}
renderItem={({ item, index }) => (
<AccordionItem
key={index}
disablePaddingBottom={helpData[index + 1]?.topicText === undefined}
appContext={appContext}
cardTopicText={item.topicText}
title={item.question}
description={item.answer}
/>
)}
/>
</ModalContainer>
);
}

View File

@ -0,0 +1,5 @@
// TODO: send app diagnostics to server within the report problem
export default function ReportProblemModalContent({ navigation, route }) {
return <></>;
}