/** * HomeScreen — karta med live-uppdrag + snabbstatistik (intjänat idag) * Integrerad med useLiveLocation() för kontinuerlig GPS-tracking */ import { useCallback, useEffect, useRef, useState } from 'react' import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, Platform, } from 'react-native' import MapView, { Marker, Region, PROVIDER_DEFAULT } from 'react-native-maps' import { useNavigation } from '@react-navigation/native' import { missions, wallet, type Mission, type WalletBalance } from '../lib/api' import { useLiveLocation } from '../src/hooks/useLiveLocation' import { useHeading } from '../src/hooks/useHeading' import { useLocationStore } from '../src/stores/locationStore' import { UserLocationMarker } from '../src/components/gps/UserLocationMarker' import { FollowUserButton } from '../src/components/gps/FollowUserButton' import { GpsDiagnostics } from '../src/components/gps/GpsDiagnostics' const STOCKHOLM_ARCHIPELAGO: Region = { latitude: 59.45, longitude: 18.5, latitudeDelta: 0.8, longitudeDelta: 0.8, } const CATEGORY_EMOJI: Record = { infrastruktur: '🏗️', vägar: '🛤️', hamnar: '⚓', bryggor: '🚢', fritidshus: '🏠', miljö: '🌿', övrigt: '📍', } export function HomeScreen() { const navigation = useNavigation() const mapRef = useRef(null) // Initialize live location tracking useLiveLocation() useHeading() const { currentPosition, followUser, setUserPannedMap, locationAuthStatus, } = useLocationStore() const [region, setRegion] = useState(STOCKHOLM_ARCHIPELAGO) const [nearbyMissions, setNearbyMissions] = useState([]) const [balance, setBalance] = useState(null) const [loadingMissions, setLoadingMissions] = useState(false) const [showDiagnostics, setShowDiagnostics] = useState(false) const [initialLocationSet, setInitialLocationSet] = useState(false) // Center map on first position fix useEffect(() => { if (currentPosition && !initialLocationSet) { const newRegion: Region = { latitude: currentPosition.coords.latitude, longitude: currentPosition.coords.longitude, latitudeDelta: 0.5, longitudeDelta: 0.5, } setRegion(newRegion) mapRef.current?.animateToRegion(newRegion, 800) fetchNearby(currentPosition.coords.latitude, currentPosition.coords.longitude) setInitialLocationSet(true) } }, [currentPosition, initialLocationSet]) // Auto-follow user when followUser is true useEffect(() => { if (followUser && currentPosition && mapRef.current) { const newRegion: Region = { latitude: currentPosition.coords.latitude, longitude: currentPosition.coords.longitude, latitudeDelta: region.latitudeDelta, longitudeDelta: region.longitudeDelta, } mapRef.current.animateToRegion(newRegion, 300) } }, [currentPosition, followUser, region.latitudeDelta, region.longitudeDelta]) // Wallet balance useEffect(() => { wallet.balance().then(setBalance).catch(() => {}) }, []) const fetchNearby = useCallback(async (lat: number, lng: number) => { setLoadingMissions(true) try { const data = await missions.nearby(lat, lng, 60) setNearbyMissions(data) } catch { // silent } finally { setLoadingMissions(false) } }, []) const onRegionChangeComplete = useCallback((r: Region) => { setRegion(r) fetchNearby(r.latitude, r.longitude) }, [fetchNearby]) const onPanDrag = useCallback(() => { setUserPannedMap(true) }, [setUserPannedMap]) const availableSEK = balance ? ((balance.balance - balance.pending_payouts) / 100).toFixed(0) : '—' const pendingSEK = balance ? (balance.pending_payouts / 100).toFixed(0) : '—' return ( {/* Map */} {/* Custom user location marker with heading */} {currentPosition && ( )} {nearbyMissions.map(m => ( navigation.navigate('MissionDetail', { missionId: m.id })} > {CATEGORY_EMOJI[m.category] ?? '📍'} {(m.reward_amount / 100).toFixed(0)} kr ))} {/* Loading indicator */} {loadingMissions && ( )} {/* GPS Diagnostics toggle */} setShowDiagnostics(prev => !prev)} > {showDiagnostics ? '🔧' : '🛠️'} {/* GPS Diagnostics overlay */} {showDiagnostics && } {/* Stats card */} Tillgängligt {availableSEK} kr Under behandling {pendingSEK} kr Uppdrag nära {nearbyMissions.length} {/* Follow user button */} {/* Location status indicator */} {locationAuthStatus !== 'granted' && locationAuthStatus !== null && ( ⚠️ GPS-behörighet: {locationAuthStatus} )} {/* Missions list button */} navigation.navigate('Uppdrag')} > 🎯 {nearbyMissions.length} uppdrag nära dig ) } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#0A0A1B' }, map: { flex: 1 }, markerBubble: { backgroundColor: '#6366f1', borderRadius: 20, paddingHorizontal: 10, paddingVertical: 6, alignItems: 'center', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.4, shadowRadius: 4, elevation: 4, }, markerEmoji: { fontSize: 14 }, markerReward: { color: '#fff', fontSize: 11, fontWeight: '700' }, loadingOverlay: { position: 'absolute', top: Platform.OS === 'ios' ? 60 : 16, right: 16, backgroundColor: 'rgba(0,0,0,0.7)', borderRadius: 20, padding: 8, }, diagnosticsToggle: { position: 'absolute', top: Platform.OS === 'ios' ? 60 : 16, right: 60, width: 36, height: 36, backgroundColor: 'rgba(10,10,27,0.9)', borderRadius: 18, borderWidth: 1, borderColor: 'rgba(255,255,255,0.15)', alignItems: 'center', justifyContent: 'center', }, diagnosticsToggleText: { fontSize: 16 }, statsCard: { position: 'absolute', top: Platform.OS === 'ios' ? 60 : 16, left: 16, right: 104, flexDirection: 'row', backgroundColor: 'rgba(10,10,27,0.92)', borderRadius: 16, borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)', padding: 12, alignItems: 'center', }, statItem: { flex: 1, alignItems: 'center' }, statLabel: { color: 'rgba(255,255,255,0.45)', fontSize: 10, marginBottom: 2 }, statValue: { color: '#fff', fontSize: 15, fontWeight: '800' }, statDivider: { width: 1, height: 28, backgroundColor: 'rgba(255,255,255,0.1)' }, followBtnContainer: { position: 'absolute', bottom: 100, right: 16, }, locationWarning: { position: 'absolute', top: Platform.OS === 'ios' ? 120 : 76, left: 16, right: 16, backgroundColor: 'rgba(245,158,11,0.2)', borderRadius: 12, padding: 10, borderWidth: 1, borderColor: 'rgba(245,158,11,0.4)', alignItems: 'center', }, locationWarningText: { color: '#f59e0b', fontSize: 12, fontWeight: '600', }, missionsBtn: { position: 'absolute', bottom: 32, left: 16, right: 16, backgroundColor: '#6366f1', borderRadius: 16, paddingVertical: 16, alignItems: 'center', shadowColor: '#6366f1', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.4, shadowRadius: 12, elevation: 8, }, missionsBtnText: { color: '#fff', fontWeight: '700', fontSize: 16 }, })