guardia-messenger/hooks/useGPSTag.ts

96 lines
2.8 KiB
TypeScript

/**
* useGPSTag — GPS 위치 태깅 훅 (기능 #59)
*
* SR 등록 / 현장 체크인 시 현재 좌표를 자동 첨부한다.
* expo-location 미설치 환경(개발/시뮬레이터)에서도 빌드/런타임이 깨지지 않도록
* 동적 require + graceful fallback 처리한다.
*
* 빌드 금기 준수: app.json 플러그인 등록 없이 런타임 권한 요청만 사용.
*/
import { useState, useCallback } from 'react'
export interface GeoTag {
lat: number
lng: number
accuracy?: number | null
ts: string
}
type PermState = 'unknown' | 'granted' | 'denied'
// 동적 로드 — 모듈이 없으면 null
function loadLocation(): any | null {
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('expo-location')
} catch {
return null
}
}
export function useGPSTag() {
const [tag, setTag] = useState<GeoTag | null>(null)
const [perm, setPerm] = useState<PermState>('unknown')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const requestPermission = useCallback(async (): Promise<boolean> => {
const Location = loadLocation()
if (!Location) {
setError('위치 모듈을 사용할 수 없습니다 (EAS 빌드 필요)')
setPerm('denied')
return false
}
try {
const { status } = await Location.requestForegroundPermissionsAsync()
const granted = status === 'granted'
setPerm(granted ? 'granted' : 'denied')
if (!granted) setError('위치 권한이 거부되었습니다')
return granted
} catch (e: any) {
setPerm('denied')
setError(e?.message ?? '권한 요청 실패')
return false
}
}, [])
/** 현재 위치 1회 획득 → GeoTag 반환(실패 시 null) */
const capture = useCallback(async (): Promise<GeoTag | null> => {
setLoading(true)
setError(null)
const Location = loadLocation()
if (!Location) {
setError('위치 모듈을 사용할 수 없습니다 (EAS 빌드 필요)')
setLoading(false)
return null
}
try {
const ok = perm === 'granted' || (await requestPermission())
if (!ok) {
setLoading(false)
return null
}
const pos = await Location.getCurrentPositionAsync({
accuracy: Location.Accuracy?.Balanced ?? 3,
})
const t: GeoTag = {
lat: pos.coords.latitude,
lng: pos.coords.longitude,
accuracy: pos.coords.accuracy ?? null,
ts: new Date().toISOString(),
}
setTag(t)
setLoading(false)
return t
} catch (e: any) {
setError(e?.message ?? '위치 획득 실패')
setLoading(false)
return null
}
}, [perm, requestPermission])
return { tag, perm, loading, error, requestPermission, capture, available: !!loadLocation() }
}
export default useGPSTag