diff --git a/app/(tabs)/settings.tsx b/app/(tabs)/settings.tsx index 0db1615..7e2e03e 100644 --- a/app/(tabs)/settings.tsx +++ b/app/(tabs)/settings.tsx @@ -1,13 +1,13 @@ import ThemedHeader from "@/src/components/themed/ThemedHeader"; -import React from "react"; +import React, {useState} from "react"; import ThemedBackground from "@/src/components/themed/ThemedBackground"; 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"; - +import {restService, Token, LocationResult} from "@/src/services/RestService"; +import CustomModal from "@/src/components/themed/CustomModal"; import {useAuth} from "@/src/stores/authStore"; -import {View, Text} from "react-native"; +import {View, Text, TextInput, Pressable, ScrollView} from "react-native"; import ThemedButton from "@/src/components/themed/ThemedButton"; import {useRouter} from "expo-router"; @@ -15,6 +15,12 @@ export default function SettingsScreen() { const {authenticatedUser, logout, refreshUser} = useAuth(); const router = useRouter(); + const [locationQuery, setLocationQuery] = useState(""); + const [locationResults, setLocationResults] = useState([]); + const [isSearching, setIsSearching] = useState(false); + const [isLocationModalVisible, setIsLocationModalVisible] = useState(false); + const [hoveredLocation, setHoveredLocation] = useState(null); + const handleAuthSuccess = (token: Token) => { const spotifyConfig = { accessToken: token.access_token, @@ -31,6 +37,38 @@ export default function SettingsScreen() { }); }; + const searchLocation = async () => { + if (!locationQuery.trim()) return; + setIsSearching(true); + try { + const res = await restService.searchLocations(locationQuery.trim()); + if (res.ok) { + setLocationResults(res.data.locations || []); + } + } catch (e) { + console.error("Location search failed", e); + } finally { + setIsSearching(false); + } + }; + + const applyLocation = async (loc: LocationResult) => { + try { + await restService.updateSelfLocation({ name: loc.name, lat: loc.lat, lon: loc.lon }); + await refreshUser(); + closeAndResetModal(); + } catch (e) { + console.error("Location update failed", e); + } + }; + + const closeAndResetModal = () => { + setIsLocationModalVisible(false); + setLocationResults([]); + setHoveredLocation(null); + setLocationQuery(""); + }; + return ( @@ -38,6 +76,25 @@ export default function SettingsScreen() { Hallo, {authenticatedUser?.name} + + + + + Standort + + + Aktueller Standort: {authenticatedUser?.location?.name ?? "nicht gesetzt"} + + + setIsLocationModalVisible(true)} + className="px-3 py-2 rounded-lg bg-primary/10 border border-primary/30" + > + ✏️ Bearbeiten + + + + Erscheinungsbild @@ -96,8 +153,82 @@ export default function SettingsScreen() { }} /> + + + + + + Standort wählen + + + Schließen + + + + + + + + {locationResults.map((loc, idx) => ( + applyLocation(loc)} + onHoverIn={() => setHoveredLocation(loc)} + onHoverOut={() => setHoveredLocation(null)} + className="border border-outline dark:border-outline-dark rounded-lg p-3 mb-2" + > + + {loc.name} + + + {loc.country ?? "Unbekannt"}{loc.state ? `, ${loc.state}` : ""} + + + ))} + + + {locationResults.length > 0 && ( + + + Details + + + {hoveredLocation?.name ?? "—"} + + + {hoveredLocation + ? `${hoveredLocation.country ?? "Land unbekannt"}${hoveredLocation.state ? `, ${hoveredLocation.state}` : ""}` + : "—" + } + + + {hoveredLocation + ? `Koordinaten: ${hoveredLocation.lat.toFixed(4)}, ${hoveredLocation.lon.toFixed(4)}` + : "Fahre mit der Maus über einen Standort" + } + + + )} + + ); } - diff --git a/src/model/User.ts b/src/model/User.ts index 42e5165..332892f 100644 --- a/src/model/User.ts +++ b/src/model/User.ts @@ -5,6 +5,12 @@ export interface User { config: UserConfig, lastState: MatrixState, spotifyConfig: SpotifyConfig + location: { + name: string, + lat: number, + lon: number + }, + timezone: string } export interface UserConfig { diff --git a/src/services/RestService.ts b/src/services/RestService.ts index c385270..209f6ed 100644 --- a/src/services/RestService.ts +++ b/src/services/RestService.ts @@ -26,6 +26,15 @@ export interface S3File { size: number; } +export interface LocationResult { + name: string; + lat: number; + lon: number; + country?: string; + state?: string; + local_names?: Record; +} + // Token provider function type - will be set from authStore type TokenProvider = () => string | null; let tokenProvider: TokenProvider = () => null; @@ -204,6 +213,22 @@ class RestService { ); } + async searchLocations(query: string): Promise> { + return this.request>( + 'GET', + `/location/search?q=${encodeURIComponent(query)}` + ); + } + + async updateSelfLocation(payload: { name: string; lat: number; lon: number }): Promise> { + return this.request>( + 'PUT', + '/user/me/location', + payload, + {'Content-Type': 'application/json'} + ); + } + private async request(method: Method, url: string, data?: any, headers?: any): Promise { try { const response = await this.api.request({