鸿蒙APP开发-带你走进家课表App提醒通知功能技术解析

1 阅读4分钟

家课表App提醒通知功能技术解析

继续聊聊家课表App,这次是提醒通知功能。想体验完整功能,可以去鸿蒙应用市场搜索"家课表"下载。

写在前面

上一篇我们聊了家课表App的作业管理功能,今天聊聊提醒通知——怎么让用户不忘记重要的事情。

提醒通知是教育类App的重要功能。Web端用浏览器通知API,鸿蒙端用系统通知和提醒服务,功能更强大、更可靠。而且鸿蒙端支持后台提醒,即使App没打开也能收到提醒。

今天这篇,我会从系统通知、定时提醒、日历集成这几个方面,聊聊家课表App的提醒通知功能。


1. 系统通知:即时提醒

ArkTS系统通知:

import { notificationManager } from '@kit.NotificationManagementKit';

class NotificationService {
  // 发送通知
  static async sendNotification(title: string, content: string, id: number = 1001) {
    const notificationRequest: notificationManager.NotificationRequest = {
      id: id,
      content: {
        notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: title,
          text: content
        }
      }
    };

    try {
      await notificationManager.publish(notificationRequest);
    } catch (err) {
      console.error('发送通知失败:', err);
    }
  }

  // 取消通知
  static async cancelNotification(id: number) {
    try {
      await notificationManager.cancel(id);
    } catch (err) {
      console.error('取消通知失败:', err);
    }
  }

  // 取消所有通知
  static async cancelAll() {
    try {
      await notificationManager.cancelAll();
    } catch (err) {
      console.error('取消所有通知失败:', err);
    }
  }
}

2. 定时提醒:后台提醒

ArkTS定时提醒:

import { reminderAgentManager } from '@kit.BackgroundTasksKit';

class ReminderService {
  // 创建一次性提醒
  static async createOnceReminder(
    title: string,
    content: string,
    triggerTime: number,
    notificationId: number
  ) {
    const date = new Date(triggerTime);

    const reminderInfo: reminderAgentManager.ReminderRequestCalendar = {
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_CALENDAR,
      dateTime: {
        year: date.getFullYear(),
        month: date.getMonth() + 1,
        day: date.getDate(),
        hour: date.getHours(),
        minute: date.getMinutes()
      },
      wantAgent: {
        wants: [
          {
            bundleName: 'com.example.jiakbiao',
            abilityName: 'EntryAbility'
          }
        ],
        actionType: 0
      },
      title: title,
      content: content,
      notificationId: notificationId
    };

    try {
      await reminderAgentManager.publishReminder(reminderInfo);
    } catch (err) {
      console.error('创建提醒失败:', err);
    }
  }

  // 创建每日提醒
  static async createDailyReminder(
    title: string,
    content: string,
    hour: number,
    minute: number,
    notificationId: number
  ) {
    const reminderInfo: reminderAgentManager.ReminderRequestAlarm = {
      reminderType: reminderAgentManager.ReminderType.REMINDER_TYPE_ALARM,
      hour: hour,
      minute: minute,
      daysOfWeek: [1, 2, 3, 4, 5], // 周一到周五
      wantAgent: {
        wants: [
          {
            bundleName: 'com.example.jiakbiao',
            abilityName: 'EntryAbility'
          }
        ],
        actionType: 0
      },
      title: title,
      content: content,
      notificationId: notificationId
    };

    try {
      await reminderAgentManager.publishReminder(reminderInfo);
    } catch (err) {
      console.error('创建提醒失败:', err);
    }
  }

  // 取消提醒
  static async cancelReminder(notificationId: number) {
    try {
      await reminderAgentManager.cancelReminder(notificationId);
    } catch (err) {
      console.error('取消提醒失败:', err);
    }
  }
}

3. 提醒管理:统一管理

ArkTS提醒管理:

interface Reminder {
  id: string;
  title: string;
  content: string;
  type: 'once' | 'daily' | 'weekly';
  triggerTime?: number;
  hour?: number;
  minute?: number;
  daysOfWeek?: number[];
  notificationId: number;
  enabled: boolean;
}

@Component
struct ReminderManager {
  @State reminders: Reminder[] = [];
  @State showCreateDialog: boolean = false;

  private rdbStore: relationalStore.RdbStore | null = null;

  async aboutToAppear() {
    await this.initDatabase();
    await this.loadReminders();
  }

  async initDatabase() {
    const config: relationalStore.StoreConfig = {
      name: 'reminders.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };

    this.rdbStore = await relationalStore.getRdbStore(getContext(), config);

    await this.rdbStore.executeSql(`
      CREATE TABLE IF NOT EXISTS reminders (
        id TEXT PRIMARY KEY,
        title TEXT,
        content TEXT,
        type TEXT,
        trigger_time INTEGER,
        hour INTEGER,
        minute INTEGER,
        days_of_week TEXT,
        notification_id INTEGER,
        enabled INTEGER DEFAULT 1
      )
    `);
  }

  async loadReminders() {
    if (!this.rdbStore) return;

    const resultSet = await this.rdbStore.querySql(
      'SELECT * FROM reminders ORDER BY trigger_time ASC'
    );

    const list: Reminder[] = [];
    while (resultSet.goToNextRow()) {
      list.push({
        id: resultSet.getString(resultSet.getColumnIndex('id')),
        title: resultSet.getString(resultSet.getColumnIndex('title')),
        content: resultSet.getString(resultSet.getColumnIndex('content')),
        type: resultSet.getString(resultSet.getColumnIndex('type')) as any,
        triggerTime: resultSet.getLong(resultSet.getColumnIndex('trigger_time')),
        hour: resultSet.getLong(resultSet.getColumnIndex('hour')),
        minute: resultSet.getLong(resultSet.getColumnIndex('minute')),
        daysOfWeek: JSON.parse(resultSet.getString(resultSet.getColumnIndex('days_of_week')) || '[]'),
        notificationId: resultSet.getLong(resultSet.getColumnIndex('notification_id')),
        enabled: resultSet.getLong(resultSet.getColumnIndex('enabled')) === 1
      });
    }
    resultSet.close();

    this.reminders = list;
  }

  async toggleReminder(reminder: Reminder) {
    const newEnabled = !reminder.enabled;

    if (newEnabled) {
      await this.enableReminder(reminder);
    } else {
      await this.disableReminder(reminder);
    }

    await this.loadReminders();
  }

  async enableReminder(reminder: Reminder) {
    if (reminder.type === 'once' && reminder.triggerTime) {
      await ReminderService.createOnceReminder(
        reminder.title,
        reminder.content,
        reminder.triggerTime,
        reminder.notificationId
      );
    } else if (reminder.type === 'daily' && reminder.hour !== undefined && reminder.minute !== undefined) {
      await ReminderService.createDailyReminder(
        reminder.title,
        reminder.content,
        reminder.hour,
        reminder.minute,
        reminder.notificationId
      );
    }

    // 更新数据库
    if (this.rdbStore) {
      await this.rdbStore.executeSql(
        'UPDATE reminders SET enabled = 1 WHERE id = ?',
        [reminder.id]
      );
    }
  }

  async disableReminder(reminder: Reminder) {
    await ReminderService.cancelReminder(reminder.notificationId);

    if (this.rdbStore) {
      await this.rdbStore.executeSql(
        'UPDATE reminders SET enabled = 0 WHERE id = ?',
        [reminder.id]
      );
    }
  }

  async deleteReminder(reminder: Reminder) {
    await ReminderService.cancelReminder(reminder.notificationId);

    if (this.rdbStore) {
      await this.rdbStore.executeSql('DELETE FROM reminders WHERE id = ?', [reminder.id]);
    }

    await this.loadReminders();
  }

  build() {
    Column() {
      Text('提醒管理')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })

      List({ space: 8 }) {
        ForEach(this.reminders, (reminder: Reminder) => {
          ListItem() {
            this.ReminderCard(reminder)
          }
        })
      }
      .margin({ top: 16 })
      .layoutWeight(1)

      Button('添加提醒')
        .width('90%')
        .margin({ bottom: 20 })
        .onClick(() => this.showCreateDialog = true)
    }
  }

  @Builder
  ReminderCard(reminder: Reminder) {
    Row() {
      Column() {
        Text(reminder.title)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)

        Text(reminder.content)
          .fontSize(14)
          .fontColor('#666')
          .margin({ top: 4 })

        Text(this.getReminderTimeText(reminder))
          .fontSize(12)
          .fontColor('#999')
          .margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)

      Toggle({ type: ToggleType.Switch, isOn: reminder.enabled })
        .onChange((isOn: boolean) => {
          this.toggleReminder(reminder);
        })
    }
    .padding(12)
    .backgroundColor('#fff')
    .borderRadius(8)
  }

  getReminderTimeText(reminder: Reminder): string {
    if (reminder.type === 'once' && reminder.triggerTime) {
      return new Date(reminder.triggerTime).toLocaleString();
    }

    if (reminder.type === 'daily' && reminder.hour !== undefined && reminder.minute !== undefined) {
      const days = reminder.daysOfWeek?.map(d => ['日', '一', '二', '三', '四', '五', '六'][d]).join('、') || '';
      return `每天 ${reminder.hour}:${reminder.minute.toString().padStart(2, '0')}`;
    }

    return '';
  }
}

4. 通知权限:权限管理

ArkTS权限管理:

class PermissionManager {
  // 检查通知权限
  static async checkNotificationPermission(): Promise<boolean> {
    try {
      const result = await notificationManager.isNotificationEnabled();
      return result;
    } catch (err) {
      console.error('检查通知权限失败:', err);
      return false;
    }
  }

  // 请求通知权限
  static async requestNotificationPermission() {
    try {
      await notificationManager.requestEnableNotification();
    } catch (err) {
      console.error('请求通知权限失败:', err);
    }
  }

  // 检查后台提醒权限
  static async checkReminderPermission(): Promise<boolean> {
    try {
      const result = await reminderAgentManager.canRequest();
      return result;
    } catch (err) {
      console.error('检查提醒权限失败:', err);
      return false;
    }
  }
}

5. 通知设置:个性化配置

ArkTS通知设置:

@Component
struct NotificationSettings {
  @State enableHomeworkReminder: boolean = true;
  @State reminderTime: number = 18; // 18:00
  @State enableSound: boolean = true;
  @State enableVibration: boolean = true;

  build() {
    Column() {
      Text('通知设置')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 })

      // 作业提醒
      Row() {
        Column() {
          Text('作业提醒')
            .fontSize(16)

          Text('在截止日期前提醒完成作业')
            .fontSize(14)
            .fontColor('#666')
            .margin({ top: 4 })
        }
        .alignItems(HorizontalAlign.Start)
        .layoutWeight(1)

        Toggle({ type: ToggleType.Switch, isOn: this.enableHomeworkReminder })
          .onChange((isOn: boolean) => {
            this.enableHomeworkReminder = isOn;
          })
      }
      .padding(16)
      .backgroundColor('#fff')
      .borderRadius(12)
      .margin({ top: 16 })

      // 提醒时间
      if (this.enableHomeworkReminder) {
        Row() {
          Text('提醒时间')
            .fontSize(14)

          Text(`${this.reminderTime}:00`)
            .fontSize(14)
            .margin({ left: 'auto' })
        }
        .padding(16)
        .backgroundColor('#fff')
        .borderRadius(12)
        .margin({ top: 8 })
      }

      // 声音设置
      Row() {
        Text('提醒声音')
          .fontSize(14)
          .layoutWeight(1)

        Toggle({ type: ToggleType.Switch, isOn: this.enableSound })
          .onChange((isOn: boolean) => {
            this.enableSound = isOn;
          })
      }
      .padding(16)
      .backgroundColor('#fff')
      .borderRadius(12)
      .margin({ top: 8 })

      // 震动设置
      Row() {
        Text('震动提醒')
          .fontSize(14)
          .layoutWeight(1)

        Toggle({ type: ToggleType.Switch, isOn: this.enableVibration })
          .onChange((isOn: boolean) => {
            this.enableVibration = isOn;
          })
      }
      .padding(16)
      .backgroundColor('#fff')
      .borderRadius(12)
      .margin({ top: 8 })
    }
  }
}

总结

家课表App的提醒通知功能,从系统通知、定时提醒、提醒管理到权限管理和通知设置,每一部分都有它的技术要点。鸿蒙端的通知API和提醒服务,提供了强大的后台提醒能力。

如果你做教育或生活管理类App,这些通知技术都很实用。系统通知、定时提醒、权限管理,这些都是通用的技术。

家课表App就聊到这里。下一篇文章,我们聊聊绘本架App的阅读计时功能。