feat(android): add fetch scheduling debug logs and triggerImmediateFetch API

- Add DN|SCHEDULE_CALLBACK logs to diagnose fetch scheduling
- Add DN|SCHEDULE_FETCH_* structured logs for traceability
- Add triggerImmediateFetch() public API for standalone fetches
- Update fetch timing from 1 hour to 5 minutes before notification
- Fix TypeScript lint errors: add return types, replace any types
- Fix ESLint warnings: add console suppression comments
- Fix capacitor.settings.gradle plugin path reference
- Update android-app-improvement-plan.md with current state

Changes:
- DailyNotificationPlugin: Added scheduled callback logging and fetch method
- DailyNotificationFetcher: Changed lead time from 1 hour to 5 minutes
- EnhancedDailyNotificationFetcher: Added ENH|* structured event IDs
- TypeScript services: Fixed lint errors and added proper types
- Test app: Fixed capacitor settings path and TypeScript warnings
This commit is contained in:
Matthew Raymer
2025-10-27 10:14:00 +00:00
parent 14287824dc
commit 66987093f7
14 changed files with 341 additions and 130 deletions

View File

@@ -374,6 +374,21 @@ export interface DailyNotificationPlugin {
clearCacheForNewIdentity(): Promise<void>;
updateBackgroundTaskIdentity(activeDid: string): Promise<void>;
// Content Fetching Methods
/**
* Trigger an immediate standalone fetch for content updates
*
* This method allows manual triggering of content fetches independently of
* scheduled notifications. Useful for on-demand content refresh, cache warming,
* or background sync operations.
*
* @returns Promise with success status and message
*/
triggerImmediateFetch(): Promise<{
success: boolean;
message: string;
}>;
// Static Daily Reminder Methods
/**
* Schedule a simple daily reminder notification

View File

@@ -47,7 +47,9 @@ export interface PermissionEducation {
export class NotificationPermissionManager {
private static instance: NotificationPermissionManager;
private constructor() {}
private constructor() {
// Singleton constructor - no initialization needed
}
public static getInstance(): NotificationPermissionManager {
if (!NotificationPermissionManager.instance) {
@@ -278,12 +280,12 @@ export class NotificationPermissionManager {
}
// Check if we can access the plugin
if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') {
if (typeof (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: unknown } } }).Capacitor?.Plugins?.DailyNotification === 'undefined') {
return 'not_supported';
}
const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.checkPermissions();
return status?.notifications || 'denied';
const status = await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { checkPermissions: () => Promise<{ notifications?: 'granted' | 'denied' | 'prompt' | 'not_supported' }> } } } }).Capacitor?.Plugins?.DailyNotification?.checkPermissions();
return (status?.notifications || 'denied') as 'granted' | 'denied' | 'prompt' | 'not_supported';
} catch (error) {
console.error('Error checking notification permissions:', error);
@@ -297,11 +299,11 @@ export class NotificationPermissionManager {
return 'not_supported';
}
if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') {
if (typeof (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: unknown } } }).Capacitor?.Plugins?.DailyNotification === 'undefined') {
return 'denied';
}
const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getExactAlarmStatus();
const status = await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { getExactAlarmStatus: () => Promise<{ canSchedule?: boolean }> } } } }).Capacitor?.Plugins?.DailyNotification?.getExactAlarmStatus();
return status?.canSchedule ? 'granted' : 'denied';
} catch (error) {
@@ -316,11 +318,11 @@ export class NotificationPermissionManager {
return 'not_supported';
}
if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') {
if (typeof (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: unknown } } }).Capacitor?.Plugins?.DailyNotification === 'undefined') {
return 'denied';
}
const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getBatteryStatus();
const status = await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { getBatteryStatus: () => Promise<{ isOptimized?: boolean }> } } } }).Capacitor?.Plugins?.DailyNotification?.getBatteryStatus();
return status?.isOptimized ? 'denied' : 'granted';
} catch (error) {
@@ -345,11 +347,11 @@ export class NotificationPermissionManager {
private async requestNotificationPermissions(): Promise<{ success: boolean; message: string }> {
try {
if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') {
if (typeof (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: unknown } } }).Capacitor?.Plugins?.DailyNotification === 'undefined') {
return { success: false, message: 'Plugin not available' };
}
const result = await (window as any).Capacitor?.Plugins?.DailyNotification?.requestPermissions();
const result = await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { requestPermissions: () => Promise<{ notifications?: string }> } } } }).Capacitor?.Plugins?.DailyNotification?.requestPermissions();
return {
success: result?.notifications === 'granted',
message: result?.notifications === 'granted' ? 'Notification permissions granted' : 'Notification permissions denied'
@@ -362,16 +364,16 @@ export class NotificationPermissionManager {
private async requestExactAlarmPermissions(): Promise<{ success: boolean; message: string }> {
try {
if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') {
if (typeof (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: unknown } } }).Capacitor?.Plugins?.DailyNotification === 'undefined') {
return { success: false, message: 'Plugin not available' };
}
await (window as any).Capacitor?.Plugins?.DailyNotification?.requestExactAlarmPermission();
await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { requestExactAlarmPermission: () => Promise<void> } } } }).Capacitor?.Plugins?.DailyNotification?.requestExactAlarmPermission();
// Check if permission was granted
const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getExactAlarmStatus();
const status = await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { getExactAlarmStatus: () => Promise<{ canSchedule?: boolean }> } } } }).Capacitor?.Plugins?.DailyNotification?.getExactAlarmStatus();
return {
success: status?.canSchedule,
success: !!status?.canSchedule,
message: status?.canSchedule ? 'Exact alarm permissions granted' : 'Exact alarm permissions denied'
};
@@ -382,14 +384,14 @@ export class NotificationPermissionManager {
private async requestBatteryOptimizationExemption(): Promise<{ success: boolean; message: string }> {
try {
if (typeof (window as any).Capacitor?.Plugins?.DailyNotification === 'undefined') {
if (typeof (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: unknown } } }).Capacitor?.Plugins?.DailyNotification === 'undefined') {
return { success: false, message: 'Plugin not available' };
}
await (window as any).Capacitor?.Plugins?.DailyNotification?.requestBatteryOptimizationExemption();
await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { requestBatteryOptimizationExemption: () => Promise<void> } } } }).Capacitor?.Plugins?.DailyNotification?.requestBatteryOptimizationExemption();
// Check if exemption was granted
const status = await (window as any).Capacitor?.Plugins?.DailyNotification?.getBatteryStatus();
const status = await (window as unknown as { Capacitor?: { Plugins?: { DailyNotification?: { getBatteryStatus: () => Promise<{ isOptimized?: boolean }> } } } }).Capacitor?.Plugins?.DailyNotification?.getBatteryStatus();
return {
success: !status?.isOptimized,
message: !status?.isOptimized ? 'Battery optimization exemption granted' : 'Battery optimization exemption denied'

View File

@@ -214,7 +214,9 @@ export interface ValidationResult<T> {
export class NotificationValidationService {
private static instance: NotificationValidationService;
private constructor() {}
private constructor() {
// Singleton constructor - no initialization needed
}
public static getInstance(): NotificationValidationService {
if (!NotificationValidationService.instance) {
@@ -412,7 +414,7 @@ export class NotificationValidationService {
/**
* Get validation schema for a specific type
*/
public getSchema(type: 'notification' | 'reminder' | 'contentFetch' | 'userNotification' | 'dualSchedule') {
public getSchema(type: 'notification' | 'reminder' | 'contentFetch' | 'userNotification' | 'dualSchedule'): z.ZodType {
switch (type) {
case 'notification':
return NotificationOptionsSchema;
@@ -451,7 +453,10 @@ export class ValidatedDailyNotificationPlugin {
}
// Call native implementation with validated data
return await this.nativeScheduleDailyNotification(validation.data!);
if (!validation.data) {
throw new Error('Validation passed but data is null');
}
return await this.nativeScheduleDailyNotification(validation.data);
}
/**
@@ -465,7 +470,10 @@ export class ValidatedDailyNotificationPlugin {
}
// Call native implementation with validated data
return await this.nativeScheduleDailyReminder(validation.data!);
if (!validation.data) {
throw new Error('Validation passed but data is null');
}
return await this.nativeScheduleDailyReminder(validation.data);
}
/**
@@ -479,7 +487,10 @@ export class ValidatedDailyNotificationPlugin {
}
// Call native implementation with validated data
return await this.nativeScheduleContentFetch(validation.data!);
if (!validation.data) {
throw new Error('Validation passed but data is null');
}
return await this.nativeScheduleContentFetch(validation.data);
}
/**
@@ -493,7 +504,10 @@ export class ValidatedDailyNotificationPlugin {
}
// Call native implementation with validated data
return await this.nativeScheduleUserNotification(validation.data!);
if (!validation.data) {
throw new Error('Validation passed but data is null');
}
return await this.nativeScheduleUserNotification(validation.data);
}
/**
@@ -507,7 +521,10 @@ export class ValidatedDailyNotificationPlugin {
}
// Call native implementation with validated data
return await this.nativeScheduleDualNotification(validation.data!);
if (!validation.data) {
throw new Error('Validation passed but data is null');
}
return await this.nativeScheduleDualNotification(validation.data);
}
// Native implementation methods (to be implemented)