-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add re request perm logic #685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
97448aa
065d2b9
4c102b9
3c9c7a2
d2e2f52
66170bb
2d11370
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| <script lang="ts"> | ||
| import { ButtonAction, Drawer } from "$lib/ui"; | ||
|
|
||
| interface CameraPermissionDialogProps { | ||
| isOpen: boolean; | ||
| onOpenSettings: () => void; | ||
| onGoBack?: () => void; | ||
| onOpenChange?: (value: boolean) => void; | ||
| title?: string; | ||
| description?: string; | ||
| } | ||
|
|
||
| let { | ||
| isOpen = $bindable(false), | ||
| onOpenSettings, | ||
| onGoBack, | ||
| onOpenChange, | ||
| title = "Camera Access Required", | ||
| description = "To continue, please grant camera permission in your device settings.", | ||
| }: CameraPermissionDialogProps = $props(); | ||
|
|
||
| function handleSwipe(value: boolean | undefined) { | ||
| if (value) { | ||
| isOpen = false; | ||
| onOpenChange?.(false); | ||
| } | ||
| } | ||
| </script> | ||
|
|
||
| <Drawer | ||
| isPaneOpen={isOpen} | ||
| handleSwipe={handleSwipe} | ||
| dismissible={false} | ||
| > | ||
| <div class="flex flex-col items-center text-center pb-4"> | ||
| <!-- Camera icon with slash --> | ||
| <svg | ||
| class="mx-auto mb-6" | ||
| width="80" | ||
| height="80" | ||
| viewBox="0 0 24 24" | ||
| fill="none" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path | ||
| d="M15.75 10.5l4.72-4.72a.75.75 0 011.28.53v11.38a.75.75 0 01-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25h-9A2.25 2.25 0 002.25 7.5v9a2.25 2.25 0 002.25 2.25z" | ||
| stroke="currentColor" | ||
| stroke-width="1.5" | ||
| stroke-linecap="round" | ||
| stroke-linejoin="round" | ||
| class="text-gray-700" | ||
| /> | ||
| <path | ||
| d="M3 3l18 18" | ||
| stroke="currentColor" | ||
| stroke-width="1.5" | ||
| stroke-linecap="round" | ||
| class="text-gray-700" | ||
| /> | ||
| </svg> | ||
|
|
||
| <h4 class="font-semibold text-xl mb-3 text-gray-900"> | ||
| {title} | ||
| </h4> | ||
|
|
||
| <p class="text-gray-600 text-sm mb-6 max-w-xs"> | ||
| {description} | ||
| </p> | ||
|
|
||
| <div class="flex flex-col gap-3 w-full"> | ||
| <ButtonAction | ||
| variant="solid" | ||
| callback={onOpenSettings} | ||
| class="w-full" | ||
| > | ||
| Open Settings | ||
| </ButtonAction> | ||
|
|
||
| {#if onGoBack} | ||
| <ButtonAction | ||
| variant="soft" | ||
| callback={onGoBack} | ||
| class="w-full" | ||
| > | ||
| Go Back | ||
| </ButtonAction> | ||
| {:else} | ||
| <!-- Spacer to maintain consistent bottom spacing --> | ||
| <div class="h-10"></div> | ||
| {/if} | ||
| </div> | ||
| </div> | ||
| </Drawer> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default as CameraPermissionDialog } from "./CameraPermissionDialog.svelte"; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,120 @@ | ||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||
| type PermissionState, | ||||||||||||||||||||||||||||||||||||||
| checkPermissions, | ||||||||||||||||||||||||||||||||||||||
| openAppSettings, | ||||||||||||||||||||||||||||||||||||||
| requestPermissions, | ||||||||||||||||||||||||||||||||||||||
| } from "@tauri-apps/plugin-barcode-scanner"; | ||||||||||||||||||||||||||||||||||||||
| import { type Writable, writable } from "svelte/store"; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export interface CameraPermissionState { | ||||||||||||||||||||||||||||||||||||||
| status: PermissionState | null; | ||||||||||||||||||||||||||||||||||||||
| isDenied: boolean; | ||||||||||||||||||||||||||||||||||||||
| isGranted: boolean; | ||||||||||||||||||||||||||||||||||||||
| isChecking: boolean; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| export interface CameraPermissionResult { | ||||||||||||||||||||||||||||||||||||||
| permissionState: Writable<CameraPermissionState>; | ||||||||||||||||||||||||||||||||||||||
| checkAndRequestPermission: () => Promise<boolean>; | ||||||||||||||||||||||||||||||||||||||
| retryPermission: () => Promise<boolean>; | ||||||||||||||||||||||||||||||||||||||
| openSettings: () => Promise<void>; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Creates a camera permission manager that handles checking, requesting, | ||||||||||||||||||||||||||||||||||||||
| * and managing camera permissions using Tauri's barcode-scanner plugin. | ||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||
| * This can be used in both the scan page and onboarding flows where camera | ||||||||||||||||||||||||||||||||||||||
| * access is required. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| export function createCameraPermissionManager(): CameraPermissionResult { | ||||||||||||||||||||||||||||||||||||||
| const permissionState = writable<CameraPermissionState>({ | ||||||||||||||||||||||||||||||||||||||
| status: null, | ||||||||||||||||||||||||||||||||||||||
| isDenied: false, | ||||||||||||||||||||||||||||||||||||||
| isGranted: false, | ||||||||||||||||||||||||||||||||||||||
| isChecking: false, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Check current permission status and request if needed. | ||||||||||||||||||||||||||||||||||||||
| * Returns true if permission is granted, false otherwise. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| async function checkAndRequestPermission(): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||
| permissionState.update((state) => ({ | ||||||||||||||||||||||||||||||||||||||
| ...state, | ||||||||||||||||||||||||||||||||||||||
| isChecking: true, | ||||||||||||||||||||||||||||||||||||||
| isDenied: false, | ||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let permissions: PermissionState | null = null; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| permissions = await checkPermissions(); | ||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||
| permissions = null; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // If permission is prompt or denied, request it | ||||||||||||||||||||||||||||||||||||||
| if (permissions === "prompt" || permissions === "denied") { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| permissions = await requestPermissions(); | ||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||
| permissions = null; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| const isGranted = permissions === "granted"; | ||||||||||||||||||||||||||||||||||||||
| const isDenied = !isGranted; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| permissionState.set({ | ||||||||||||||||||||||||||||||||||||||
| status: permissions, | ||||||||||||||||||||||||||||||||||||||
| isDenied, | ||||||||||||||||||||||||||||||||||||||
| isGranted, | ||||||||||||||||||||||||||||||||||||||
| isChecking: false, | ||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+66
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n infrastructure/eid-wallet/src/lib/utils/cameraPermission.tsRepository: MetaState-Prototype-Project/prototype Length of output: 4288 🏁 Script executed: rg "isDenied" infrastructure/eid-wallet/src --type ts --type tsx -B 2 -A 2Repository: MetaState-Prototype-Project/prototype Length of output: 167 🏁 Script executed: rg "isDenied" infrastructure/eid-wallet/src --max-count 20Repository: MetaState-Prototype-Project/prototype Length of output: 600 🏁 Script executed: rg "permissionState" infrastructure/eid-wallet/src --max-count 30Repository: MetaState-Prototype-Project/prototype Length of output: 914 🏁 Script executed: cat -n infrastructure/eid-wallet/src/routes/\(auth\)/verify/steps/selfie.svelteRepository: MetaState-Prototype-Project/prototype Length of output: 5982 🏁 Script executed: cat -n infrastructure/eid-wallet/src/routes/\(auth\)/verify/steps/passport.svelteRepository: MetaState-Prototype-Project/prototype Length of output: 20862 Fix The computed Suggested fix-const isGranted = permissions === "granted";
-const isDenied = !isGranted;
+const isGranted = permissions === "granted";
+const isDenied = permissions === "denied";📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (isDenied) { | ||||||||||||||||||||||||||||||||||||||
| console.warn("Camera permission denied or unavailable"); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return isGranted; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Retry permission request. If permission was previously denied (not just prompt), | ||||||||||||||||||||||||||||||||||||||
| * this will open app settings since the OS won't show the dialog again. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| async function retryPermission(): Promise<boolean> { | ||||||||||||||||||||||||||||||||||||||
| let permissions: PermissionState | null = null; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| permissions = await checkPermissions(); | ||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||
| permissions = null; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // If permission is denied (not just prompt), open app settings | ||||||||||||||||||||||||||||||||||||||
| // because the OS won't show the permission dialog again | ||||||||||||||||||||||||||||||||||||||
| if (permissions === "denied") { | ||||||||||||||||||||||||||||||||||||||
| await openAppSettings(); | ||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Otherwise, attempt to request permissions again | ||||||||||||||||||||||||||||||||||||||
| return checkAndRequestPermission(); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Open the app's settings page in system settings. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| async function openSettings(): Promise<void> { | ||||||||||||||||||||||||||||||||||||||
| await openAppSettings(); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||
| permissionState, | ||||||||||||||||||||||||||||||||||||||
| checkAndRequestPermission, | ||||||||||||||||||||||||||||||||||||||
| retryPermission, | ||||||||||||||||||||||||||||||||||||||
| openSettings, | ||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
rg -n "handleSwipe|dismissible" -C3 infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelteRepository: MetaState-Prototype-Project/prototype
Length of output: 1076
🏁 Script executed:
rg -n "CupertinoPane|events:" -A5 infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelteRepository: MetaState-Prototype-Project/prototype
Length of output: 167
🏁 Script executed:
Repository: MetaState-Prototype-Project/prototype
Length of output: 648
🌐 Web query:
CupertinoPane library bottomClose dismissible behavior swipe events💡 Result:
Short summary — how bottomClose / dismissible and swipe events work in CupertinoPane:
Events you can listen to for swipe/close flows:
Quick usage patterns:
Sources: CupertinoPane docs (settings and options) and Events / Public Methods docs. [1][2]
References
[1] Cupertino Pane settings (bottomClose, fastSwipeClose, touchAngle, etc.).
[2] Cupertino Pane Events & Public Methods (onDragStart/onDrag/onDragEnd, onWillDismiss/onDidDismiss, preventDismiss).
🏁 Script executed:
rg -n "handleDrawerSwipe|addEventListener|on:" infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelteRepository: MetaState-Prototype-Project/prototype
Length of output: 206
🏁 Script executed:
sed -n '30,45p' infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelteRepository: MetaState-Prototype-Project/prototype
Length of output: 448
🏁 Script executed:
rg -n "use:swipe" infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelteRepository: MetaState-Prototype-Project/prototype
Length of output: 97
🏁 Script executed:
sed -n '88,98p' infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelteRepository: MetaState-Prototype-Project/prototype
Length of output: 255
🏁 Script executed:
cat -n infrastructure/eid-wallet/src/lib/ui/CameraPermissionDialog/CameraPermissionDialog.svelte | head -50Repository: MetaState-Prototype-Project/prototype
Length of output: 1896
Remove
handleSwipeor guard it based ondismissibleandonOpenChangeavailability.The
use:swipedirective in the Drawer component is unconditional—swipe events fire even whendismissible={false}. In CameraPermissionDialog, this means a user can swipe to setisOpen = falselocally while the parent remains unaware (ifonOpenChangeis undefined), causing state desynchronization and bypassing the permission gate.Suggested fix
<Drawer isPaneOpen={isOpen} - handleSwipe={handleSwipe} + handleSwipe={onOpenChange ? handleSwipe : undefined} dismissible={false} >Alternatively, only wire
handleSwipewhen the dialog is actually dismissible:<Drawer isPaneOpen={isOpen} - handleSwipe={handleSwipe} - dismissible={false} + handleSwipe={onOpenChange ? handleSwipe : undefined} + dismissible={Boolean(onOpenChange)} >📝 Committable suggestion
🤖 Prompt for AI Agents