diff --git a/src/components/NotificationSection.vue b/src/components/NotificationSection.vue
index 32c67bac..08beb72a 100644
--- a/src/components/NotificationSection.vue
+++ b/src/components/NotificationSection.vue
@@ -14,10 +14,7 @@
aria-label="Learn more about reminder notifications"
@click.stop="showReminderNotificationInfo"
>
-
+
void;
$router!: Router;
- // Props
- isRegistered: boolean = false;
- notifyingNewActivity: boolean = false;
- notifyingNewActivityTime: string = "";
- notifyingReminder: boolean = false;
- notifyingReminderMessage: string = "";
- notifyingReminderTime: string = "";
-
+ // Notification settings service - handles all business logic
+ private notificationService!: NotificationSettingsService;
private notify!: ReturnType
;
created() {
this.notify = createNotifyHelpers(this.$notify);
+ this.notificationService = createNotificationSettingsService(
+ this,
+ this.notify,
+ );
+ }
+
+ /**
+ * Computed property to determine if user is registered
+ * Reads from the notification service
+ */
+ private get isRegistered(): boolean {
+ return this.notificationService.isRegistered;
+ }
+
+ /**
+ * Initialize component state from persisted settings
+ * Called when component is mounted
+ */
+ async mounted(): Promise {
+ await this.notificationService.hydrateFromSettings();
+ }
+
+ // Computed properties for template access
+ private get notifyingNewActivity(): boolean {
+ return this.notificationService.notifyingNewActivity;
+ }
+
+ private get notifyingNewActivityTime(): string {
+ return this.notificationService.notifyingNewActivityTime;
+ }
+
+ private get notifyingReminder(): boolean {
+ return this.notificationService.notifyingReminder;
+ }
+
+ private get notifyingReminderMessage(): string {
+ return this.notificationService.notifyingReminderMessage;
+ }
+
+ private get notifyingReminderTime(): string {
+ return this.notificationService.notifyingReminderTime;
}
async showNewActivityNotificationInfo(): Promise {
- this.notify.confirm(
- ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO,
- async () => {
- await (this.$router as Router).push({
- name: "help-notification-types",
- });
- },
+ await this.notificationService.showNewActivityNotificationInfo(
+ this.$router,
);
}
async showNewActivityNotificationChoice(): Promise {
- if (!this.notifyingNewActivity) {
- (
- this.$refs.pushNotificationPermission as PushNotificationPermission
- ).open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => {
- if (success) {
- await this.$saveSettings({
- notifyingNewActivityTime: timeText,
- });
- this.notifyingNewActivity = true;
- this.notifyingNewActivityTime = timeText;
- }
- });
- } else {
- this.notify.notificationOff(DAILY_CHECK_TITLE, async (success) => {
- if (success) {
- await this.$saveSettings({
- notifyingNewActivityTime: "",
- });
- this.notifyingNewActivity = false;
- this.notifyingNewActivityTime = "";
- }
- });
- }
+ await this.notificationService.showNewActivityNotificationChoice(
+ this.$refs.pushNotificationPermission as PushNotificationPermission,
+ );
}
async showReminderNotificationInfo(): Promise {
- this.notify.confirm(
- ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.REMINDER_INFO,
- async () => {
- await (this.$router as Router).push({
- name: "help-notification-types",
- });
- },
- );
+ await this.notificationService.showReminderNotificationInfo(this.$router);
}
async showReminderNotificationChoice(): Promise {
- if (!this.notifyingReminder) {
- (
- this.$refs.pushNotificationPermission as PushNotificationPermission
- ).open(
- DIRECT_PUSH_TITLE,
- async (success: boolean, timeText: string, message?: string) => {
- if (success) {
- await this.$saveSettings({
- notifyingReminderMessage: message,
- notifyingReminderTime: timeText,
- });
- this.notifyingReminder = true;
- this.notifyingReminderMessage = message || "";
- this.notifyingReminderTime = timeText;
- }
- },
- );
- } else {
- this.notify.notificationOff(DIRECT_PUSH_TITLE, async (success) => {
- if (success) {
- await this.$saveSettings({
- notifyingReminderMessage: "",
- notifyingReminderTime: "",
- });
- this.notifyingReminder = false;
- this.notifyingReminderMessage = "";
- this.notifyingReminderTime = "";
- }
- });
- }
+ await this.notificationService.showReminderNotificationChoice(
+ this.$refs.pushNotificationPermission as PushNotificationPermission,
+ );
}
}
diff --git a/src/composables/useNotificationSettings.ts b/src/composables/useNotificationSettings.ts
new file mode 100644
index 00000000..646af7f5
--- /dev/null
+++ b/src/composables/useNotificationSettings.ts
@@ -0,0 +1,233 @@
+/**
+ * useNotificationSettings.ts - Notification settings and permissions service
+ *
+ * This service handles all notification-related business logic including:
+ * - Settings hydration and persistence
+ * - Registration status checking
+ * - Notification permission management
+ * - Settings state management
+ *
+ * Separates business logic from UI components while maintaining
+ * the lifecycle boundary and settings access patterns.
+ *
+ * @author Matthew Raymer
+ * @service NotificationSettingsService
+ */
+
+import { createNotifyHelpers } from "@/utils/notify";
+import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
+import { DAILY_CHECK_TITLE, DIRECT_PUSH_TITLE } from "@/libs/util";
+import type { ComponentPublicInstance } from "vue";
+
+/**
+ * Interface for notification settings state
+ */
+export interface NotificationSettingsState {
+ isRegistered: boolean;
+ notifyingNewActivity: boolean;
+ notifyingNewActivityTime: string;
+ notifyingReminder: boolean;
+ notifyingReminderMessage: string;
+ notifyingReminderTime: string;
+}
+
+/**
+ * Interface for notification settings actions
+ */
+export interface NotificationSettingsActions {
+ hydrateFromSettings: () => Promise;
+ updateNewActivityNotification: (
+ enabled: boolean,
+ timeText?: string,
+ ) => Promise;
+ updateReminderNotification: (
+ enabled: boolean,
+ timeText?: string,
+ message?: string,
+ ) => Promise;
+ showNewActivityNotificationInfo: (router: any) => Promise;
+ showReminderNotificationInfo: (router: any) => Promise;
+ showNewActivityNotificationChoice: (
+ pushNotificationPermissionRef: any,
+ ) => Promise;
+ showReminderNotificationChoice: (
+ pushNotificationPermissionRef: any,
+ ) => Promise;
+}
+
+/**
+ * Service class for managing notification settings and permissions
+ *
+ * @param platformService - PlatformServiceMixin instance for settings access
+ * @param notify - Notification helper functions
+ */
+export class NotificationSettingsService
+ implements NotificationSettingsState, NotificationSettingsActions
+{
+ // State properties
+ public isRegistered: boolean = false;
+ public notifyingNewActivity: boolean = false;
+ public notifyingNewActivityTime: string = "";
+ public notifyingReminder: boolean = false;
+ public notifyingReminderMessage: string = "";
+ public notifyingReminderTime: string = "";
+
+ constructor(
+ private platformService: ComponentPublicInstance & {
+ $accountSettings: () => Promise;
+ $saveSettings: (changes: any) => Promise;
+ },
+ private notify: ReturnType,
+ ) {}
+
+ /**
+ * Load notification settings from database and hydrate internal state
+ * Uses the existing settings mechanism for consistency
+ */
+ public async hydrateFromSettings(): Promise {
+ try {
+ const settings = await this.platformService.$accountSettings();
+
+ // Hydrate registration status
+ this.isRegistered = !!settings?.isRegistered;
+
+ // Hydrate boolean flags from time presence
+ this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
+ this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
+ this.notifyingReminder = !!settings.notifyingReminderTime;
+ this.notifyingReminderMessage = settings.notifyingReminderMessage || "";
+ this.notifyingReminderTime = settings.notifyingReminderTime || "";
+ } catch (error) {
+ console.error("Failed to hydrate notification settings:", error);
+ // Keep default values on error
+ }
+ }
+
+ /**
+ * Update new activity notification settings
+ */
+ public async updateNewActivityNotification(
+ enabled: boolean,
+ timeText: string = "",
+ ): Promise {
+ await this.platformService.$saveSettings({
+ notifyingNewActivityTime: timeText,
+ });
+ this.notifyingNewActivity = enabled;
+ this.notifyingNewActivityTime = timeText;
+ }
+
+ /**
+ * Update reminder notification settings
+ */
+ public async updateReminderNotification(
+ enabled: boolean,
+ timeText: string = "",
+ message: string = "",
+ ): Promise {
+ await this.platformService.$saveSettings({
+ notifyingReminderMessage: message,
+ notifyingReminderTime: timeText,
+ });
+ this.notifyingReminder = enabled;
+ this.notifyingReminderMessage = message;
+ this.notifyingReminderTime = timeText;
+ }
+
+ /**
+ * Show new activity notification info dialog
+ */
+ public async showNewActivityNotificationInfo(router: any): Promise {
+ this.notify.confirm(
+ ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO,
+ async () => {
+ await router.push({
+ name: "help-notification-types",
+ });
+ },
+ );
+ }
+
+ /**
+ * Show reminder notification info dialog
+ */
+ public async showReminderNotificationInfo(router: any): Promise {
+ this.notify.confirm(
+ ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.REMINDER_INFO,
+ async () => {
+ await router.push({
+ name: "help-notification-types",
+ });
+ },
+ );
+ }
+
+ /**
+ * Handle new activity notification choice (enable/disable)
+ */
+ public async showNewActivityNotificationChoice(
+ pushNotificationPermissionRef: any,
+ ): Promise {
+ if (!this.notifyingNewActivity) {
+ // Enable notification
+ pushNotificationPermissionRef.open(
+ DAILY_CHECK_TITLE,
+ async (success: boolean, timeText: string) => {
+ if (success) {
+ await this.updateNewActivityNotification(true, timeText);
+ }
+ },
+ );
+ } else {
+ // Disable notification
+ this.notify.notificationOff(DAILY_CHECK_TITLE, async (success) => {
+ if (success) {
+ await this.updateNewActivityNotification(false);
+ }
+ });
+ }
+ }
+
+ /**
+ * Handle reminder notification choice (enable/disable)
+ */
+ public async showReminderNotificationChoice(
+ pushNotificationPermissionRef: any,
+ ): Promise {
+ if (!this.notifyingReminder) {
+ // Enable notification
+ pushNotificationPermissionRef.open(
+ DIRECT_PUSH_TITLE,
+ async (success: boolean, timeText: string, message?: string) => {
+ if (success) {
+ await this.updateReminderNotification(true, timeText, message);
+ }
+ },
+ );
+ } else {
+ // Disable notification
+ this.notify.notificationOff(DIRECT_PUSH_TITLE, async (success) => {
+ if (success) {
+ await this.updateReminderNotification(false);
+ }
+ });
+ }
+ }
+}
+
+/**
+ * Factory function to create a NotificationSettingsService instance
+ *
+ * @param platformService - PlatformServiceMixin instance
+ * @param notify - Notification helper functions
+ * @returns NotificationSettingsService instance
+ */
+export function createNotificationSettingsService(
+ platformService: ComponentPublicInstance & {
+ $accountSettings: () => Promise;
+ $saveSettings: (changes: any) => Promise;
+ },
+ notify: ReturnType,
+): NotificationSettingsService {
+ return new NotificationSettingsService(platformService, notify);
+}
diff --git a/src/views/AccountViewView.vue b/src/views/AccountViewView.vue
index cb058a59..734b7239 100644
--- a/src/views/AccountViewView.vue
+++ b/src/views/AccountViewView.vue
@@ -60,14 +60,7 @@
@share-info="onShareInfo"
/>
-
+
@@ -797,12 +790,7 @@ export default class AccountViewView extends Vue {
includeUserProfileLocation: boolean = false;
savingProfile: boolean = false;
- // Notification properties
- notifyingNewActivity: boolean = false;
- notifyingNewActivityTime: string = "";
- notifyingReminder: boolean = false;
- notifyingReminderMessage: string = "";
- notifyingReminderTime: string = "";
+ // Push notification subscription (kept for service worker checks)
subscription: PushSubscription | null = null;
// UI state properties
@@ -896,10 +884,9 @@ export default class AccountViewView extends Vue {
const registration = await navigator.serviceWorker?.ready;
this.subscription = await registration.pushManager.getSubscription();
if (!this.subscription) {
- if (this.notifyingNewActivity || this.notifyingReminder) {
- // the app thought there was a subscription but there isn't, so fix the settings
- this.turnOffNotifyingFlags();
- }
+ // Check if there are any notification settings that need to be cleared
+ // This will be handled by the NotificationSection component now
+ // No need to call turnOffNotifyingFlags() as it's no longer needed
}
} catch (error) {
this.notify.warning(
@@ -939,11 +926,6 @@ export default class AccountViewView extends Vue {
this.isRegistered = !!settings?.isRegistered;
this.isSearchAreasSet = !!settings.searchBoxes;
this.searchBox = settings.searchBoxes?.[0] || null;
- this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
- this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
- this.notifyingReminder = !!settings.notifyingReminderTime;
- this.notifyingReminderMessage = settings.notifyingReminderMessage || "";
- this.notifyingReminderTime = settings.notifyingReminderTime || "";
this.partnerApiServer = settings.partnerApiServer || this.partnerApiServer;
this.partnerApiServerInput =
settings.partnerApiServer || this.partnerApiServerInput;
@@ -1041,19 +1023,7 @@ export default class AccountViewView extends Vue {
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
}
- public async turnOffNotifyingFlags(): Promise {
- // should tell the push server as well
- await this.$saveSettings({
- notifyingNewActivityTime: "",
- notifyingReminderMessage: "",
- notifyingReminderTime: "",
- });
- this.notifyingNewActivity = false;
- this.notifyingNewActivityTime = "";
- this.notifyingReminder = false;
- this.notifyingReminderMessage = "";
- this.notifyingReminderTime = "";
- }
+ // turnOffNotifyingFlags method removed - notification state is now managed by NotificationSection component
/**
* Asynchronously exports the database into a downloadable JSON file.