fix permission request & display photo center

This commit is contained in:
Warunee Tamkoo 2026-05-08 11:30:41 +07:00
parent 92f9137cbb
commit 30a8f01441
2 changed files with 191 additions and 81 deletions

View file

@ -1,52 +1,171 @@
import { useQuasar } from 'quasar'
import { onBeforeUnmount, ref } from 'vue'
import { usePrivacyStore } from '@/stores/privacy'
type BrowserPermissionState = PermissionState | 'unsupported'
type PermissionQueryName = 'camera' | 'geolocation'
export function usePermissions() {
const $q = useQuasar()
const privacyStore = usePrivacyStore()
const cameraPermissionState = ref<BrowserPermissionState>('prompt')
const locationPermissionState = ref<BrowserPermissionState>('prompt')
// const checkCameraPermission = (): boolean => {
// if (!privacyStore.isAccepted) {
// privacyStore.modalPrivacy = true
// $q.notify({
// type: 'warning',
// message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งานกล้อง',
// position: 'top',
// })
// return false
// }
// return true
// }
let cameraPermissionStatus: PermissionStatus | null = null
let locationPermissionStatus: PermissionStatus | null = null
// const checkLocationPermission = (): boolean => {
// if (!privacyStore.isAccepted) {
// privacyStore.modalPrivacy = true
// $q.notify({
// type: 'warning',
// message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งานแผนที่',
// position: 'top',
// })
// return false
// }
// return true
// }
const isPermissionsApiSupported = () =>
typeof navigator !== 'undefined' && 'permissions' in navigator
const setPermissionState = (
target: typeof cameraPermissionState | typeof locationPermissionState,
state: BrowserPermissionState
) => {
target.value = state
}
const setPermissionChangeListener = (
name: PermissionQueryName,
status: PermissionStatus
) => {
status.onchange = () => {
const target =
name === 'camera' ? cameraPermissionState : locationPermissionState
setPermissionState(target, status.state)
}
}
async function queryPermissionState(name: PermissionQueryName) {
if (!isPermissionsApiSupported()) {
return null
}
try {
return await navigator.permissions.query({ name } as PermissionDescriptor)
} catch (error) {
return null
}
}
async function syncPermissionState(name: PermissionQueryName) {
const target =
name === 'camera' ? cameraPermissionState : locationPermissionState
const previousStatus =
name === 'camera' ? cameraPermissionStatus : locationPermissionStatus
if (previousStatus) {
previousStatus.onchange = null
}
const status = await queryPermissionState(name)
if (!status) {
if (target.value === 'prompt') {
setPermissionState(target, 'unsupported')
}
return
}
if (name === 'camera') {
cameraPermissionStatus = status
} else {
locationPermissionStatus = status
}
setPermissionState(target, status.state)
setPermissionChangeListener(name, status)
}
async function syncPermissionStates() {
await Promise.all([
syncPermissionState('camera'),
syncPermissionState('geolocation'),
])
}
const checkPrivacyAccepted = (): boolean => {
if (!privacyStore.isAccepted) {
privacyStore.modalPrivacy = true
// $q.notify({
// type: 'warning',
// message: 'กรุณายอมรับนโยบายคุ้มครองข้อมูลส่วนบุคคลก่อนใช้งาน',
// position: 'center',
// })
return false
}
return true
}
async function requestCameraPermission() {
if (!checkPrivacyAccepted()) {
return false
}
if (cameraPermissionState.value === 'granted') {
return true
}
if (!navigator.mediaDevices?.getUserMedia) {
setPermissionState(cameraPermissionState, 'unsupported')
return false
}
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user' },
audio: false,
})
stream.getTracks().forEach((track) => track.stop())
setPermissionState(cameraPermissionState, 'granted')
return true
} catch (error) {
setPermissionState(cameraPermissionState, 'denied')
return false
} finally {
await syncPermissionState('camera')
}
}
async function requestLocationPermission() {
if (!checkPrivacyAccepted()) {
return false
}
if (locationPermissionState.value === 'granted') {
return true
}
if (!navigator.geolocation) {
setPermissionState(locationPermissionState, 'unsupported')
return false
}
return new Promise<boolean>((resolve) => {
navigator.geolocation.getCurrentPosition(
async () => {
setPermissionState(locationPermissionState, 'granted')
await syncPermissionState('geolocation')
resolve(true)
},
async () => {
setPermissionState(locationPermissionState, 'denied')
await syncPermissionState('geolocation')
resolve(false)
}
)
})
}
onBeforeUnmount(() => {
if (cameraPermissionStatus) {
cameraPermissionStatus.onchange = null
}
if (locationPermissionStatus) {
locationPermissionStatus.onchange = null
}
})
return {
// checkCameraPermission,
// checkLocationPermission,
cameraPermissionState,
locationPermissionState,
checkPrivacyAccepted,
syncPermissionStates,
requestCameraPermission,
requestLocationPermission,
}
}

View file

@ -18,7 +18,11 @@ import MapCheck from '@/components/AscGISMap.vue'
const mixin = useCounterMixin()
const { date2Thai, showLoader, hideLoader, messageError, dialogConfirm } = mixin
const $q = useQuasar()
const { checkPrivacyAccepted } = usePermissions()
const {
checkPrivacyAccepted,
syncPermissionStates,
requestCameraPermission,
} = usePermissions()
const privacyStore = usePrivacyStore()
const positionKeycloakStore = usePositionKeycloakStore()
const MOCK_CHECK_DELAY_MS = 800
@ -471,34 +475,14 @@ async function openCamera() {
return
}
if (!isPermissionCameraDenied.value) {
try {
if (cameraIsOn.value) {
camera.value?.stop()
} else {
// Keep the initial stream aligned with the Camera component's front-camera constraint.
await camera.value?.start()
const devices: any = await camera.value?.devices(['videoinput'])
if (devices) {
availableCameras.value = devices
if (cameraIsOn.value) {
camera.value?.stop()
cameraIsOn.value = false
return
}
// Avoid an extra stop/start cycle here; on iOS it can override the initial camera selection.
const activeId = camera.value?.currentDeviceID()
const activeIdx = activeId
? devices.findIndex((d: any) => d.deviceId === activeId)
: -1
currentCameraIndex.value = activeIdx >= 0 ? activeIdx : 0
currentCameraType.value = identifyCameraType(
devices[currentCameraIndex.value]?.label || ''
)
}
}
cameraIsOn.value = !cameraIsOn.value
} catch (error) {
console.error('Error opening camera:', error)
messageError($q, error, 'ไม่สามารถเปิดกล้องได้ กรุณาลองใหม่อีกครั้ง')
}
} else {
const hasCameraPermission = await requestCameraPermission()
if (!hasCameraPermission) {
messageError(
$q,
'',
@ -506,6 +490,29 @@ async function openCamera() {
)
return
}
try {
// Keep the initial stream aligned with the Camera component's front-camera constraint.
await camera.value?.start()
const devices: any = await camera.value?.devices(['videoinput'])
if (devices) {
availableCameras.value = devices
// Avoid an extra stop/start cycle here; on iOS it can override the initial camera selection.
const activeId = camera.value?.currentDeviceID()
const activeIdx = activeId
? devices.findIndex((d: any) => d.deviceId === activeId)
: -1
currentCameraIndex.value = activeIdx >= 0 ? activeIdx : 0
currentCameraType.value = identifyCameraType(
devices[currentCameraIndex.value]?.label || ''
)
}
cameraIsOn.value = true
} catch (error) {
console.error('Error opening camera:', error)
messageError($q, error, 'ไม่สามารถเปิดกล้องได้ กรุณาลองใหม่อีกครั้ง')
}
}
/** change camera device*/
@ -880,21 +887,6 @@ function handleVisibilityChange() {
}
}
const isPermissionCameraDenied = ref<boolean>(false) //
async function requestCamera() {
try {
// Probe camera permission with the same front-camera constraint, then release the stream immediately.
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user' },
audio: false,
})
stream.getTracks().forEach((track) => track.stop())
} catch (err) {
isPermissionCameraDenied.value = true
}
}
/** Hook*/
onMounted(async () => {
// clock
@ -911,11 +903,11 @@ onMounted(async () => {
startChecking()
}
await syncPermissionStates()
// privacy
if (privacyStore.isAccepted) {
mapRef.value?.requestLocationPermission()
// iOS uses the file-input fallback and does not need a getUserMedia probe
if (!useNativePhotoCapture.value) requestCamera()
}
})
@ -954,11 +946,10 @@ onBeforeUnmount(() => {
watch(
() => privacyStore.isAccepted,
(newVal) => {
async (newVal) => {
if (newVal) {
await syncPermissionStates()
mapRef.value?.requestLocationPermission()
// iOS uses the file-input fallback and does not need a getUserMedia probe
if (!useNativePhotoCapture.value) requestCamera()
}
}
)