96 lines
2.8 KiB
TypeScript
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
|