small refactoring
This commit is contained in:
+63
-28
@@ -1,44 +1,79 @@
|
||||
import ThemedBackground from "@/src/components/themed/ThemedBackground";
|
||||
import {useState} from "react";
|
||||
import { useState } from "react";
|
||||
import ThemedTextInput from "@/src/components/themed/ThemedTextInput";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import {useAuth} from "@/src/context/AuthProvider";
|
||||
import { useAuth } from "@/src/context/AuthProvider";
|
||||
import ColorSelector from "@/src/components/themed/ColorSelector";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import ThemedSegmentedButtons from "@/src/components/themed/ThemedSegmentedButtons";
|
||||
import { MatrixState } from '@/src/model/User';
|
||||
|
||||
import CustomColorPicker from "@/src/components/themed/CustomColorPicker";
|
||||
import {View} from "react-native";
|
||||
type TextProps = MatrixState['text'];
|
||||
|
||||
export default function TextScreen() {
|
||||
const {authenticatedUser} = useAuth();
|
||||
const [textProps, setTextProps] = useState(
|
||||
authenticatedUser?.lastState?.text! || { text: '', color: [255, 255, 255] }
|
||||
const { authenticatedUser } = useAuth();
|
||||
|
||||
const [textProps, setTextProps] = useState<TextProps>(
|
||||
authenticatedUser?.lastState?.text || {
|
||||
text: '',
|
||||
color: [255, 255, 255],
|
||||
align: 'center',
|
||||
speed: 50,
|
||||
size: 16,
|
||||
}
|
||||
);
|
||||
|
||||
const updateTextProp = (prop: Partial<TextProps>) => {
|
||||
setTextProps(prev => ({ ...prev, ...prop }));
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemedBackground>
|
||||
<View style={{ padding: 20, gap: 10 }}>
|
||||
<ThemedTextInput
|
||||
label="Text"
|
||||
returnKeyType="next"
|
||||
value={textProps?.text}
|
||||
onChangeText={(value: string) => {
|
||||
setTextProps(prev => ({
|
||||
...prev,
|
||||
text: value
|
||||
}));
|
||||
}}
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
<View style={styles.contentWrapper}>
|
||||
<View style={styles.inputGroup}>
|
||||
<ThemedTextInput
|
||||
label="Text"
|
||||
value={textProps.text}
|
||||
onChangeText={(text) => updateTextProp({ text })}
|
||||
/>
|
||||
|
||||
<CustomColorPicker
|
||||
onSelect={rgb => setTextProps(prev => ({ ...prev!, color: rgb }))}
|
||||
defaultColor={textProps.color}
|
||||
/>
|
||||
</View>
|
||||
<ColorSelector
|
||||
onSelect={(color) => updateTextProp({ color })}
|
||||
defaultColor={textProps.color}
|
||||
/>
|
||||
|
||||
<View style={{ paddingHorizontal: 20, marginTop: 20 }}>
|
||||
<ThemedButton mode="contained" onPress={() => console.log(textProps)} title={"Cooler Knopf"} />
|
||||
<ThemedSegmentedButtons
|
||||
value={textProps.align}
|
||||
onValueChange={(align) => updateTextProp({ align })}
|
||||
options={{
|
||||
left: 'Links',
|
||||
center: 'Mitte',
|
||||
right: 'Rechts',
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.actionGroup}>
|
||||
<ThemedButton
|
||||
mode="contained"
|
||||
onPress={() => console.log(textProps)}
|
||||
title={"An die Matrix senden"}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</ThemedBackground>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentWrapper: {
|
||||
flex: 1,
|
||||
justifyContent: 'space-between',
|
||||
paddingVertical: 20,
|
||||
},
|
||||
inputGroup: {
|
||||
gap: 15,
|
||||
},
|
||||
actionGroup: {
|
||||
}
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import ThemedHeader from "@/src/components/themed/ThemedHeader";
|
||||
import React from "react";
|
||||
import ThemedBackground from "@/src/components/themed/ThemedBackground";
|
||||
import ChangePasswordModal from "@/src/components/ChangePasswordModal";
|
||||
import ChangePasswordFeature from "@/src/components/ChangePasswordFeature";
|
||||
import ThemeToggleButton from "@/src/components/ThemeToggleButton";
|
||||
import SpotifyAuthButton from "@/src/components/SpotifyAuthButton";
|
||||
import {RestService, Token} from "@/src/services/RestService";
|
||||
@@ -36,7 +36,7 @@ export default function SettingsScreen() {
|
||||
<ThemedBackground>
|
||||
<View style={styles.container}>
|
||||
<ThemedHeader>Einen wunderschönen guten Tag, {authenticatedUser?.name}</ThemedHeader>
|
||||
<ChangePasswordModal/>
|
||||
<ChangePasswordFeature/>
|
||||
<ThemeToggleButton/>
|
||||
<SpotifyAuthButton
|
||||
onAuthSuccess={handleAuthSuccess}
|
||||
|
||||
+9
-1
@@ -12,12 +12,14 @@ import {useAuth} from "@/src/context/AuthProvider";
|
||||
import {useRouter} from "expo-router";
|
||||
import ThemeToggleButton from "@/src/components/ThemeToggleButton";
|
||||
import PasswordInput from "@/src/components/PasswordInput";
|
||||
import ThemedCheckbox from "@/src/components/themed/ThemedCheckbox";
|
||||
|
||||
export default function LoginScreen() {
|
||||
const {isAuthenticated, login, logout, error} = useAuth();
|
||||
const router = useRouter();
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [stayLoggedIn, setStayLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(isAuthenticated);
|
||||
@@ -30,7 +32,7 @@ export default function LoginScreen() {
|
||||
|
||||
const onLoginPressed = async () => {
|
||||
console.log("Login wird ausgeführt...")
|
||||
await login(username, password);
|
||||
await login(username, password, stayLoggedIn);
|
||||
};
|
||||
|
||||
if (isAuthenticated) {
|
||||
@@ -68,6 +70,12 @@ export default function LoginScreen() {
|
||||
errorText={error?.message}
|
||||
autoComplete="password"
|
||||
/>
|
||||
<ThemedCheckbox
|
||||
label="Eingeloggt bleiben"
|
||||
value={stayLoggedIn}
|
||||
onValueChange={setStayLoggedIn}
|
||||
/>
|
||||
|
||||
<ThemedButton mode="outlined" onPress={onLoginPressed} title={"Login"} />
|
||||
<ThemeToggleButton />
|
||||
</ThemedBackground>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import React, { useState } from "react";
|
||||
import CustomModal from "@/src/components/themed/CustomModal";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import ChangePasswordForm from "./ChangePasswordForm"; // Unser neues Formular
|
||||
|
||||
export default function ChangePasswordFeature() {
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemedButton
|
||||
mode="outlined"
|
||||
icon="key-variant"
|
||||
onPress={() => setIsModalVisible(true)}
|
||||
title="Passwort ändern"
|
||||
/>
|
||||
|
||||
<CustomModal
|
||||
isVisible={isModalVisible}
|
||||
onClose={closeModal}
|
||||
>
|
||||
<ChangePasswordForm
|
||||
onSuccess={closeModal}
|
||||
onCancel={closeModal}
|
||||
/>
|
||||
</CustomModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import React, {useRef, useState} from "react";
|
||||
import { useTheme } from "@/src/context/ThemeProvider";
|
||||
import {StyleSheet, TextInput, View} from "react-native";
|
||||
import { ApiResponse, RestService } from "@/src/services/RestService";
|
||||
import { useAuth } from "@/src/context/AuthProvider";
|
||||
import PasswordInput from "@/src/components/PasswordInput";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import { Text } from "react-native-paper";
|
||||
|
||||
|
||||
interface ChangePasswordFormProps {
|
||||
onSuccess: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export default function ChangePasswordForm({ onSuccess, onCancel }: ChangePasswordFormProps) {
|
||||
const { theme } = useTheme();
|
||||
const { token: jwtToken } = useAuth();
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [apiResponse, setApiResponse] = useState<ApiResponse<{ message: string }> | null>(null);
|
||||
|
||||
const confirmPasswordRef = useRef<TextInput>(null);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!password || !confirmPassword) {
|
||||
setApiResponse({ ok: false, data: { message: "Bitte füllen Sie alle Felder aus!" } });
|
||||
return;
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
setApiResponse({ ok: false, data: { message: "Passwörter stimmen nicht überein!" } });
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await new RestService(jwtToken).changeSelfPassword(password, confirmPassword);
|
||||
setApiResponse(response);
|
||||
|
||||
if (response.ok) {
|
||||
setTimeout(() => {
|
||||
onSuccess();
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.modalContent, { backgroundColor: theme.colors.surface }]}>
|
||||
<Text variant="titleMedium" style={{ color: theme.colors.onSurface, fontSize: 18, marginBottom: 10 }}>Passwort ändern</Text>
|
||||
|
||||
{apiResponse && apiResponse.data?.message && (
|
||||
<View style={[styles.apiResponseBox, { backgroundColor: apiResponse.ok ? theme.colors.success : theme.colors.error }]}>
|
||||
<Text variant="bodyMedium"style={{ color: apiResponse.ok ? theme.colors.onSuccess : theme.colors.onError }}>
|
||||
{apiResponse.data.message}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!apiResponse?.ok && (
|
||||
<>
|
||||
<PasswordInput
|
||||
label="Neues Passwort"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
returnKeyType="next"
|
||||
onSubmitEditing={() => confirmPasswordRef.current?.focus()}
|
||||
submitBehavior="submit"
|
||||
/>
|
||||
<PasswordInput
|
||||
ref={confirmPasswordRef}
|
||||
label="Passwort bestätigen"
|
||||
value={confirmPassword}
|
||||
onChangeText={setConfirmPassword}
|
||||
returnKeyType="go"
|
||||
onSubmitEditing={handleConfirm}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<View style={styles.buttonGroup}>
|
||||
{apiResponse?.ok ? (
|
||||
<ThemedButton mode="contained" onPress={onCancel} title={"Schließen"} style={{flex: 1}} />
|
||||
) : (
|
||||
<>
|
||||
<ThemedButton mode="elevated" onPress={onCancel} title={"Abbrechen"} />
|
||||
<ThemedButton mode="contained" onPress={handleConfirm} title={"Bestätigen"} />
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContent: {
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
alignSelf: 'center',
|
||||
width: '100%',
|
||||
maxWidth: 400,
|
||||
},
|
||||
apiResponseBox: {
|
||||
marginBottom: 8,
|
||||
marginTop: 8,
|
||||
padding: 12,
|
||||
borderRadius: 8,
|
||||
},
|
||||
buttonGroup: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
gap: 10,
|
||||
marginTop: 16,
|
||||
},
|
||||
});
|
||||
@@ -1,106 +0,0 @@
|
||||
import React, {useRef, useState} from "react";
|
||||
import { Paragraph} from "react-native-paper";
|
||||
import {useTheme} from "@/src/context/ThemeProvider";
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import {ApiResponse, RestService} from "@/src/services/RestService";
|
||||
import {useAuth} from "@/src/context/AuthProvider";
|
||||
import CustomModal, {CustomModalHandles} from "@/src/components/themed/CustomModal";
|
||||
import PasswordInput from "@/src/components/PasswordInput";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
|
||||
|
||||
export default function ChangePasswordModal() {
|
||||
const {theme} = useTheme();
|
||||
const {token: jwtToken} = useAuth();
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirmPassword, setConfirmPassword] = useState("");
|
||||
const [apiResponse, setApiResponse] = useState<ApiResponse<{ message: string }> | null>(null);
|
||||
const modalRef = useRef<CustomModalHandles>(null);
|
||||
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!password || !confirmPassword) {
|
||||
setApiResponse({ok: false, data: {message: "Bitte füllen Sie alle Felder aus!"}});
|
||||
return;
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
setApiResponse({ok: false, data: { message: "Passwörter stimmen nicht überein!"}});
|
||||
return;
|
||||
}
|
||||
new RestService(jwtToken).changeSelfPassword(password, confirmPassword).then(
|
||||
(response) => {
|
||||
console.log(response);
|
||||
setApiResponse(response);
|
||||
if (response.ok) {
|
||||
// add something here
|
||||
setPassword("");
|
||||
setConfirmPassword("");
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
};
|
||||
const resetModal = () => {
|
||||
setApiResponse(null);
|
||||
setPassword("");
|
||||
setConfirmPassword("");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomModal resetCallback={resetModal} buttonTitle="Passwort ändern" ref={modalRef}>
|
||||
<View style={styles.modalContent}>
|
||||
<Paragraph>Passwort ändern</Paragraph>
|
||||
{apiResponse && apiResponse.data?.message && (
|
||||
<View
|
||||
style={[styles.apiResponseBox, {backgroundColor: apiResponse.ok ? theme.colors.success : theme.colors.error}]}>
|
||||
<Paragraph
|
||||
style={{color: apiResponse.ok ? theme.colors.onSuccess : theme.colors.onError}}>{apiResponse.data.message}</Paragraph>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<PasswordInput
|
||||
label="Passwort"
|
||||
returnKeyType="next"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
autoComplete={"new-password"}
|
||||
/>
|
||||
<PasswordInput
|
||||
label="Passwort bestätigen"
|
||||
returnKeyType="go"
|
||||
value={confirmPassword}
|
||||
onChangeText={setConfirmPassword}
|
||||
autoComplete={"new-password"}
|
||||
/>
|
||||
|
||||
<View style={styles.buttonGroup}>
|
||||
<ThemedButton mode="contained" onPress={handleConfirm} title={"Bestätigen"} />
|
||||
<ThemedButton mode="elevated" onPress={() => {
|
||||
modalRef.current?.close();
|
||||
resetModal();
|
||||
}} title={"Schließen"} />
|
||||
</View>
|
||||
</View>
|
||||
</CustomModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContent: {
|
||||
padding: 16,
|
||||
borderRadius: 8,
|
||||
},
|
||||
apiResponseBox: {
|
||||
marginBottom: 8,
|
||||
marginTop: 8,
|
||||
padding: 8,
|
||||
},
|
||||
buttonGroup: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, StyleSheet, Dimensions } from 'react-native';
|
||||
import ColorPicker, { Panel1, Swatches, Preview, HueSlider, ColorFormatsObject } from 'reanimated-color-picker';
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import { useTheme } from "@/src/context/ThemeProvider";
|
||||
import ThemedColorPickerButton from "@/src/components/themed/ThemedColorPickerButton";
|
||||
import CustomModal from './CustomModal'; // Importiere den NEUEN CustomModal
|
||||
|
||||
|
||||
interface ColorSelectorProps {
|
||||
defaultColor?: [number, number, number];
|
||||
onSelect: (rgb: [number, number, number]) => void;
|
||||
}
|
||||
|
||||
export default function ColorSelector({ defaultColor = [255, 255, 255], onSelect }: ColorSelectorProps) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
|
||||
const [pickerHex, setPickerHex] = useState(() => rgbToHex(defaultColor));
|
||||
|
||||
const openModal = () => {
|
||||
setPickerHex(rgbToHex(defaultColor));
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
const rgb = hexToRgbArray(pickerHex);
|
||||
onSelect(rgb);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const { width } = Dimensions.get('window');
|
||||
const modalWidth = Math.min(350, width * 0.9);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ThemedColorPickerButton
|
||||
color={defaultColor}
|
||||
onPress={openModal}
|
||||
/>
|
||||
|
||||
<CustomModal
|
||||
isVisible={isModalVisible}
|
||||
onClose={closeModal}
|
||||
>
|
||||
<View style={[styles.modalContent, { width: modalWidth, backgroundColor: theme.colors.surface }]}>
|
||||
<ColorPicker
|
||||
style={{ width: '100%' }}
|
||||
value={pickerHex}
|
||||
onComplete={(color: ColorFormatsObject) => setPickerHex(color.hex)}
|
||||
>
|
||||
<Preview style={{ marginBottom: 15 }} />
|
||||
<Panel1 style={{ marginBottom: 15 }} />
|
||||
<HueSlider style={{ marginBottom: 15 }} />
|
||||
<Swatches style={{ marginTop: 10 }} />
|
||||
</ColorPicker>
|
||||
|
||||
<ThemedButton
|
||||
style={{ marginTop: 20, width: '100%' }}
|
||||
mode="contained"
|
||||
onPress={handleConfirm}
|
||||
title={"Bestätigen"}
|
||||
/>
|
||||
</View>
|
||||
</CustomModal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
modalContent: {
|
||||
padding: 20,
|
||||
borderRadius: 12,
|
||||
alignSelf: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
const rgbToHex = ([r, g, b]: [number, number, number]) =>
|
||||
`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||
|
||||
const hexToRgbArray = (hex: string): [number, number, number] => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
|
||||
: [255, 255, 255];
|
||||
};
|
||||
@@ -1,105 +0,0 @@
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {StyleSheet, View, Dimensions} from 'react-native';
|
||||
import ColorPicker, {Panel1, Swatches, Preview, HueSlider, ColorFormatsObject} from 'reanimated-color-picker';
|
||||
import CustomModal, {CustomModalHandles} from "@/src/components/themed/CustomModal";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
import {useTheme} from "@/src/context/ThemeProvider";
|
||||
|
||||
interface CustomColorPickerProps {
|
||||
defaultColor?: [number, number, number];
|
||||
onSelect: (rgb: [number, number, number]) => void;
|
||||
}
|
||||
|
||||
export default function CustomColorPicker({defaultColor = [255, 255, 255], onSelect}: CustomColorPickerProps) {
|
||||
const [currentColor, setCurrentColor] = useState(defaultColor);
|
||||
const modalRef = useRef<CustomModalHandles>(null);
|
||||
const {theme} = useTheme();
|
||||
|
||||
const rgbToHex = ([r, g, b]: [number, number, number]) =>
|
||||
`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
||||
|
||||
const hexToRgbArray = (hex: string): [number, number, number] => {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
|
||||
: [255, 255, 255];
|
||||
};
|
||||
|
||||
const [pickerHex, setPickerHex] = useState(rgbToHex(defaultColor));
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentColor(defaultColor);
|
||||
setPickerHex(rgbToHex(defaultColor));
|
||||
}, [defaultColor]);
|
||||
|
||||
const {width, height} = Dimensions.get('window');
|
||||
const pickerWidth = Math.min(400, width * 0.9);
|
||||
const pickerHeight = Math.min(400, height * 0.6);
|
||||
|
||||
const handleOk = () => {
|
||||
const rgb = hexToRgbArray(pickerHex);
|
||||
setCurrentColor(rgb);
|
||||
onSelect(rgb);
|
||||
console.log(rgb)
|
||||
console.log(modalRef.current)
|
||||
modalRef.current?.close();
|
||||
};
|
||||
|
||||
const resetModal = () => {
|
||||
setCurrentColor(defaultColor);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<CustomModal resetCallback={resetModal} buttonTitle={"Color Picker"} buttonMode={"outlined"} ref={modalRef}>
|
||||
<View style={styles.modalWrapper}>
|
||||
<View style={[styles.modalContent, {width: pickerWidth, backgroundColor: theme.colors.onSurface}]}>
|
||||
<ColorPicker
|
||||
style={{width: pickerWidth * 0.9, height: pickerHeight}}
|
||||
value={pickerHex}
|
||||
onComplete={(color: ColorFormatsObject) => setPickerHex(color.hex)}
|
||||
>
|
||||
<Preview style={{marginBottom: 15}}/>
|
||||
<Panel1 style={{marginBottom: 15}}/>
|
||||
<HueSlider style={{marginBottom: 15}}/>
|
||||
<Swatches style={{marginTop: 10, marginBottom: 20}}/>
|
||||
</ColorPicker>
|
||||
|
||||
<View style={styles.buttonContainer}>
|
||||
<ThemedButton mode="contained" onPress={handleOk} title={"Bestätigen"}/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</CustomModal>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
overlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
modalWrapper: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
modalContent: {
|
||||
padding: 20,
|
||||
backgroundColor: 'white',
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
},
|
||||
buttonContainer: {
|
||||
marginTop: 15,
|
||||
width: '50%',
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, {forwardRef, useImperativeHandle, useState} from "react";
|
||||
import React from "react";
|
||||
import {StyleSheet, View} from "react-native";
|
||||
import Modal from "react-native-modal";
|
||||
import ThemedButton from "@/src/components/themed/ThemedButton";
|
||||
|
||||
export interface CustomModalHandles {
|
||||
open: () => void;
|
||||
@@ -9,43 +8,29 @@ export interface CustomModalHandles {
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
resetCallback: () => void;
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
buttonTitle: string;
|
||||
buttonMode?: 'text' | 'outlined' | 'contained';
|
||||
onModalDidHide?: () => void;
|
||||
}
|
||||
|
||||
const CustomModal = forwardRef<CustomModalHandles, Props>(
|
||||
({resetCallback, children, buttonTitle, buttonMode = "outlined"}, ref) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const resetModal = () => {
|
||||
setIsVisible(false);
|
||||
resetCallback();
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => setIsVisible(true),
|
||||
close: () => setIsVisible(false),
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemedButton mode={buttonMode} onPress={() => setIsVisible(true)} title={buttonTitle} />
|
||||
<Modal
|
||||
isVisible={isVisible}
|
||||
onModalHide={resetModal}
|
||||
onBackdropPress={resetModal}
|
||||
onBackButtonPress={resetModal}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
{children}
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
const CustomModal = ({ isVisible, onClose, children, onModalDidHide }: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isVisible={isVisible}
|
||||
onBackdropPress={onClose}
|
||||
onBackButtonPress={onClose}
|
||||
onModalHide={onModalDidHide}
|
||||
animationIn="zoomIn"
|
||||
animationOut="zoomOut"
|
||||
backdropTransitionOutTiming={0}
|
||||
>
|
||||
<View style={styles.modalContent}>
|
||||
{children}
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomModal;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React from "react";
|
||||
import {ImageBackground, KeyboardAvoidingView, StyleSheet,} from "react-native";
|
||||
import {ImageBackground, KeyboardAvoidingView, Platform, StyleSheet,} from "react-native";
|
||||
import {useTheme} from "../../context/ThemeProvider";
|
||||
import { Dimensions } from "react-native";
|
||||
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
@@ -16,15 +14,16 @@ export default function ThemedBackground({children}: Props) {
|
||||
resizeMode="repeat"
|
||||
style={[styles.background, {backgroundColor: theme.colors.background}]}
|
||||
>
|
||||
<KeyboardAvoidingView style={styles.container} behavior="padding">
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
>
|
||||
{children}
|
||||
</KeyboardAvoidingView>
|
||||
</ImageBackground>
|
||||
);
|
||||
}
|
||||
|
||||
const screenWidth = Dimensions.get("window").width;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
background: {
|
||||
flex: 1,
|
||||
@@ -33,9 +32,8 @@ const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 20,
|
||||
width: Math.min(screenWidth, 340),
|
||||
width: "90%",
|
||||
maxWidth: 600,
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from "react";
|
||||
import {StyleSheet, Text} from "react-native";
|
||||
import {Button as PaperButton} from "react-native-paper";
|
||||
import {StyleSheet} from "react-native";
|
||||
import { Button as PaperButton } from "react-native-paper";
|
||||
import {useTheme} from "@/src/context/ThemeProvider";
|
||||
import {IconSource} from "react-native-paper/src/components/Icon";
|
||||
|
||||
type Props = {
|
||||
mode: "text" | "outlined" | "contained" | "elevated" | "contained-tonal"
|
||||
@@ -10,9 +11,10 @@ type Props = {
|
||||
onPress: () => void;
|
||||
disabled?: boolean;
|
||||
title: string;
|
||||
icon?: IconSource;
|
||||
}
|
||||
|
||||
export default function ThemedButton({mode, style, title, ...props}: Props) {
|
||||
export default function ThemedButton({mode, style, title, icon, ...props}: Props) {
|
||||
const {theme} = useTheme();
|
||||
return (
|
||||
<PaperButton
|
||||
@@ -23,9 +25,10 @@ export default function ThemedButton({mode, style, title, ...props}: Props) {
|
||||
]}
|
||||
labelStyle={styles.text}
|
||||
mode={mode}
|
||||
icon={icon}
|
||||
{...props}
|
||||
>
|
||||
<Text>{title}</Text>
|
||||
{title}
|
||||
</PaperButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Text, StyleSheet, Pressable } from 'react-native';
|
||||
import Checkbox from 'expo-checkbox';
|
||||
import { useTheme } from '@/src/context/ThemeProvider';
|
||||
|
||||
type ThemedCheckboxProps = {
|
||||
label: string;
|
||||
value: boolean;
|
||||
onValueChange: (newValue: boolean) => void;
|
||||
style?: object;
|
||||
};
|
||||
|
||||
const ThemedCheckbox = ({ label, value, onValueChange, style }: ThemedCheckboxProps) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const componentStyles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 12,
|
||||
},
|
||||
checkbox: {
|
||||
marginRight: 8,
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
color: theme.colors.onSurface,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Pressable onPress={() => onValueChange(!value)} style={[componentStyles.container, style]}>
|
||||
<Checkbox
|
||||
style={componentStyles.checkbox}
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
color={value ? theme.colors.primary : undefined} // Setzt die Farbe aus dem Theme
|
||||
/>
|
||||
<Text style={componentStyles.label}>{label}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemedCheckbox;
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Pressable, View, Text, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import { useTheme } from '@/src/context/ThemeProvider';
|
||||
|
||||
type Props = {
|
||||
color: [number, number, number];
|
||||
onPress: () => void;
|
||||
title?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
const ThemedColorPickerButton = ({ color, onPress, title = "Farbe wählen", style }: Props) => {
|
||||
const { theme } = useTheme();
|
||||
const rgbColor = `rgb(${color.join(',')})`;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.container,
|
||||
{
|
||||
backgroundColor: theme.colors.surface,
|
||||
borderColor: theme.colors.outline
|
||||
},
|
||||
pressed && styles.pressed,
|
||||
style
|
||||
]}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={[styles.swatch, { backgroundColor: rgbColor }]} />
|
||||
<Text style={{ color: theme.colors.onSurface }}>{title}</Text>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemedColorPickerButton;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 28,
|
||||
borderWidth: 1,
|
||||
gap: 12,
|
||||
},
|
||||
swatch: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
borderColor: 'rgba(255, 255, 255, 0.5)',
|
||||
},
|
||||
pressed: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import { SegmentedButtons } from 'react-native-paper';
|
||||
import { useTheme } from '@/src/context/ThemeProvider';
|
||||
|
||||
export type SegmentedButton<T extends string> = {
|
||||
value: T;
|
||||
label: string;
|
||||
icon?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
type ThemedSegmentedButtonsProps<T extends string> = {
|
||||
value: T;
|
||||
onValueChange: (value: T) => void;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
} & ({
|
||||
buttons: SegmentedButton<T>[];
|
||||
options?: never;
|
||||
} | {
|
||||
buttons?: never;
|
||||
options: Record<T, string>;
|
||||
});
|
||||
|
||||
const ThemedSegmentedButtons = <T extends string>({
|
||||
value,
|
||||
onValueChange,
|
||||
buttons,
|
||||
options,
|
||||
style,
|
||||
}: ThemedSegmentedButtonsProps<T>) => {
|
||||
|
||||
const finalButtons = buttons || (options ? (Object.keys(options) as T[]).map(key => ({
|
||||
value: key,
|
||||
label: options[key],
|
||||
})) : []);
|
||||
|
||||
const handleValueChange = (val: string) => {
|
||||
onValueChange(val as T);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, style]}>
|
||||
<SegmentedButtons
|
||||
value={value}
|
||||
onValueChange={handleValueChange}
|
||||
buttons={finalButtons}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: '100%',
|
||||
marginVertical: 12,
|
||||
},
|
||||
});
|
||||
|
||||
export default ThemedSegmentedButtons;
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { forwardRef } from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { TextInput as Input, TextInputProps } from "react-native-paper"; // Importiere den korrekten Typ
|
||||
import { TextInput as Input, TextInputProps } from "react-native-paper";
|
||||
import { useTheme } from "@/src/context/ThemeProvider";
|
||||
|
||||
export type ThemedTextInputProps = TextInputProps & { // Erweitere die Typen um die Eigenschaften von TextInput
|
||||
export type ThemedTextInputProps = TextInputProps & {
|
||||
errorText?: string;
|
||||
description?: string;
|
||||
error?: boolean;
|
||||
@@ -28,8 +28,8 @@ const ThemedTextInput = forwardRef<any, ThemedTextInputProps>(
|
||||
<Input
|
||||
underlineColor="transparent"
|
||||
mode="outlined"
|
||||
{...props} // Übergib alle Props an Input
|
||||
ref={ref} // Übergibt die ref an die Input-Komponente
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
{description && !error ? (
|
||||
<Text style={styles.description}>{description}</Text>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {Platform} from "react-native";
|
||||
type AuthContextType = {
|
||||
isAuthenticated: boolean | null;
|
||||
token: string | null;
|
||||
login: (username: string, password: string) => Promise<void>;
|
||||
login: (username: string, password: string, stayLoggedIn?: boolean) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
authenticatedUser: User | null;
|
||||
error: { field: string, message: string } | null;
|
||||
@@ -53,7 +53,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({children}
|
||||
setToken(null);
|
||||
setIsAuthenticated(false);
|
||||
setAuthenticatedUser(null);
|
||||
setError({field: "general", message:"Token is invalid."});
|
||||
setError({field: "general", message: "Token is invalid."});
|
||||
return null;
|
||||
}
|
||||
const user = response.data;
|
||||
@@ -61,7 +61,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({children}
|
||||
return user;
|
||||
}
|
||||
|
||||
const login = async (username: string, password: string) => {
|
||||
const login = async (username: string, password: string, stayLoggedIn?: boolean) => {
|
||||
if (isAuthenticated) {
|
||||
console.log("Already authenticated");
|
||||
return;
|
||||
@@ -69,7 +69,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({children}
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await new RestService(null).login(username, password);
|
||||
const response = await new RestService(null).login(username, password, stayLoggedIn);
|
||||
if (!response.ok) {
|
||||
console.error("Login failed:", response.data);
|
||||
const message = response.data.message!;
|
||||
|
||||
@@ -125,7 +125,7 @@ class RestService {
|
||||
return this.request<ApiResponse<User>>('DELETE', '/user/me/spotify');
|
||||
}
|
||||
|
||||
async login(username: string, password: string): Promise<ApiResponse<{
|
||||
async login(username: string, password: string, stayLoggedIn?: boolean): Promise<ApiResponse<{
|
||||
message?: string, token?: string, details?: {
|
||||
field: string;
|
||||
code: string;
|
||||
@@ -139,7 +139,7 @@ class RestService {
|
||||
}>>(
|
||||
"POST",
|
||||
'/auth/login',
|
||||
{username, password},
|
||||
{username, password, stayLoggedIn},
|
||||
{'Content-Type': 'application/json'}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user