Browse Source
- Add notification methods to PlatformService interface - Implement notification support in CapacitorPlatformService with plugin integration - Add stub implementations in WebPlatformService and ElectronPlatformService - Add nativeNotificationTime, nativeNotificationTitle, and nativeNotificationMessage fields to Settings interface - Create DailyNotificationSection component for AccountViewView integration - Add Android manifest permissions (POST_NOTIFICATIONS, SCHEDULE_EXACT_ALARM, RECEIVE_BOOT_COMPLETED) - Register daily-notification-plugin in capacitor.plugins.json - Integrate DailyNotificationSection into AccountViewView Features: - Platform capability detection (hides on unsupported platforms) - Permission request flow with fallback to settings - Schedule/cancel notifications - Time editing with HTML5 time input - Settings persistence - Plugin state synchronization on app load NOTE: Currently storing notification schedule in SQLite database, but plugin was designed to store schedule internally. Consider migrating to plugin's internal storage to avoid database initialization issues.pull/214/head
9 changed files with 1346 additions and 0 deletions
@ -0,0 +1,676 @@ |
|||||
|
<template> |
||||
|
<section |
||||
|
v-if="notificationsSupported" |
||||
|
id="sectionDailyNotifications" |
||||
|
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8" |
||||
|
aria-labelledby="dailyNotificationsHeading" |
||||
|
> |
||||
|
<h2 id="dailyNotificationsHeading" class="mb-2 font-bold"> |
||||
|
Daily Notifications |
||||
|
<button |
||||
|
class="text-slate-400 fa-fw cursor-pointer" |
||||
|
aria-label="Learn more about native notifications" |
||||
|
@click.stop="showNativeNotificationInfo" |
||||
|
> |
||||
|
<font-awesome icon="circle-question" aria-hidden="true" /> |
||||
|
</button> |
||||
|
</h2> |
||||
|
|
||||
|
<div class="flex items-center justify-between"> |
||||
|
<div>Daily Notification</div> |
||||
|
<!-- Toggle switch --> |
||||
|
<div |
||||
|
class="relative ml-2 cursor-pointer" |
||||
|
role="switch" |
||||
|
:aria-checked="nativeNotificationEnabled" |
||||
|
:aria-label=" |
||||
|
nativeNotificationEnabled |
||||
|
? 'Disable daily notifications' |
||||
|
: 'Enable daily notifications' |
||||
|
" |
||||
|
tabindex="0" |
||||
|
@click="toggleNativeNotification" |
||||
|
> |
||||
|
<!-- input --> |
||||
|
<input |
||||
|
:checked="nativeNotificationEnabled" |
||||
|
type="checkbox" |
||||
|
class="sr-only" |
||||
|
tabindex="-1" |
||||
|
readonly |
||||
|
/> |
||||
|
<!-- line --> |
||||
|
<div |
||||
|
class="block bg-slate-500 w-14 h-8 rounded-full transition" |
||||
|
:class="{ |
||||
|
'bg-blue-600': nativeNotificationEnabled, |
||||
|
}" |
||||
|
></div> |
||||
|
<!-- dot --> |
||||
|
<div |
||||
|
class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition" |
||||
|
:class="{ |
||||
|
'left-7 bg-white': nativeNotificationEnabled, |
||||
|
}" |
||||
|
></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Show "Open Settings" button when permissions are denied --> |
||||
|
<div |
||||
|
v-if=" |
||||
|
notificationsSupported && |
||||
|
notificationStatus && |
||||
|
notificationStatus.permissions.notifications === 'denied' |
||||
|
" |
||||
|
class="mt-2" |
||||
|
> |
||||
|
<button |
||||
|
class="w-full px-3 py-2 bg-blue-600 text-white rounded text-sm font-medium" |
||||
|
@click="openNotificationSettings" |
||||
|
> |
||||
|
Open Settings |
||||
|
</button> |
||||
|
<p class="text-xs text-slate-500 mt-1 text-center"> |
||||
|
Enable notifications in Settings > App info > Notifications |
||||
|
</p> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Time input section - show when enabled OR when no time is set --> |
||||
|
<div |
||||
|
v-if="nativeNotificationEnabled || !nativeNotificationTimeStorage" |
||||
|
class="mt-2" |
||||
|
> |
||||
|
<div |
||||
|
v-if="nativeNotificationEnabled" |
||||
|
class="flex items-center justify-between mb-2" |
||||
|
> |
||||
|
<span |
||||
|
>Scheduled for: |
||||
|
<span v-if="nativeNotificationTime">{{ |
||||
|
nativeNotificationTime |
||||
|
}}</span> |
||||
|
<span v-else class="text-slate-500">Not set</span></span |
||||
|
> |
||||
|
<button |
||||
|
class="text-blue-500 text-sm" |
||||
|
@click="editNativeNotificationTime" |
||||
|
> |
||||
|
{{ showTimeEdit ? "Cancel" : "Edit Time" }} |
||||
|
</button> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Time input (shown when editing or when no time is set) --> |
||||
|
<div v-if="showTimeEdit || !nativeNotificationTimeStorage" class="mt-2"> |
||||
|
<label class="block text-sm text-slate-600 mb-1"> |
||||
|
Notification Time |
||||
|
</label> |
||||
|
<div class="flex items-center gap-2"> |
||||
|
<input |
||||
|
v-model="nativeNotificationTimeStorage" |
||||
|
type="time" |
||||
|
class="rounded border border-slate-400 px-2 py-2" |
||||
|
@change="onTimeChange" |
||||
|
/> |
||||
|
<button |
||||
|
v-if="showTimeEdit || nativeNotificationTimeStorage" |
||||
|
class="px-3 py-2 bg-blue-600 text-white rounded" |
||||
|
@click="saveTimeChange" |
||||
|
> |
||||
|
Save |
||||
|
</button> |
||||
|
</div> |
||||
|
<p |
||||
|
v-if="!nativeNotificationTimeStorage" |
||||
|
class="text-xs text-slate-500 mt-1" |
||||
|
> |
||||
|
Set a time before enabling notifications |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- Loading state --> |
||||
|
<div v-if="loading" class="mt-2 text-sm text-slate-500">Loading...</div> |
||||
|
</section> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
/** |
||||
|
* DailyNotificationSection Component |
||||
|
* |
||||
|
* A self-contained component for managing daily notification scheduling |
||||
|
* in AccountViewView. This component handles platform detection, permission |
||||
|
* requests, scheduling, and state management for daily notifications. |
||||
|
* |
||||
|
* Features: |
||||
|
* - Platform capability detection (hides on unsupported platforms) |
||||
|
* - Permission request flow |
||||
|
* - Schedule/cancel notifications |
||||
|
* - Time editing with HTML5 time input |
||||
|
* - Settings persistence |
||||
|
* - Plugin state synchronization |
||||
|
* |
||||
|
* @author Generated for TimeSafari Daily Notification Integration |
||||
|
* @component |
||||
|
*/ |
||||
|
|
||||
|
import { Component, Vue } from "vue-facing-decorator"; |
||||
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin"; |
||||
|
import { logger } from "@/utils/logger"; |
||||
|
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; |
||||
|
import type { |
||||
|
NotificationStatus, |
||||
|
PermissionStatus, |
||||
|
} from "@/services/PlatformService"; |
||||
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify"; |
||||
|
import type { NotificationIface } from "@/constants/app"; |
||||
|
|
||||
|
/** |
||||
|
* Convert 24-hour time format ("09:00") to 12-hour display format ("9:00 AM") |
||||
|
*/ |
||||
|
function formatTimeForDisplay(time24: string): string { |
||||
|
if (!time24) return ""; |
||||
|
const [hours, minutes] = time24.split(":"); |
||||
|
const hourNum = parseInt(hours); |
||||
|
const isPM = hourNum >= 12; |
||||
|
const displayHour = |
||||
|
hourNum === 0 ? 12 : hourNum > 12 ? hourNum - 12 : hourNum; |
||||
|
return `${displayHour}:${minutes} ${isPM ? "PM" : "AM"}`; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
name: "DailyNotificationSection", |
||||
|
mixins: [PlatformServiceMixin], |
||||
|
}) |
||||
|
export default class DailyNotificationSection extends Vue { |
||||
|
$notify!: (notification: NotificationIface, timeout?: number) => void; |
||||
|
|
||||
|
// Component state |
||||
|
notificationsSupported: boolean = false; |
||||
|
nativeNotificationEnabled: boolean = false; |
||||
|
nativeNotificationTime: string = ""; // Display format: "9:00 AM" |
||||
|
nativeNotificationTimeStorage: string = ""; // Plugin format: "09:00" |
||||
|
nativeNotificationTitle: string = "Daily Update"; |
||||
|
nativeNotificationMessage: string = "Your daily notification is ready!"; |
||||
|
showTimeEdit: boolean = false; |
||||
|
loading: boolean = false; |
||||
|
notificationStatus: NotificationStatus | null = null; |
||||
|
|
||||
|
// Notify helpers |
||||
|
private notify!: ReturnType<typeof createNotifyHelpers>; |
||||
|
|
||||
|
async created(): Promise<void> { |
||||
|
this.notify = createNotifyHelpers(this.$notify); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Initialize component state on mount |
||||
|
* Checks platform support and syncs with plugin state |
||||
|
*/ |
||||
|
async mounted(): Promise<void> { |
||||
|
await this.initializeState(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Initialize component state |
||||
|
* Checks platform support and syncs with plugin state |
||||
|
*/ |
||||
|
async initializeState(): Promise<void> { |
||||
|
try { |
||||
|
this.loading = true; |
||||
|
const platformService = PlatformServiceFactory.getInstance(); |
||||
|
|
||||
|
// Check if notifications are supported on this platform |
||||
|
// This also verifies plugin availability (returns null if plugin unavailable) |
||||
|
const status = await platformService.getDailyNotificationStatus(); |
||||
|
if (status === null) { |
||||
|
// Notifications not supported or plugin unavailable - don't initialize |
||||
|
this.notificationsSupported = false; |
||||
|
logger.warn( |
||||
|
"[DailyNotificationSection] Notifications not supported or plugin unavailable", |
||||
|
); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.notificationsSupported = true; |
||||
|
this.notificationStatus = status; |
||||
|
|
||||
|
// CRITICAL: Sync with plugin state first (source of truth) |
||||
|
// Plugin may have an existing schedule even if settings don't |
||||
|
if (status.isScheduled && status.scheduledTime) { |
||||
|
// Plugin has a scheduled notification - sync UI to match |
||||
|
this.nativeNotificationEnabled = true; |
||||
|
this.nativeNotificationTimeStorage = status.scheduledTime; |
||||
|
this.nativeNotificationTime = formatTimeForDisplay( |
||||
|
status.scheduledTime, |
||||
|
); |
||||
|
|
||||
|
// Also sync settings to match plugin state |
||||
|
const settings = await this.$accountSettings(); |
||||
|
if (settings.nativeNotificationTime !== status.scheduledTime) { |
||||
|
await this.$saveSettings({ |
||||
|
nativeNotificationTime: status.scheduledTime, |
||||
|
nativeNotificationTitle: |
||||
|
settings.nativeNotificationTitle || this.nativeNotificationTitle, |
||||
|
nativeNotificationMessage: |
||||
|
settings.nativeNotificationMessage || |
||||
|
this.nativeNotificationMessage, |
||||
|
}); |
||||
|
} |
||||
|
} else { |
||||
|
// No plugin schedule - check settings for user preference |
||||
|
const settings = await this.$accountSettings(); |
||||
|
const nativeNotificationTime = settings.nativeNotificationTime || ""; |
||||
|
this.nativeNotificationEnabled = !!nativeNotificationTime; |
||||
|
this.nativeNotificationTimeStorage = nativeNotificationTime; |
||||
|
|
||||
|
if (nativeNotificationTime) { |
||||
|
this.nativeNotificationTime = formatTimeForDisplay( |
||||
|
nativeNotificationTime, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} catch (error) { |
||||
|
logger.error("[DailyNotificationSection] Failed to initialize:", error); |
||||
|
this.notificationsSupported = false; |
||||
|
} finally { |
||||
|
this.loading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Toggle notification on/off |
||||
|
*/ |
||||
|
async toggleNativeNotification(): Promise<void> { |
||||
|
// Prevent multiple simultaneous toggles |
||||
|
if (this.loading) { |
||||
|
logger.warn( |
||||
|
"[DailyNotificationSection] Toggle ignored - operation in progress", |
||||
|
); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
logger.info( |
||||
|
`[DailyNotificationSection] Toggling notification: ${this.nativeNotificationEnabled} -> ${!this.nativeNotificationEnabled}`, |
||||
|
); |
||||
|
|
||||
|
if (this.nativeNotificationEnabled) { |
||||
|
await this.disableNativeNotification(); |
||||
|
} else { |
||||
|
await this.enableNativeNotification(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Enable daily notification |
||||
|
*/ |
||||
|
async enableNativeNotification(): Promise<void> { |
||||
|
try { |
||||
|
this.loading = true; |
||||
|
const platformService = PlatformServiceFactory.getInstance(); |
||||
|
|
||||
|
// Check if we have a time set |
||||
|
if (!this.nativeNotificationTimeStorage) { |
||||
|
this.notify.error( |
||||
|
"Please set a notification time first", |
||||
|
TIMEOUTS.SHORT, |
||||
|
); |
||||
|
this.loading = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Check permissions first - this also verifies plugin availability |
||||
|
let permissions: PermissionStatus | null; |
||||
|
try { |
||||
|
permissions = await platformService.checkNotificationPermissions(); |
||||
|
logger.info( |
||||
|
`[DailyNotificationSection] Permission check result:`, |
||||
|
permissions, |
||||
|
); |
||||
|
} catch (error) { |
||||
|
// Plugin may not be available or there's an error |
||||
|
logger.error( |
||||
|
"[DailyNotificationSection] Failed to check permissions (plugin may be unavailable):", |
||||
|
error, |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Unable to check notification permissions. The notification plugin may not be installed.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (permissions === null) { |
||||
|
// Platform doesn't support notifications or plugin unavailable |
||||
|
logger.warn( |
||||
|
"[DailyNotificationSection] Notifications not supported or plugin unavailable", |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Notifications are not supported on this platform or the plugin is not installed.", |
||||
|
TIMEOUTS.SHORT, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
logger.info( |
||||
|
`[DailyNotificationSection] Permission state: ${permissions.notifications}`, |
||||
|
); |
||||
|
|
||||
|
// If permissions are explicitly denied, don't try to request again |
||||
|
// (this prevents the plugin crash when handling denied permissions) |
||||
|
// Android won't show the dialog again if permissions are permanently denied |
||||
|
if (permissions.notifications === "denied") { |
||||
|
logger.warn( |
||||
|
"[DailyNotificationSection] Permissions already denied, directing user to settings", |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Notification permissions were denied. Tap 'Open Settings' to enable them.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Only request if permissions are in "prompt" state (not denied, not granted) |
||||
|
// This ensures we only call requestPermissions when Android will actually show a dialog |
||||
|
if (permissions.notifications === "prompt") { |
||||
|
logger.info( |
||||
|
"[DailyNotificationSection] Permission state is 'prompt', requesting permissions...", |
||||
|
); |
||||
|
try { |
||||
|
const result = await platformService.requestNotificationPermissions(); |
||||
|
logger.info( |
||||
|
`[DailyNotificationSection] Permission request result:`, |
||||
|
result, |
||||
|
); |
||||
|
if (result === null) { |
||||
|
// Plugin unavailable or request failed |
||||
|
logger.error( |
||||
|
"[DailyNotificationSection] Permission request returned null", |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Unable to request notification permissions. The plugin may not be available.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
return; |
||||
|
} |
||||
|
if (!result.notifications) { |
||||
|
// Permission request was denied |
||||
|
logger.warn( |
||||
|
"[DailyNotificationSection] Permission request denied by user", |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Notification permissions are required. Tap 'Open Settings' to enable them.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
return; |
||||
|
} |
||||
|
// Permissions granted - continue |
||||
|
logger.info( |
||||
|
"[DailyNotificationSection] Permissions granted successfully", |
||||
|
); |
||||
|
} catch (error) { |
||||
|
// Handle permission request errors (including plugin crashes) |
||||
|
logger.error( |
||||
|
"[DailyNotificationSection] Permission request failed:", |
||||
|
error, |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Unable to request notification permissions. Tap 'Open Settings' to enable them.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
return; |
||||
|
} |
||||
|
} else if (permissions.notifications !== "granted") { |
||||
|
// Unexpected state - shouldn't happen, but handle gracefully |
||||
|
logger.warn( |
||||
|
`[DailyNotificationSection] Unexpected permission state: ${permissions.notifications}`, |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Unable to determine notification permission status. Tap 'Open Settings' to check.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
return; |
||||
|
} else { |
||||
|
logger.info("[DailyNotificationSection] Permissions already granted"); |
||||
|
} |
||||
|
|
||||
|
// Permissions are granted - continue with scheduling |
||||
|
|
||||
|
// Schedule notification via PlatformService |
||||
|
await platformService.scheduleDailyNotification({ |
||||
|
time: this.nativeNotificationTimeStorage, // "09:00" in local time |
||||
|
title: this.nativeNotificationTitle, |
||||
|
body: this.nativeNotificationMessage, |
||||
|
sound: true, |
||||
|
priority: "high", |
||||
|
}); |
||||
|
|
||||
|
// Save to settings |
||||
|
await this.$saveSettings({ |
||||
|
nativeNotificationTime: this.nativeNotificationTimeStorage, |
||||
|
nativeNotificationTitle: this.nativeNotificationTitle, |
||||
|
nativeNotificationMessage: this.nativeNotificationMessage, |
||||
|
}); |
||||
|
|
||||
|
// Update UI state |
||||
|
this.nativeNotificationEnabled = true; |
||||
|
|
||||
|
this.notify.success( |
||||
|
"Daily notification scheduled successfully", |
||||
|
TIMEOUTS.SHORT, |
||||
|
); |
||||
|
} catch (error) { |
||||
|
logger.error( |
||||
|
"[DailyNotificationSection] Failed to enable notification:", |
||||
|
error, |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Failed to schedule notification. Please try again.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
} finally { |
||||
|
this.loading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Disable daily notification |
||||
|
*/ |
||||
|
async disableNativeNotification(): Promise<void> { |
||||
|
try { |
||||
|
this.loading = true; |
||||
|
const platformService = PlatformServiceFactory.getInstance(); |
||||
|
|
||||
|
// Cancel notification via PlatformService |
||||
|
await platformService.cancelDailyNotification(); |
||||
|
|
||||
|
// Clear settings |
||||
|
await this.$saveSettings({ |
||||
|
nativeNotificationTime: "", |
||||
|
nativeNotificationTitle: "", |
||||
|
nativeNotificationMessage: "", |
||||
|
}); |
||||
|
|
||||
|
// Update UI state |
||||
|
this.nativeNotificationEnabled = false; |
||||
|
this.nativeNotificationTime = ""; |
||||
|
this.nativeNotificationTimeStorage = ""; |
||||
|
this.showTimeEdit = false; |
||||
|
|
||||
|
this.notify.success("Daily notification disabled", TIMEOUTS.SHORT); |
||||
|
} catch (error) { |
||||
|
logger.error( |
||||
|
"[DailyNotificationSection] Failed to disable notification:", |
||||
|
error, |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Failed to disable notification. Please try again.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
} finally { |
||||
|
this.loading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Show/hide time edit input |
||||
|
*/ |
||||
|
editNativeNotificationTime(): void { |
||||
|
this.showTimeEdit = !this.showTimeEdit; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Handle time input change |
||||
|
*/ |
||||
|
onTimeChange(): void { |
||||
|
// Time is already in nativeNotificationTimeStorage via v-model |
||||
|
// Just update display format |
||||
|
if (this.nativeNotificationTimeStorage) { |
||||
|
this.nativeNotificationTime = formatTimeForDisplay( |
||||
|
this.nativeNotificationTimeStorage, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Save time change and update notification schedule |
||||
|
*/ |
||||
|
async saveTimeChange(): Promise<void> { |
||||
|
if (!this.nativeNotificationTimeStorage) { |
||||
|
this.notify.error("Please select a time", TIMEOUTS.SHORT); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Update display format |
||||
|
this.nativeNotificationTime = formatTimeForDisplay( |
||||
|
this.nativeNotificationTimeStorage, |
||||
|
); |
||||
|
|
||||
|
// If notification is enabled, update the schedule |
||||
|
if (this.nativeNotificationEnabled) { |
||||
|
await this.updateNotificationTime(this.nativeNotificationTimeStorage); |
||||
|
} else { |
||||
|
// Just save the time preference |
||||
|
await this.$saveSettings({ |
||||
|
nativeNotificationTime: this.nativeNotificationTimeStorage, |
||||
|
}); |
||||
|
this.showTimeEdit = false; |
||||
|
this.notify.success("Notification time saved", TIMEOUTS.SHORT); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Update notification time |
||||
|
* If notification is enabled, immediately updates the schedule |
||||
|
*/ |
||||
|
async updateNotificationTime(newTime: string): Promise<void> { |
||||
|
// newTime is in "HH:mm" format from HTML5 time input |
||||
|
if (!this.nativeNotificationEnabled) { |
||||
|
// If notification is disabled, just save the time preference |
||||
|
this.nativeNotificationTimeStorage = newTime; |
||||
|
this.nativeNotificationTime = formatTimeForDisplay(newTime); |
||||
|
await this.$saveSettings({ |
||||
|
nativeNotificationTime: newTime, |
||||
|
}); |
||||
|
this.showTimeEdit = false; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Notification is enabled - update the schedule |
||||
|
try { |
||||
|
this.loading = true; |
||||
|
const platformService = PlatformServiceFactory.getInstance(); |
||||
|
|
||||
|
// 1. Cancel existing notification |
||||
|
await platformService.cancelDailyNotification(); |
||||
|
|
||||
|
// 2. Schedule with new time |
||||
|
await platformService.scheduleDailyNotification({ |
||||
|
time: newTime, // "09:00" in local time |
||||
|
title: this.nativeNotificationTitle, |
||||
|
body: this.nativeNotificationMessage, |
||||
|
sound: true, |
||||
|
priority: "high", |
||||
|
}); |
||||
|
|
||||
|
// 3. Update local state |
||||
|
this.nativeNotificationTimeStorage = newTime; |
||||
|
this.nativeNotificationTime = formatTimeForDisplay(newTime); |
||||
|
|
||||
|
// 4. Save to settings |
||||
|
await this.$saveSettings({ |
||||
|
nativeNotificationTime: newTime, |
||||
|
}); |
||||
|
|
||||
|
this.notify.success( |
||||
|
"Notification time updated successfully", |
||||
|
TIMEOUTS.SHORT, |
||||
|
); |
||||
|
this.showTimeEdit = false; |
||||
|
} catch (error) { |
||||
|
logger.error( |
||||
|
"[DailyNotificationSection] Failed to update notification time:", |
||||
|
error, |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Failed to update notification time. Please try again.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
} finally { |
||||
|
this.loading = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Show info dialog about native notifications |
||||
|
*/ |
||||
|
showNativeNotificationInfo(): void { |
||||
|
// TODO: Implement info dialog or navigate to help page |
||||
|
this.notify.info( |
||||
|
"Daily notifications use your device's native notification system. They work even when the app is closed.", |
||||
|
TIMEOUTS.STANDARD, |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Open app notification settings |
||||
|
*/ |
||||
|
async openNotificationSettings(): Promise<void> { |
||||
|
try { |
||||
|
const platformService = PlatformServiceFactory.getInstance(); |
||||
|
const result = await platformService.openAppNotificationSettings(); |
||||
|
if (result === null) { |
||||
|
this.notify.error( |
||||
|
"Unable to open settings. Please go to Settings > Apps > TimeSafari > Notifications manually.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
} else { |
||||
|
this.notify.success("Opening notification settings...", TIMEOUTS.SHORT); |
||||
|
} |
||||
|
} catch (error) { |
||||
|
logger.error( |
||||
|
"[DailyNotificationSection] Failed to open notification settings:", |
||||
|
error, |
||||
|
); |
||||
|
this.notify.error( |
||||
|
"Unable to open settings. Please go to Settings > Apps > TimeSafari > Notifications manually.", |
||||
|
TIMEOUTS.LONG, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.dot { |
||||
|
transition: left 0.2s ease; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue