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