update themes and add a toggle switch

This commit is contained in:
StarAppeal
2024-12-06 03:53:33 +01:00
parent d8ce2b8386
commit 11d0a88624
16 changed files with 155 additions and 222 deletions
+2 -6
View File
@@ -9,8 +9,8 @@ import ThemedTextInput from "../src/components/themed/ThemedTextInput";
import BackButton from "../src/components/BackButton";
import {useAuth} from "@/src/context/AuthProvider";
import {useTheme} from "@/src/context/ThemeProvider";
import {useRouter} from "expo-router";
import ThemeToggleButton from "@/src/components/ThemeToggleButton";
export default function LoginScreen() {
@@ -18,8 +18,6 @@ export default function LoginScreen() {
const router = useRouter();
const [username, setUsername] = useState({value: ""});
const [password, setPassword] = useState({value: ""});
const {toggleTheme} = useTheme();
useEffect(() => {
console.log(isAuthenticated);
@@ -76,9 +74,7 @@ export default function LoginScreen() {
<ThemedButton mode="outlined" onPress={onLoginPressed}>
Log in
</ThemedButton>
<ThemedButton mode={"outlined"} onPress={toggleTheme}>
Toggle Theme
</ThemedButton>
<ThemeToggleButton />
</ThemedBackground>
);
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-moon"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>

After

Width:  |  Height:  |  Size: 281 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sun"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>

After

Width:  |  Height:  |  Size: 650 B

+33 -1
View File
@@ -9,7 +9,6 @@
"version": "1.0.0",
"dependencies": {
"@expo/ngrok": "^4.1.3",
"@expo/vector-icons": "^14.0.4",
"axios": "^1.7.8",
"expo": "^52.0.14",
"expo-auth-session": "^6.0.1",
@@ -34,6 +33,7 @@
"react-native-paper": "^5.12.5",
"react-native-reanimated": "~3.16.3",
"react-native-status-bar-height": "^2.6.0",
"react-native-switch-with-icons": "^3.0.1",
"react-native-web": "~0.19.13",
"react-native-webview": "13.12.4"
},
@@ -41,6 +41,7 @@
"@babel/core": "^7.26.0",
"@types/jest": "^29.5.14",
"@types/react": "~18.3.12",
"@types/react-native-vector-icons": "^6.4.18",
"@types/react-test-renderer": "^18.3.0",
"compression": "^1.7.5",
"eslint-config-expo": "~8.0.1",
@@ -5026,6 +5027,27 @@
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-native": {
"version": "0.70.19",
"resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz",
"integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-native-vector-icons": {
"version": "6.4.18",
"resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz",
"integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*",
"@types/react-native": "^0.70"
}
},
"node_modules/@types/react-test-renderer": {
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.3.0.tgz",
@@ -15467,6 +15489,16 @@
"integrity": "sha512-z3SGLF0mHT+OlJDq7B7h/jXPjWcdBT3V14Le5L2PjntjjWM3+EJzq2BcXDwV+v67KFNJic5pgA26cCmseYek6w==",
"license": "MIT"
},
"node_modules/react-native-switch-with-icons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/react-native-switch-with-icons/-/react-native-switch-with-icons-3.0.1.tgz",
"integrity": "sha512-OzhUWNn1RNcaER6TmKD+XeD3cL0xEa/MMHrggTm5WIhiy6q27dThlkFTdelpttAp6HVy1e5+qM1iyFwc+ZHLyA==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-vector-icons": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz",
+1 -1
View File
@@ -18,7 +18,6 @@
},
"dependencies": {
"@expo/ngrok": "^4.1.3",
"@expo/vector-icons": "^14.0.4",
"axios": "^1.7.8",
"expo": "^52.0.14",
"expo-auth-session": "^6.0.1",
@@ -43,6 +42,7 @@
"react-native-paper": "^5.12.5",
"react-native-reanimated": "~3.16.3",
"react-native-status-bar-height": "^2.6.0",
"react-native-switch-with-icons": "^3.0.1",
"react-native-web": "~0.19.13",
"react-native-webview": "13.12.4"
},
+3 -1
View File
@@ -1,16 +1,18 @@
import React from "react";
import {Image, StyleSheet, TouchableOpacity} from "react-native";
import {getStatusBarHeight} from "react-native-status-bar-height";
import {useTheme} from "@/src/context/ThemeProvider";
type Props = {
goBack: () => void;
}
export default function BackButton({goBack}: Props) {
const {theme} = useTheme();
return (
<TouchableOpacity onPress={goBack} style={styles.container}>
<Image
style={styles.image}
style={[styles.image, {tintColor: theme.colors.onBackground}]}
source={require("../../assets/items/back.png")}
/>
</TouchableOpacity>
+19
View File
@@ -0,0 +1,19 @@
import React from "react";
import {useTheme as useCustomTheme} from "@/src/context/ThemeProvider";
import SwitchWithIcons from "react-native-switch-with-icons";
import {darkTheme, lightTheme} from "@/src/core/theme";
export default function ThemeToggleButton() {
const {theme, toggleTheme} = useCustomTheme();
return (
<SwitchWithIcons
value={theme.dark}
onValueChange={toggleTheme}
icon={{true: require("../../assets/items/moon.svg"), false: require("../../assets/items/sun.svg")}}
thumbColor={{true: darkTheme.colors.surfaceVariant, false: lightTheme.colors.surfaceVariant}}
iconColor={{true: darkTheme.colors.onSurfaceVariant, false: lightTheme.colors.onSurfaceVariant}}
trackColor={{true: darkTheme.colors.secondary, false: lightTheme.colors.secondary}}
/>
)
}
+16 -21
View File
@@ -1,7 +1,6 @@
import React from "react";
import {ImageBackground, KeyboardAvoidingView, StyleSheet,} from "react-native";
import {useTheme} from "../../context/ThemeProvider";
import {ThemeType} from "@/src/core/theme";
type Props = {
children: React.ReactNode;
@@ -9,12 +8,11 @@ type Props = {
export default function ThemedBackground({children}: Props) {
const {theme} = useTheme();
const styles = createStyles(theme);
return (
<ImageBackground
source={require("../../../assets/items/dot.png")}
resizeMode="repeat"
style={styles.background}
style={[styles.background, {backgroundColor: theme.colors.surface}]}
>
<KeyboardAvoidingView style={styles.container} behavior="padding">
{children}
@@ -23,21 +21,18 @@ export default function ThemedBackground({children}: Props) {
);
}
const createStyles = (theme: ThemeType) => {
return StyleSheet.create({
background: {
flex: 1,
width: "100%",
backgroundColor: theme.colors.background,
},
container: {
flex: 1,
padding: 20,
width: "100%",
maxWidth: 340,
alignSelf: "center",
alignItems: "center",
justifyContent: "center",
},
});
}
const styles = StyleSheet.create({
background: {
flex: 1,
width: "100%",
},
container: {
flex: 1,
padding: 20,
width: "100%",
maxWidth: 340,
alignSelf: "center",
alignItems: "center",
justifyContent: "center",
},
});
+13 -17
View File
@@ -2,7 +2,6 @@ import React from "react";
import {StyleSheet} from "react-native";
import {Button as PaperButton} from "react-native-paper";
import {useTheme} from "@/src/context/ThemeProvider";
import {ThemeType} from "@/src/core/theme";
type Props = {
mode: "text" | "outlined" | "contained";
@@ -13,7 +12,6 @@ type Props = {
export default function ThemedButton({mode, style, ...props}: Props) {
const {theme} = useTheme();
const styles = createStyle(theme);
return (
<PaperButton
style={[
@@ -28,19 +26,17 @@ export default function ThemedButton({mode, style, ...props}: Props) {
);
}
const createStyle =(theme:ThemeType) => {
return StyleSheet.create({
button: {
width: "100%",
marginVertical: 10,
paddingVertical: 2,
},
text: {
fontWeight: "bold",
fontSize: 15,
lineHeight: 26,
color: theme.colors.text,
},
});
}
const styles =
StyleSheet.create({
button: {
width: "100%",
marginVertical: 10,
paddingVertical: 2,
},
text: {
fontWeight: "bold",
fontSize: 15,
lineHeight: 26,
},
});
+7 -12
View File
@@ -2,7 +2,6 @@ import React from "react";
import {StyleSheet} from "react-native";
import {Text} from "react-native-paper";
import {useTheme} from "@/src/context/ThemeProvider";
import {ThemeType} from "@/src/core/theme";
type Props = {
children: React.ReactNode;
@@ -10,17 +9,13 @@ type Props = {
export default function ThemedHeader(props: Props) {
const {theme} = useTheme();
const styles = createStyles(theme);
return <Text style={styles.header} {...props} />;
}
const createStyles = (theme: ThemeType) => {
return StyleSheet.create({
header: {
fontSize: 21,
color: theme.colors.primary,
fontWeight: "bold",
paddingVertical: 12,
},
});
}
const styles = StyleSheet.create({
header: {
fontSize: 21,
fontWeight: "bold",
paddingVertical: 12,
},
});
+8 -15
View File
@@ -1,28 +1,21 @@
import React from "react";
import {StyleSheet} from "react-native";
import {Text} from "react-native-paper";
import {ThemeType} from "@/src/core/theme";
import {useTheme} from "@/src/context/ThemeProvider";
type Props = {
children: React.ReactNode;
};
export default function ThemedParagraph(props: Props) {
const {theme} = useTheme();
const styles = createStyles(theme);
return <Text style={styles.text} {...props} />;
}
const createStyles = (theme: ThemeType) => {
return StyleSheet.create({
text: {
color: theme.colors.text,
fontSize: 15,
lineHeight: 21,
textAlign: "center",
marginBottom: 12,
},
});
}
const styles = StyleSheet.create({
text: {
fontSize: 15,
lineHeight: 21,
textAlign: "center",
marginBottom: 12,
},
});
-16
View File
@@ -1,16 +0,0 @@
import {ThemeType} from "@/src/core/theme";
import {StyleSheet, Text} from "react-native";
import {useTheme} from "@/src/context/ThemeProvider";
const createStyles = (theme: ThemeType) => {
return StyleSheet.create({
text: {
color: theme.colors.text,
},
});
}
export const ThemedText = ({children}: { children: React.ReactNode }) => {
const {theme} = useTheme();
const styles = createStyles(theme);
return <Text style={styles.text}>{children}</Text>;
}
+18 -40
View File
@@ -2,8 +2,6 @@ import React from "react";
import {StyleSheet, Text, View} from "react-native";
import {TextInput as Input} from "react-native-paper";
import {useTheme} from "@/src/context/ThemeProvider";
import {ThemeType} from "@/src/core/theme";
import {MD3Colors} from "react-native-paper/src/types";
type Props = {
errorText?: string;
@@ -14,60 +12,40 @@ type Props = {
export default function ThemedTextInput({errorText, description, error, ...props}: Props) {
const {theme} = useTheme();
const styles = createStyles(theme);
// Umwandlung deines Themes für react-native-paper
const paperTheme = {
colors: {
primary: theme.colors.primary,
text: theme.colors.text,
background: theme.colors.background,
placeholder: theme.colors.secondary, // Placeholder-Farbe
error: theme.colors.error,
secondary: theme.colors.secondary,
},
};
if (error && !errorText) {
console.log("ErrorText is missing! Please provide an errorText prop!");
}
const errorStyle = {
fontSize: 13,
color: theme.colors.error,
paddingTop: 8,
};
return (
<View style={styles.container}>
<Input
style={styles.input}
selectionColor={theme.colors.primary} // Cursorfarbe
underlineColor="transparent"
mode="outlined"
theme={paperTheme} // Hier wird das Theme angewandt
{...props}
/>
{description && !error ? (
<Text style={styles.description}>{description}</Text>
) : null}
{error && <Text style={styles.error}>{errorText}</Text> }
{error && <Text style={errorStyle}>{errorText}</Text>}
</View>
);
}
const createStyles = (theme: ThemeType) => {
return StyleSheet.create({
container: {
width: "100%",
marginVertical: 12,
},
input: {
backgroundColor: theme.colors.background,
color: theme.colors.text, // Textfarbe im Eingabefeld
},
description: {
fontSize: 13,
color: theme.colors.primary,
paddingTop: 8,
},
error: {
fontSize: 13,
color: theme.colors.error,
paddingTop: 8,
},
});
};
const styles = StyleSheet.create({
container: {
width: "100%",
marginVertical: 12,
},
description: {
fontSize: 13,
paddingTop: 8,
},
});
-16
View File
@@ -1,16 +0,0 @@
import {ThemeType} from "@/src/core/theme";
import {StyleSheet, View} from "react-native";
import {useTheme} from "@/src/context/ThemeProvider";
const createStyles = (theme: ThemeType) => {
return StyleSheet.create({
container: {
flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: theme.colors.background
},
});
}
export const ThemedView = ({children}: { children: React.ReactNode }) => {
const {theme} = useTheme();
const styles = createStyles(theme);
return <View style={styles.container}>{children}</View>;
}
+22 -15
View File
@@ -1,35 +1,42 @@
import React, {createContext, ReactNode, useContext, useEffect, useState} from "react";
import {getThemeType, themes, ThemeType} from "@/src/core/theme";
import {MD3Theme, Provider as PaperProvider} from "react-native-paper";
import {getFromStorage, saveInStorage, THEME_KEY} from "@/src/utils/secureStorage";
import {darkTheme, lightTheme} from "@/src/core/theme";
type ThemeContextType = {
theme: ThemeType;
theme: MD3Theme;
toggleTheme: () => void;
};
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
export const ThemeProvider: React.FC<{ children: ReactNode }> = ({children}) => {
const [theme, setTheme] = useState<ThemeType>(themes.light);
export const ThemeProvider = ({children}: { children: ReactNode }) => {
const [theme, setTheme] = useState<MD3Theme>();
useEffect(() => {
const loadTheme = async () => {
const storedTheme = await getFromStorage(THEME_KEY);
if (storedTheme) {
setTheme(getThemeType(storedTheme));
getFromStorage(THEME_KEY).then((theme) => {
if (theme === "dark") {
setTheme(darkTheme);
} else {
setTheme(lightTheme);
}
};
loadTheme();
});
}, []);
const toggleTheme = () => {
const newTheme = theme === themes.light ? themes.dark : themes.light;
saveInStorage(THEME_KEY, newTheme.name).then(() => setTheme(newTheme));
};
const newTheme = theme === lightTheme ? darkTheme : lightTheme;
setTheme(newTheme);
saveInStorage(THEME_KEY, newTheme === lightTheme ? "light" : "dark");
}
if (!theme) {
// maybe show a loading spinner here
return <></>;
}
return (
<ThemeContext.Provider value={{theme, toggleTheme}}>
{children}
<PaperProvider theme={theme}>{children}</PaperProvider>
</ThemeContext.Provider>
);
};
@@ -37,7 +44,7 @@ export const ThemeProvider: React.FC<{ children: ReactNode }> = ({children}) =>
export const useTheme = (): ThemeContextType => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error("useTheme must be used within a ThemeProvider");
throw new Error("useTheme muss innerhalb eines ThemeProviders verwendet werden");
}
return context;
};
+11 -61
View File
@@ -1,67 +1,17 @@
export type ThemeType = {
import { MD3LightTheme as DefaultTheme, MD3DarkTheme as DarkTheme, MD3Theme } from "react-native-paper";
export const lightTheme: MD3Theme = {
...DefaultTheme,
colors: {
background: string; // Haupt-Hintergrundfarbe
text: string; // Standard-Textfarbe
primary: string; // Primärfarbe (Buttons, Hervorhebungen)
secondary: string; // Sekundärfarbe (Akzente, Links)
error: string; // Farbe für Fehler
success: string; // Farbe für Erfolgsmeldungen
warning: string; // Farbe für Warnungen
disabled: string; // Farbe für deaktivierte Buttons/Elemente
border: string; // Farbe für Ränder
card: string; // Hintergrundfarbe für Karten
overlay: string; // Überlagerungen (z. B. modale Dialoge)
shadow: string; // Schattenfarbe (bei Karten oder Schaltflächen)
};
spacing: (factor: number) => number; // Funktion für dynamische Abstände
name: string; // Name des Themes
...DefaultTheme.colors,
// add more colors here if you want to change some
},
};
const lightTheme: ThemeType = {
export const darkTheme: MD3Theme = {
...DarkTheme,
colors: {
background: "#ffffff", // Heller Hintergrund
text: "#000000", // Schwarzer Text
primary: "#6200ee", // Lila als Hauptfarbe
secondary: "#03dac6", // Türkis für Akzente
error: "#b00020", // Rot für Fehler
success: "#4caf50", // Grün für Erfolg
warning: "#ff9800", // Orange für Warnungen
disabled: "#e0e0e0", // Grauer Ton für deaktivierte Elemente
border: "#dcdcdc", // Hellgrauer Rand
card: "#f8f9fa", // Leicht grauer Hintergrund für Karten
overlay: "rgba(0, 0, 0, 0.4)", // Transparenter schwarzer Overlay
shadow: "rgba(0, 0, 0, 0.2)", // Leichte Schattenfarbe
...DarkTheme.colors,
// add more colors here if you want to change some
},
spacing: (factor: number) => factor * 8, // Basis 8px
name: "light",
};
const darkTheme: ThemeType = {
colors: {
background: "#121212", // Dunkler Hintergrund
text: "#ffffff", // Weißer Text
primary: "#bb86fc", // Hellviolett als Hauptfarbe
secondary: "#03dac6", // Türkis für Akzente
error: "#cf6679", // Helles Rot für Fehler
success: "#66bb6a", // Hellgrün für Erfolg
warning: "#ffa726", // Helles Orange für Warnungen
disabled: "#666666", // Grauer Ton für deaktivierte Elemente
border: "#333333", // Dunkelgrauer Rand
card: "#1e1e1e", // Dunkler Hintergrund für Karten
overlay: "rgba(255, 255, 255, 0.1)", // Transparenter weißer Overlay
shadow: "rgba(0, 0, 0, 0.8)", // Dunklere Schattenfarbe
},
spacing: (factor: number) => factor * 8, // Basis 8px
name: "dark",
};
export const themes = {light: lightTheme, dark: darkTheme};
export function getThemeType(theme: string): ThemeType {
if (theme === "light") {
return lightTheme;
}
return darkTheme;
}