fix(notifications): New Activity vs Daily Reminder separation and copy

- PushNotificationPermission: show "Turn on New Activity Notifications"
  when enabling New Activity; use NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY for
  success toast so copy says "New Activity notifications are now enabled."
- App.vue: on native, turnOffNotifications invokes the modal's callback
  only (fixes turn-off not updating state); add comment that callback is
  per notification type.
- AccountViewView: handle plugin UNIMPLEMENTED for scheduleDualNotification
  on iOS with friendlier message; add New Activity time block and "Edit
  New Activity Notification…"; rename Daily Reminder button to "Edit Daily
  Reminder…".
- Constants: add NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY. Reminder IDs and
  Option A (skip single reminder for New Activity) from earlier commit.
This commit is contained in:
Jose Olarte III
2026-03-17 19:23:40 +08:00
parent 263b12c37e
commit e155e55e49
4 changed files with 124 additions and 19 deletions

View File

@@ -360,6 +360,7 @@
<script lang="ts">
import { Vue, Component } from "vue-facing-decorator";
import { Capacitor } from "@capacitor/core";
import { NotificationIface } from "./constants/app";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
@@ -382,6 +383,24 @@ export default class App extends Vue {
async turnOffNotifications(
notification: NotificationIface,
): Promise<boolean> {
// On native (iOS/Android) we don't use web push; the callback handles cancel + state in the view.
// The callback is the one passed for this specific modal (New Activity or Daily Reminder), so we only turn off that one.
if (Capacitor.isNativePlatform()) {
if (notification.callback) {
await notification.callback(true);
}
this.$notify(
{
group: "alert",
type: "info",
title: "Finished",
text: "Notifications are off.",
},
5000,
);
return true;
}
let subscription: PushSubscriptionJSON | null = null;
let allGoingOff = false;

View File

@@ -67,7 +67,7 @@
class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white mt-2 px-2 py-2 rounded-md"
@click="handleTurnOnNotifications"
>
Turn on Daily Reminder
{{ isDailyCheck ? "Turn on New Activity Notifications" : "Turn on Daily Reminder" }}
</button>
</div>
@@ -95,6 +95,7 @@ import {
NOTIFY_PUSH_PERMISSION_ERROR,
NOTIFY_PUSH_SETUP_UNDERWAY,
NOTIFY_PUSH_SUCCESS,
NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY,
NOTIFY_PUSH_SETUP_ERROR,
NOTIFY_PUSH_SUBSCRIPTION_ERROR,
PUSH_NOTIFICATION_TIMEOUT_SHORT,
@@ -775,8 +776,8 @@ export default class PushNotificationPermission extends Vue {
{
group: "alert",
type: "success",
title: NOTIFY_PUSH_SUCCESS.title,
text: NOTIFY_PUSH_SUCCESS.message,
title: NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY.title,
text: NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY.message,
},
PUSH_NOTIFICATION_TIMEOUT_LONG,
);

View File

@@ -1640,12 +1640,18 @@ export const NOTIFY_PUSH_SETUP_UNDERWAY = {
"Setting up notifications for interesting activity, which takes about 10 seconds. If you don't see a final confirmation, check the 'Troubleshoot' page.",
};
// Used in: PushNotificationPermission.vue (turnOnNotifications method - success)
// Used in: PushNotificationPermission.vue (turnOnNotifications method - success, Daily Reminder)
export const NOTIFY_PUSH_SUCCESS = {
title: "Notifications On",
message: "Daily Reminder notifications are now enabled.",
};
// Used in: PushNotificationPermission.vue (turnOnNotifications method - success, New Activity only)
export const NOTIFY_PUSH_SUCCESS_NEW_ACTIVITY = {
title: "Notifications On",
message: "New Activity notifications are now enabled.",
};
// Used in: PushNotificationPermission.vue (turnOnNotifications method - general error)
export const NOTIFY_PUSH_SETUP_ERROR = {
title: "Error Setting Notification Permissions",

View File

@@ -139,11 +139,11 @@
class="w-full text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
@click="editReminderNotification"
>
Edit Notification Details
Edit Daily Reminder
</button>
</div>
</div>
<div class="mt-4 flex items-center justify-between">
<div class="flex items-center justify-between mt-4 mb-2">
<!-- label -->
<div>
New Activity Notification
@@ -178,8 +178,22 @@
></div>
</div>
</div>
<div v-if="notifyingNewActivityTime" class="w-full text-right">
{{ notifyingNewActivityTime.replace(" ", "&nbsp;") }}
<div v-if="notifyingNewActivity" class="w-full">
<div
class="text-sm text-slate-500 mb-2 bg-white rounded px-3 py-2 border border-slate-200"
>
<div>
<b>Time:</b> {{ notifyingNewActivityTime.replace(" ", "&nbsp;") }}
</div>
</div>
<div class="mt-2 text-center">
<button
class="w-full text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
@click="editNewActivityNotification"
>
Edit New Activity Notification
</button>
</div>
</div>
<div class="mt-2 text-center">
<router-link class="text-sm text-blue-500" to="/help-notifications">
@@ -1244,6 +1258,19 @@ export default class AccountViewView extends Vue {
* Configure native fetcher, sync starred plans, and schedule API-driven dual notification.
*/
async scheduleNewActivityDualNotification(notifyTime: string): Promise<void> {
const plugin = DailyNotification as unknown as {
scheduleDualNotification?: (opts: { config: unknown }) => Promise<void>;
};
if (!plugin.scheduleDualNotification) {
logger.warn(
"[AccountViewView] scheduleDualNotification not available on this device",
);
this.notify.error(
"New Activity scheduling is not available on this device. Please update the app.",
TIMEOUTS.STANDARD,
);
return;
}
try {
await configureNativeFetcherIfReady(this.activeDid);
const settings = await this.$accountSettings();
@@ -1252,22 +1279,24 @@ export default class AccountViewView extends Vue {
await DailyNotification.updateStarredPlans({ planIds });
}
const config = buildDualScheduleConfig({ notifyTime });
await (
DailyNotification as unknown as {
scheduleDualNotification: (opts: {
config: unknown;
}) => Promise<void>;
}
).scheduleDualNotification({ config });
await plugin.scheduleDualNotification!({ config });
} catch (error) {
logger.error(
"[AccountViewView] scheduleNewActivityDualNotification failed:",
error,
);
this.notify.error(
"Could not schedule New Activity notification. Please try again.",
TIMEOUTS.STANDARD,
);
const code = (error as { code?: string })?.code;
if (code === "UNIMPLEMENTED") {
this.notify.error(
"New Activity scheduling is not yet available on this device. Please update the app when support is added.",
TIMEOUTS.STANDARD,
);
} else {
this.notify.error(
"Could not schedule New Activity notification. Please try again.",
TIMEOUTS.STANDARD,
);
}
}
}
@@ -1468,6 +1497,56 @@ export default class AccountViewView extends Vue {
}, 150);
}
/**
* Edit existing New Activity notification time.
* Opens the dialog with current time; on success reschedules dual notification.
*/
async editNewActivityNotification(): Promise<void> {
const dialog = this.$refs
.pushNotificationPermission as PushNotificationPermission;
dialog.open(
DAILY_CHECK_TITLE,
async (success: boolean, timeText: string) => {
if (!success) return;
if (Capacitor.isNativePlatform()) {
await this.scheduleNewActivityDualNotification(timeText);
}
await this.$saveSettings({ notifyingNewActivityTime: timeText });
this.notifyingNewActivityTime = timeText;
this.notify.success(
"New Activity notification time updated.",
TIMEOUTS.STANDARD,
);
},
{ skipSchedule: true },
);
// Pre-populate the dialog with current New Activity time
setTimeout(() => {
const timeMatch = this.notifyingNewActivityTime.match(
/(\d+):(\d+)\s*(AM|PM)/i,
);
if (timeMatch) {
let hour = parseInt(timeMatch[1], 10);
const minute = timeMatch[2];
const isAm = timeMatch[3].toUpperCase() === "AM";
if (hour === 12) {
hour = 12;
} else if (hour > 12) {
hour = hour - 12;
}
const dialogComponent =
dialog as unknown as PushNotificationPermissionRef;
if (dialogComponent) {
dialogComponent.hourInput = hour.toString();
dialogComponent.minuteInput = minute;
dialogComponent.hourAm = isAm;
}
}
}, 150);
}
/**
* Toggle dev-only 10-minute rollover for daily reminder. Saves the setting and,
* if reminder is already on, reschedules so the plugin uses the new interval.