15 KiB
Daily Notification Plugin - AccountViewView Integration Strategy
Author: Matthew Raymer
Date: 2025-11-03
Status: 🎯 PLANNING - UI integration strategy
Feature: Daily Notification Scheduling in Account Settings
Overview
This document outlines the strategy for integrating daily notification scheduling into AccountViewView.vue, allowing users to configure notification times directly from their account settings.
Current State Analysis
Account Settings Context
Location: AccountViewView.vue - Settings view for user account configuration
Integration Approach: Add new "Daily Notifications" section for scheduling native daily notifications using PlatformService.
Integration Strategy
Approach: Create a separate "Daily Notifications" section
This approach adds a dedicated "Daily Notifications" section that checks PlatformService capabilities. On Capacitor platforms, it provides full functionality. On other platforms, the UI is hidden when PlatformService returns null for notification methods.
Key Benefits:
- Uses PlatformService interface pattern (consistent with camera, filesystem)
- Platform-specific features properly isolated
- Can use native time picker (better UX on mobile)
- Future-proof: Easy to extend with additional notification features
- Graceful degradation on unsupported platforms
Implementation:
<!-- New Daily Notifications section -->
<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="question-circle" 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"
@click="toggleNativeNotification()"
>
<!-- Custom toggle UI -->
</div>
</div>
<!-- Show current time when enabled -->
<div v-if="nativeNotificationEnabled" class="mt-2">
<div class="flex items-center justify-between">
<span>Scheduled for: {{ nativeNotificationTime }}</span>
<button
class="text-blue-500 text-sm"
@click="editNativeNotificationTime()"
>
Edit Time
</button>
</div>
</div>
</section>
Approach Rationale
Decision Date: 2025-11-03
Status: ✅ ACCEPTED - Will proceed with separate native notification section
- Clear Platform Distinction: Users understand this is for mobile apps
- No Conflicts: Doesn't interfere with disabled web notifications
- Better UX: Can use native time picker on mobile
- Future-Proof: Easy to extend with additional native notification features
Implementation Status
- Approach decision finalized
- Implementation begins (Phase 1)
UI Component Design
1. Platform Capability Detection
// In AccountViewView component
async checkNotificationSupport(): Promise<boolean> {
const platformService = PlatformServiceFactory.getInstance();
const status = await platformService.getDailyNotificationStatus();
return status !== null; // null means not supported
}
2. State Management
// Component properties
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!";
3. Time Input ✅ SELECTED: HTML5 Time Input
Decision: Use HTML5 <input type="time"> for native mobile experience
<input
type="time"
v-model="nativeNotificationTimeStorage"
class="rounded border border-slate-400 px-2 py-2"
/>
Benefits:
- Native mobile time picker UI on Capacitor platforms
- Simpler implementation (no custom time parsing needed)
- Automatic 24-hour format output (compatible with plugin)
- System handles locale-specific time formatting
- Better UX on mobile devices
Note: HTML5 time input provides time in "HH:mm" format (24-hour) which matches the plugin's expected format perfectly.
4. Time Format Conversion (Using System Time)
Key Principle: Use device's local system time - no timezone conversions needed. The plugin handles system time natively.
// Convert "09:00" (plugin storage format) to "9:00 AM" (display)
function formatTimeForDisplay(time24: string): string {
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'}`;
}
// HTML5 time input provides "HH:mm" in local time - use directly
// No UTC conversion needed - plugin handles local timezone
function getTimeFromInput(timeInput: string): string {
// timeInput is already in "HH:mm" format from <input type="time">
// This is in the user's local timezone - pass directly to plugin
return timeInput; // e.g., "09:00" in user's local time
}
Time Handling:
- PlatformService Integration: Uses device's local system time directly - NO UTC conversion needed. The plugin schedules notifications on the device itself, using the device's timezone.
Implementation Principles:
- HTML5
<input type="time">provides time in device's local timezone - Plugin receives time in "HH:mm" format and schedules relative to device's local time
- No manual timezone conversion or UTC calculations needed
- System automatically handles:
- Timezone changes
- Daylight saving time transitions
- Device timezone updates
- User sets "9:00 AM" in their local time → plugin schedules for 9:00 AM local time every day
Data Flow
1. Initialization
async initializeState() {
// ... existing initialization ...
const platformService = PlatformServiceFactory.getInstance();
// Check if notifications are supported on this platform
const status = await platformService.getDailyNotificationStatus();
if (status === null) {
// Notifications not supported - don't initialize
return;
}
// Load from settings
const nativeNotificationTime = settings.nativeNotificationTime || "";
this.nativeNotificationEnabled = !!nativeNotificationTime;
this.nativeNotificationTimeStorage = nativeNotificationTime;
if (nativeNotificationTime) {
this.nativeNotificationTime = formatTimeForDisplay(nativeNotificationTime);
}
// Update UI with current status
this.notificationStatus = status;
}
2. Enable Notification
async enableNativeNotification() {
try {
const platformService = PlatformServiceFactory.getInstance();
// 1. Request permissions if needed
const permissions = await platformService.checkNotificationPermissions();
if (permissions === null || permissions.notifications !== 'granted') {
const result = await platformService.requestNotificationPermissions();
if (result === null || !result.notifications) {
throw new Error("Notification permissions denied");
}
}
// 2. Schedule notification via PlatformService
// Time is in device's local system time (from HTML5 time input)
// PlatformService handles timezone and scheduling internally
await platformService.scheduleDailyNotification({
time: this.nativeNotificationTimeStorage, // "09:00" in local time
title: this.nativeNotificationTitle,
body: this.nativeNotificationMessage,
sound: true,
priority: 'high'
});
// 3. Save to settings
await this.$saveSettings({
nativeNotificationTime: this.nativeNotificationTimeStorage,
nativeNotificationTitle: this.nativeNotificationTitle,
nativeNotificationMessage: this.nativeNotificationMessage,
});
// 4. Update UI state
this.nativeNotificationEnabled = true;
this.notify.success("Daily notification scheduled successfully", TIMEOUTS.SHORT);
} catch (error) {
logger.error("Failed to enable notification:", error);
this.notify.error("Failed to schedule notification. Please try again.", TIMEOUTS.LONG);
}
}
### 3. Disable Notification
```typescript
async disableNativeNotification() {
try {
const platformService = PlatformServiceFactory.getInstance();
// 1. Cancel notification via PlatformService
await platformService.cancelDailyNotification();
// 2. Clear settings
await this.$saveSettings({
nativeNotificationTime: "",
nativeNotificationTitle: "",
nativeNotificationMessage: "",
});
// 3. Update UI state
this.nativeNotificationEnabled = false;
this.nativeNotificationTime = "";
this.nativeNotificationTimeStorage = "";
this.notify.success("Daily notification disabled", TIMEOUTS.SHORT);
} catch (error) {
logger.error("Failed to disable native notification:", error);
this.notify.error("Failed to disable notification. Please try again.", TIMEOUTS.LONG);
}
}
4. Edit Time
Approach: Use inline HTML5 time input for quick edits
async editNativeNotificationTime() {
// Show inline HTML5 time input for quick changes
// For complex editing (title, message), navigate to ScheduleView
this.showTimeEdit = true;
}
Implementation Note: HTML5 time input provides native mobile picker experience when shown inline, making it ideal for quick time adjustments in AccountViewView.
Settings Schema
New Settings Fields
// Add to Settings interface in src/db/tables/settings.ts
interface Settings {
// ... existing fields ...
// Native notification settings (Capacitor only)
nativeNotificationTime?: string; // "09:00" format (24-hour)
nativeNotificationTitle?: string; // Default: "Daily Update"
nativeNotificationMessage?: string; // Default message
}
Settings Persistence
- Store in
settingstable via$saveSettings() - Use same pattern as
notifyingNewActivityTime - Persist across app restarts
- Sync with plugin state on component mount
Plugin Integration
PlatformService Usage
import { PlatformServiceFactory } from '@/services/PlatformServiceFactory';
const platformService = PlatformServiceFactory.getInstance();
// Check if notifications are supported
const status = await platformService.getDailyNotificationStatus();
if (status === null) {
// Notifications not supported on this platform
}
Key Operations (via PlatformService)
- Check Status:
getDailyNotificationStatus()- Returns status ornullif unsupported - Check Permissions:
checkNotificationPermissions()- Returns permissions ornullif unsupported - Request Permissions:
requestNotificationPermissions()- Requests permissions or returnsnullif unsupported - Schedule:
scheduleDailyNotification(options)- Schedules notification or throws error if unsupported - Cancel:
cancelDailyNotification()- Cancels notification or throws error if unsupported
UI/UX Considerations
Visual Design
- Section Style: Match existing notification section (
bg-slate-100 rounded-md) - Toggle Switch: Reuse existing custom toggle pattern
- Time Display: Show in user-friendly format ("9:00 AM")
- Edit Button: Small, subtle link/button to edit time
User Feedback
- Success: Toast notification when scheduled successfully
- Error: Clear error message with troubleshooting guidance
- Loading: Show loading state during plugin operations
- Permission Request: Handle gracefully if denied
Accessibility
- ARIA Labels: Proper labels for all interactive elements
- Keyboard Navigation: Full keyboard support
- Screen Reader: Clear announcements for state changes
Implementation Phases
Phase 1: Basic Integration
- Add platform detection property
- Create native notification section in template
- Add component state properties
- Implement toggle functionality
- Basic enable/disable operations
Phase 2: Time Management
- Add time input (HTML5 time picker)
- Implement time format conversion
- Add edit time functionality
- Save/load time from settings
Phase 3: Plugin Integration
- Integrate with DailyNotificationFactory
- Schedule notification on enable
- Cancel notification on disable
- Update notification on time change
- Sync plugin state with settings
Phase 4: Polish & Error Handling
- Permission request flow
- Error handling and user feedback
- Status verification
- Help/info dialogs
- Accessibility improvements
Implementation Decisions
Time Input Format ✅
- Selected: HTML5
<input type="time">for Capacitor platforms - Rationale: Native mobile experience, simpler code, automatic 24-hour format
Edit Approach ✅
- Selected: Inline HTML5 time input for quick edits in AccountViewView
- Note: For complex editing (title, message changes), users can navigate to dedicated ScheduleView
Settings Field Names ✅
- Selected:
nativeNotificationTime,nativeNotificationTitle,nativeNotificationMessage - Rationale: Clear distinction from web push notification fields
Notification Title/Message ✅
- Selected: Allow customization, default to "Daily Update" / "Your daily notification is ready!"
- Rationale: Flexibility for users, sensible defaults
Success Criteria
- Native notification section appears only on Capacitor platforms
- Toggle enables/disables notifications via plugin
- Time can be set and edited
- Settings persist across app restarts
- Plugin state syncs with settings
- Error handling provides clear user feedback
- UI matches existing design patterns
- Accessibility requirements met
Related Components
- PlatformService: Interface for platform capabilities (notification methods)
- PlatformServiceFactory: Factory for getting platform service instance
- ScheduleView: Dedicated scheduling interface (for complex editing)
- AccountViewView: Main settings view (integration target)
Next Steps
Decide on Approach: Separate native notification section✅ DECIDED- Define Settings Schema: Add native notification fields to Settings interface
- Create UI Components: Build notification section in AccountViewView
- Integrate Plugin: Connect UI to DailyNotificationFactory service
- Test Flow: Verify enable/disable/edit workflows
- Add Help Content: Create help documentation for native notifications
See also:
doc/daily-notification-plugin-integration-plan.md- Overall integration plansrc/views/AccountViewView.vue- Target component for integrationsrc/services/PlatformService.ts- PlatformService interface definitionsrc/services/PlatformServiceFactory.ts- Factory for platform service instances