fitaiProto/apps/mobile/src/components/GeofenceStatus.tsx

199 lines
5.0 KiB
TypeScript

import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator } from 'react-native'
import { useState, useEffect } from 'react'
import { LinearGradient } from 'expo-linear-gradient'
import { Ionicons } from '@expo/vector-icons'
import * as Location from 'expo-location'
import { theme } from '../styles/theme'
import { isWithinGeofence, calculateDistance, getFormattedDistance, GYM_LOCATION } from '../services/geofencing'
export interface GeofenceStatusProps {
onStatusChange?: (isInside: boolean) => void
}
export const GeofenceStatus = ({ onStatusChange }: GeofenceStatusProps) => {
const [loading, setLoading] = useState(false)
const [isInside, setIsInside] = useState(false)
const [distance, setDistance] = useState<string>('--')
const [permission, setPermission] = useState<string>('undetermined')
const [lastCheck, setLastCheck] = useState<Date | null>(null)
const checkGeofence = async () => {
try {
setLoading(true)
// Request location permission
const { status } = await Location.requestForegroundPermissionsAsync()
setPermission(status)
if (status !== 'granted') {
setLoading(false)
return
}
// Get current location
const location = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy.Balanced,
})
const { latitude, longitude } = location.coords
// Check if within geofence
const within = isWithinGeofence(latitude, longitude, 500)
setIsInside(within)
onStatusChange?.(within)
// Calculate distance
const dist = calculateDistance(
latitude,
longitude,
GYM_LOCATION.latitude,
GYM_LOCATION.longitude
)
setDistance(getFormattedDistance(dist))
setLastCheck(new Date())
} catch (error) {
console.error('Geofence check error:', error)
} finally {
setLoading(false)
}
}
useEffect(() => {
checkGeofence()
// Auto-check every 30 seconds
const interval = setInterval(checkGeofence, 30000)
return () => clearInterval(interval)
}, [])
return (
<View style={styles.container}>
<LinearGradient
colors={
isInside
? ['#10b981', '#6ee7b7', '#a7f3d0']
: ['#64748b', '#94a3b8', '#cbd5e1']
}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
style={styles.card}
>
<View style={styles.header}>
<View style={styles.titleRow}>
<Ionicons
name={isInside ? 'location-sharp' : 'location'}
size={24}
color="#fff"
/>
<Text style={styles.title}>
{isInside ? 'At Gym' : 'Away from Gym'}
</Text>
</View>
<TouchableOpacity
onPress={checkGeofence}
disabled={loading}
style={styles.refreshButton}
>
{loading ? (
<ActivityIndicator size="small" color="#fff" />
) : (
<Ionicons name="refresh" size={20} color="#fff" />
)}
</TouchableOpacity>
</View>
<View style={styles.content}>
<View style={styles.stat}>
<Text style={styles.statLabel}>Distance</Text>
<Text style={styles.statValue}>{distance}</Text>
</View>
{lastCheck && (
<Text style={styles.lastCheck}>
Last check: {lastCheck.toLocaleTimeString()}
</Text>
)}
</View>
{permission !== 'granted' && (
<View style={styles.permissionAlert}>
<Ionicons name="alert-circle" size={16} color="#fff" />
<Text style={styles.permissionText}>
Location permission required
</Text>
</View>
)}
</LinearGradient>
</View>
)
}
const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
paddingVertical: 12,
},
card: {
borderRadius: 16,
padding: 16,
overflow: 'hidden',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
titleRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
},
title: {
fontSize: 18,
fontWeight: '600',
color: '#fff',
},
refreshButton: {
padding: 8,
borderRadius: 8,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
},
content: {
gap: 12,
},
stat: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
statLabel: {
fontSize: 14,
color: 'rgba(255, 255, 255, 0.8)',
fontWeight: '500',
},
statValue: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
},
lastCheck: {
fontSize: 12,
color: 'rgba(255, 255, 255, 0.7)',
marginTop: 4,
},
permissionAlert: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
marginTop: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: 'rgba(255, 255, 255, 0.3)',
},
permissionText: {
fontSize: 12,
color: '#fff',
fontWeight: '500',
},
})