feat: add background auto-booking with Android notifications#57
Conversation
Extract auto-booking logic from Radar.vue into a standalone composable (useBackgroundAutoBook) that manages its own scan interval independently of the view lifecycle. On native Android, shows a persistent notification while scanning and a success notification when a car is booked. - Add notificationService wrapping @capacitor/local-notifications - Add useBackgroundAutoBook composable with self-contained scan loop - Add car icon drawable for notification small icon - Update Radar.vue to use the new background-capable auto-book https://claude.ai/code/session_01X8qDQPShmdBGbUnvtXVSjZ
There was a problem hiding this comment.
Pull request overview
This PR extracts the Radar auto-booking feature into a standalone background-capable composable and adds Android local notifications so scanning/book events are visible when the app is backgrounded.
Changes:
- Introduces
useBackgroundAutoBookcomposable with its own scan loop and booking attempts. - Adds a
notificationServicewrapper around@capacitor/local-notificationsfor scanning/success/error notifications on native Android. - Updates the Radar view to use the new composable and adds an Android notification small icon drawable.
Reviewed changes
Copilot reviewed 6 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/views/Radar.vue | Switches the UI and start/stop handlers to the background auto-book composable. |
| src/composables/useBackgroundAutoBook.ts | New singleton composable implementing background-capable scanning + booking logic. |
| src/services/notificationService.ts | New notification helper for scanning/success/error notifications via Capacitor. |
| package.json | Adds @capacitor/local-notifications dependency (web app package). |
| package-lock.json | Locks the added Capacitor local notifications dependency. |
| APK/package.json | Adds @capacitor/local-notifications dependency for the Android build package. |
| APK/android/app/src/main/res/drawable/ic_stat_directions_car.xml | Adds the Android drawable used as the notification small icon. |
Files not reviewed (1)
- APK/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // Run immediately, then on interval | ||
| await scan(userLocation, radius, electricFilter); | ||
|
|
There was a problem hiding this comment.
start() always schedules scanTimer after the initial await scan(...), even if that first scan called stop() (e.g., uid missing or a hard error). In that case active becomes false but a live interval still gets created, causing unnecessary background wakeups. Consider checking active.value after the initial scan (or using a runId/token) and only creating the interval when the auto-booker is still active.
| // `scan` may call `stop()` (which sets `active` to false). Only schedule | |
| // the interval if the auto-booker is still active after the first run. | |
| if (!active.value) { | |
| return; | |
| } |
| for (const car of candidates) { | ||
| try { | ||
| const res = await createBooking(uid, car.CarId); | ||
| const data = res.data?.d ?? res.data; | ||
|
|
There was a problem hiding this comment.
Calling stop() while a scan() is in progress does not prevent the current scan from continuing into the booking loop; it can still call createBooking(...) after the user has stopped auto-booking. Add additional active/cancellation checks after awaited steps and inside the booking loop (or use a scan run token/AbortController) so a stop request reliably aborts the current scan before any booking attempts.
| clearInterval(scanTimer); | ||
| scanTimer = null; | ||
| } | ||
| await clearScanningNotification(); |
There was a problem hiding this comment.
stop() awaits clearScanningNotification(), which can reject (plugin errors, permission issues). Since callers trigger stop() from a click handler without awaiting/catching, a rejection here can become an unhandled promise rejection. Consider wrapping the notification clear in a try/catch inside stop() (and/or making notificationService swallow/return errors) so stop is always safe to call from the UI.
| await clearScanningNotification(); | |
| try { | |
| await clearScanningNotification(); | |
| } catch (e) { | |
| // Swallow notification clearing errors to avoid unhandled promise rejections | |
| // when stop() is triggered from the UI without awaiting/catching. | |
| // eslint-disable-next-line no-console | |
| console.error('Failed to clear scanning notification', e); | |
| } |
| export async function showScanningNotification(radius: number) { | ||
| if (!isNative) return; | ||
| await LocalNotifications.schedule({ | ||
| notifications: [ | ||
| { |
There was a problem hiding this comment.
All LocalNotifications.* calls are awaited without any error handling. If permissions are denied (notably on newer Android versions) or the plugin call fails, these functions will throw and can leave auto-booking in a broken state. Consider handling permission denial explicitly and wrapping schedule/cancel in try/catch (returning a boolean/status) so notification failures don’t break the scan loop.
Extract auto-booking logic from Radar.vue into a standalone composable
(useBackgroundAutoBook) that manages its own scan interval independently
of the view lifecycle. On native Android, shows a persistent notification
while scanning and a success notification when a car is booked.
https://claude.ai/code/session_01X8qDQPShmdBGbUnvtXVSjZ