flutter_for_openharmony家庭相册app实战+通知设置实现

通知设置让用户可以自定义接收哪些类型的通知,以及何时接收。合理的通知设置能在不打扰用户的前提下,及时提醒重要事项。

设计思路

通知设置页面包括总开关、各类提醒开关、提醒时间和免打扰时段。用户可以根据自己的需求灵活配置。

创建页面结构

先搭建基本框架:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:provider/provider.dart';
import '../providers/settings_provider.dart';

class NotificationSettingsScreen extends StatefulWidget {
  const NotificationSettingsScreen({super.key});

  @override
  State<NotificationSettingsScreen> createState() =>
      _NotificationSettingsScreenState();
}

class _NotificationSettingsScreenState
    extends State<NotificationSettingsScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('通知设置'),
        elevation: 0,
      ),
      body: Consumer<SettingsProvider>(
        builder: (context, settings, _) {
          return ListView(
            children: [
              _buildMainSwitch(settings),
              if (settings.notificationsEnabled) ...[
                _buildReminderTypes(settings),
                _buildReminderTime(settings),
                _buildDoNotDisturb(settings),
              ],
            ],
          );
        },
      ),
    );
  }
}

用Consumer监听设置变化,当通知总开关关闭时,其他选项会隐藏起来。这样的设计让界面更简洁。

通知总开关

最上面是通知的总开关:

dart 复制代码
Widget _buildMainSwitch(SettingsProvider settings) {
  return Container(
    color: Colors.white,
    child: SwitchListTile(
      secondary: Container(
        padding: EdgeInsets.all(8.w),
        decoration: BoxDecoration(
          color: const Color(0xFFE91E63).withOpacity(0.1),
          borderRadius: BorderRadius.circular(8.r),
        ),
        child: const Icon(
          Icons.notifications_active,
          color: Color(0xFFE91E63),
        ),
      ),
      title: Text(
        '启用通知',
        style: TextStyle(
          fontSize: 16.sp,
          fontWeight: FontWeight.w500,
        ),
      ),
      subtitle: Text(
        settings.notificationsEnabled ? '已开启' : '已关闭',
        style: TextStyle(
          fontSize: 13.sp,
          color: Colors.grey[600],
        ),
      ),
      value: settings.notificationsEnabled,
      onChanged: (value) {
        settings.setNotificationsEnabled(value);
        if (!value) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: const Text('已关闭所有通知'),
              duration: const Duration(seconds: 2),
              behavior: SnackBarBehavior.floating,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(8.r),
              ),
            ),
          );
        }
      },
      activeColor: const Color(0xFFE91E63),
    ),
  );
}

总开关关闭时会显示一个提示,让用户知道操作生效了。图标用圆角容器包装,和设置页面保持一致的风格。

提醒类型设置

各种类型的提醒开关:

dart 复制代码
Widget _buildReminderTypes(SettingsProvider settings) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Padding(
        padding: EdgeInsets.fromLTRB(16.w, 20.h, 16.w, 8.h),
        child: Text(
          '提醒类型',
          style: TextStyle(
            fontSize: 13.sp,
            color: Colors.grey[600],
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
      Container(
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border(
            top: BorderSide(color: Colors.grey[200]!, width: 0.5),
            bottom: BorderSide(color: Colors.grey[200]!, width: 0.5),
          ),
        ),
        child: Column(
          children: [
            _buildReminderItem(
              settings: settings,
              icon: Icons.cake_outlined,
              iconColor: const Color(0xFFFF9800),
              title: '生日提醒',
              subtitle: '家人生日前提醒',
              value: settings.birthdayReminder,
              onChanged: (value) => settings.setBirthdayReminder(value),
            ),
            _buildDivider(),
            _buildReminderItem(
              settings: settings,
              icon: Icons.favorite_outline,
              iconColor: const Color(0xFFE91E63),
              title: '纪念日提醒',
              subtitle: '重要纪念日前提醒',
              value: settings.anniversaryReminder,
              onChanged: (value) => settings.setAnniversaryReminder(value),
            ),
            _buildDivider(),
            _buildReminderItem(
              settings: settings,
              icon: Icons.event_outlined,
              iconColor: const Color(0xFF2196F3),
              title: '活动提醒',
              subtitle: '家庭活动前提醒',
              value: settings.eventReminder,
              onChanged: (value) => settings.setEventReminder(value),
            ),
            _buildDivider(),
            _buildReminderItem(
              settings: settings,
              icon: Icons.check_circle_outline,
              iconColor: const Color(0xFF4CAF50),
              title: '待办提醒',
              subtitle: '待办事项到期提醒',
              value: settings.todoReminder,
              onChanged: (value) => settings.setTodoReminder(value),
            ),
          ],
        ),
      ),
    ],
  );
}

Widget _buildReminderItem({
  required SettingsProvider settings,
  required IconData icon,
  required Color iconColor,
  required String title,
  required String subtitle,
  required bool value,
  required Function(bool) onChanged,
}) {
  return SwitchListTile(
    secondary: Container(
      padding: EdgeInsets.all(8.w),
      decoration: BoxDecoration(
        color: iconColor.withOpacity(0.1),
        borderRadius: BorderRadius.circular(8.r),
      ),
      child: Icon(icon, color: iconColor, size: 20.sp),
    ),
    title: Text(title),
    subtitle: Text(
      subtitle,
      style: TextStyle(
        fontSize: 12.sp,
        color: Colors.grey[600],
      ),
    ),
    value: value,
    onChanged: onChanged,
    activeColor: const Color(0xFFE91E63),
  );
}

Widget _buildDivider() {
  return Divider(
    height: 1,
    indent: 72.w,
    color: Colors.grey[200],
  );
}

每种提醒类型都有独特的图标和颜色,让用户一眼就能区分。分隔线从图标后面开始,这样看起来更整齐。

提醒时间设置

让用户选择提前多久提醒:

dart 复制代码
Widget _buildReminderTime(SettingsProvider settings) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Padding(
        padding: EdgeInsets.fromLTRB(16.w, 20.h, 16.w, 8.h),
        child: Text(
          '提醒时间',
          style: TextStyle(
            fontSize: 13.sp,
            color: Colors.grey[600],
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
      Container(
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border(
            top: BorderSide(color: Colors.grey[200]!, width: 0.5),
            bottom: BorderSide(color: Colors.grey[200]!, width: 0.5),
          ),
        ),
        child: ListTile(
          leading: Container(
            padding: EdgeInsets.all(8.w),
            decoration: BoxDecoration(
              color: const Color(0xFF9C27B0).withOpacity(0.1),
              borderRadius: BorderRadius.circular(8.r),
            ),
            child: const Icon(
              Icons.access_time,
              color: Color(0xFF9C27B0),
            ),
          ),
          title: const Text('提前提醒时间'),
          subtitle: Text(
            _getReminderTimeText(settings.reminderTime),
            style: TextStyle(
              fontSize: 12.sp,
              color: Colors.grey[600],
            ),
          ),
          trailing: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                _getReminderTimeText(settings.reminderTime),
                style: TextStyle(
                  fontSize: 14.sp,
                  color: const Color(0xFFE91E63),
                  fontWeight: FontWeight.w500,
                ),
              ),
              SizedBox(width: 4.w),
              const Icon(Icons.chevron_right, color: Colors.grey),
            ],
          ),
          onTap: () => _showReminderTimeDialog(settings),
        ),
      ),
    ],
  );
}

String _getReminderTimeText(int minutes) {
  if (minutes == 0) return '当天';
  if (minutes == 1440) return '1天前';
  if (minutes == 4320) return '3天前';
  if (minutes == 10080) return '1周前';
  return '$minutes分钟前';
}

提醒时间用一个列表项展示,点击后弹出选择对话框。右边显示当前选择的值,用主题色高亮。

提醒时间选择对话框

让用户选择具体的提醒时间:

dart 复制代码
void _showReminderTimeDialog(SettingsProvider settings) {
  final options = [
    {'label': '当天', 'value': 0},
    {'label': '1天前', 'value': 1440},
    {'label': '3天前', 'value': 4320},
    {'label': '1周前', 'value': 10080},
  ];

  showDialog(
    context: context,
    builder: (dialogContext) => AlertDialog(
      title: const Text('选择提醒时间'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: options.map((option) {
          final isSelected = settings.reminderTime == option['value'];
          return InkWell(
            onTap: () {
              settings.setReminderTime(option['value'] as int);
              Navigator.pop(dialogContext);
            },
            borderRadius: BorderRadius.circular(8.r),
            child: Container(
              padding: EdgeInsets.symmetric(
                horizontal: 16.w,
                vertical: 12.h,
              ),
              margin: EdgeInsets.only(bottom: 8.h),
              decoration: BoxDecoration(
                color: isSelected
                    ? const Color(0xFFE91E63).withOpacity(0.1)
                    : Colors.transparent,
                borderRadius: BorderRadius.circular(8.r),
                border: Border.all(
                  color: isSelected
                      ? const Color(0xFFE91E63)
                      : Colors.grey[300]!,
                  width: isSelected ? 2 : 1,
                ),
              ),
              child: Row(
                children: [
                  Text(
                    option['label'] as String,
                    style: TextStyle(
                      fontSize: 15.sp,
                      color: isSelected
                          ? const Color(0xFFE91E63)
                          : Colors.black87,
                      fontWeight:
                          isSelected ? FontWeight.w600 : FontWeight.normal,
                    ),
                  ),
                  const Spacer(),
                  if (isSelected)
                    const Icon(
                      Icons.check_circle,
                      color: Color(0xFFE91E63),
                    ),
                ],
              ),
            ),
          );
        }).toList(),
      ),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16.r),
      ),
    ),
  );
}

选项用卡片式设计,选中的选项有边框和背景色。用户点击后立即应用并关闭对话框。

免打扰设置

设置免打扰时段:

dart 复制代码
Widget _buildDoNotDisturb(SettingsProvider settings) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Padding(
        padding: EdgeInsets.fromLTRB(16.w, 20.h, 16.w, 8.h),
        child: Text(
          '免打扰',
          style: TextStyle(
            fontSize: 13.sp,
            color: Colors.grey[600],
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
      Container(
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border(
            top: BorderSide(color: Colors.grey[200]!, width: 0.5),
            bottom: BorderSide(color: Colors.grey[200]!, width: 0.5),
          ),
        ),
        child: Column(
          children: [
            SwitchListTile(
              secondary: Container(
                padding: EdgeInsets.all(8.w),
                decoration: BoxDecoration(
                  color: const Color(0xFF607D8B).withOpacity(0.1),
                  borderRadius: BorderRadius.circular(8.r),
                ),
                child: const Icon(
                  Icons.do_not_disturb_on_outlined,
                  color: Color(0xFF607D8B),
                ),
              ),
              title: const Text('启用免打扰'),
              subtitle: Text(
                '在指定时段内不接收通知',
                style: TextStyle(
                  fontSize: 12.sp,
                  color: Colors.grey[600],
                ),
              ),
              value: settings.doNotDisturbEnabled,
              onChanged: (value) => settings.setDoNotDisturbEnabled(value),
              activeColor: const Color(0xFFE91E63),
            ),
            if (settings.doNotDisturbEnabled) ...[
              _buildDivider(),
              ListTile(
                leading: Container(
                  padding: EdgeInsets.all(8.w),
                  decoration: BoxDecoration(
                    color: const Color(0xFF00BCD4).withOpacity(0.1),
                    borderRadius: BorderRadius.circular(8.r),
                  ),
                  child: const Icon(
                    Icons.bedtime_outlined,
                    color: Color(0xFF00BCD4),
                  ),
                ),
                title: const Text('免打扰时段'),
                subtitle: Text(
                  '${settings.dndStartTime} - ${settings.dndEndTime}',
                  style: TextStyle(
                    fontSize: 12.sp,
                    color: Colors.grey[600],
                  ),
                ),
                trailing: const Icon(Icons.chevron_right, color: Colors.grey),
                onTap: () => _showDndTimeDialog(settings),
              ),
            ],
          ],
        ),
      ),
    ],
  );
}

免打扰开关打开后,才显示时段设置。这样的条件显示让界面更简洁。

免打扰时段对话框

让用户设置开始和结束时间:

dart 复制代码
void _showDndTimeDialog(SettingsProvider settings) {
  showDialog(
    context: context,
    builder: (dialogContext) => AlertDialog(
      title: const Text('免打扰时段'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            leading: const Icon(Icons.wb_sunny_outlined, color: Color(0xFFFF9800)),
            title: const Text('开始时间'),
            trailing: Text(
              settings.dndStartTime,
              style: TextStyle(
                fontSize: 16.sp,
                color: const Color(0xFFE91E63),
                fontWeight: FontWeight.w600,
              ),
            ),
            onTap: () async {
              final time = await showTimePicker(
                context: dialogContext,
                initialTime: TimeOfDay(
                  hour: int.parse(settings.dndStartTime.split(':')[0]),
                  minute: int.parse(settings.dndStartTime.split(':')[1]),
                ),
              );
              if (time != null) {
                settings.setDndStartTime(
                  '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
                );
              }
            },
          ),
          ListTile(
            leading: const Icon(Icons.nightlight_outlined, color: Color(0xFF3F51B5)),
            title: const Text('结束时间'),
            trailing: Text(
              settings.dndEndTime,
              style: TextStyle(
                fontSize: 16.sp,
                color: const Color(0xFFE91E63),
                fontWeight: FontWeight.w600,
              ),
            ),
            onTap: () async {
              final time = await showTimePicker(
                context: dialogContext,
                initialTime: TimeOfDay(
                  hour: int.parse(settings.dndEndTime.split(':')[0]),
                  minute: int.parse(settings.dndEndTime.split(':')[1]),
                ),
              );
              if (time != null) {
                settings.setDndEndTime(
                  '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}',
                );
              }
            },
          ),
        ],
      ),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16.r),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(dialogContext),
          child: const Text(
            '完成',
            style: TextStyle(
              color: Color(0xFFE91E63),
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
      ],
    ),
  );
}

点击时间会弹出系统的时间选择器,用户可以方便地选择具体时间。开始时间用太阳图标,结束时间用月亮图标,很直观。

通知声音设置

让用户选择通知提示音:

dart 复制代码
Widget _buildSoundSettings(SettingsProvider settings) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Padding(
        padding: EdgeInsets.fromLTRB(16.w, 20.h, 16.w, 8.h),
        child: Text(
          '声音与振动',
          style: TextStyle(
            fontSize: 13.sp,
            color: Colors.grey[600],
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
      Container(
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border(
            top: BorderSide(color: Colors.grey[200]!, width: 0.5),
            bottom: BorderSide(color: Colors.grey[200]!, width: 0.5),
          ),
        ),
        child: Column(
          children: [
            SwitchListTile(
              secondary: Container(
                padding: EdgeInsets.all(8.w),
                decoration: BoxDecoration(
                  color: const Color(0xFFFF9800).withOpacity(0.1),
                  borderRadius: BorderRadius.circular(8.r),
                ),
                child: const Icon(
                  Icons.volume_up,
                  color: Color(0xFFFF9800),
                ),
              ),
              title: const Text('提示音'),
              subtitle: const Text('播放通知提示音'),
              value: settings.notificationSound,
              onChanged: (value) => settings.setNotificationSound(value),
              activeColor: const Color(0xFFE91E63),
            ),
            _buildDivider(),
            SwitchListTile(
              secondary: Container(
                padding: EdgeInsets.all(8.w),
                decoration: BoxDecoration(
                  color: const Color(0xFF795548).withOpacity(0.1),
                  borderRadius: BorderRadius.circular(8.r),
                ),
                child: const Icon(
                  Icons.vibration,
                  color: Color(0xFF795548),
                ),
              ),
              title: const Text('振动'),
              subtitle: const Text('收到通知时振动'),
              value: settings.notificationVibrate,
              onChanged: (value) => settings.setNotificationVibrate(value),
              activeColor: const Color(0xFFE91E63),
            ),
          ],
        ),
      ),
    ],
  );
}

声音和振动设置让用户可以选择通知的提示方式。有些用户喜欢静音,有些喜欢振动,这样的设计满足不同需求。

通知预览

在设置页面添加测试通知功能:

dart 复制代码
Widget _buildTestNotification() {
  return Container(
    margin: EdgeInsets.all(16.w),
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: const Color(0xFFE91E63).withOpacity(0.1),
      borderRadius: BorderRadius.circular(12.r),
      border: Border.all(
        color: const Color(0xFFE91E63).withOpacity(0.3),
        width: 1,
      ),
    ),
    child: Row(
      children: [
        Icon(
          Icons.info_outline,
          color: const Color(0xFFE91E63),
          size: 24.sp,
        ),
        SizedBox(width: 12.w),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                '测试通知',
                style: TextStyle(
                  fontSize: 14.sp,
                  fontWeight: FontWeight.w600,
                  color: const Color(0xFFE91E63),
                ),
              ),
              SizedBox(height: 4.h),
              Text(
                '点击发送测试通知,查看效果',
                style: TextStyle(
                  fontSize: 12.sp,
                  color: Colors.grey[700],
                ),
              ),
            ],
          ),
        ),
        ElevatedButton(
          onPressed: () => _sendTestNotification(),
          style: ElevatedButton.styleFrom(
            backgroundColor: const Color(0xFFE91E63),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(20.r),
            ),
            padding: EdgeInsets.symmetric(
              horizontal: 16.w,
              vertical: 8.h,
            ),
          ),
          child: const Text('测试'),
        ),
      ],
    ),
  );
}

void _sendTestNotification() {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Row(
        children: [
          const Icon(Icons.notifications_active, color: Colors.white),
          SizedBox(width: 12.w),
          const Expanded(
            child: Text('这是一条测试通知'),
          ),
        ],
      ),
      behavior: SnackBarBehavior.floating,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(8.r),
      ),
      backgroundColor: const Color(0xFFE91E63),
      duration: const Duration(seconds: 3),
    ),
  );
}

测试通知功能让用户可以预览通知效果,确保设置符合预期。这个小功能很实用,用户可以立即看到修改后的效果。

通知权限检查

检查系统通知权限状态:

dart 复制代码
Widget _buildPermissionStatus() {
  return Container(
    margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
    padding: EdgeInsets.all(12.w),
    decoration: BoxDecoration(
      color: Colors.blue[50],
      borderRadius: BorderRadius.circular(8.r),
    ),
    child: Row(
      children: [
        Icon(
          Icons.check_circle,
          color: Colors.blue[700],
          size: 20.sp,
        ),
        SizedBox(width: 8.w),
        Expanded(
          child: Text(
            '已授予通知权限',
            style: TextStyle(
              fontSize: 13.sp,
              color: Colors.blue[700],
            ),
          ),
        ),
        TextButton(
          onPressed: () => _openSystemSettings(),
          child: Text(
            '系统设置',
            style: TextStyle(
              fontSize: 12.sp,
              color: Colors.blue[700],
            ),
          ),
        ),
      ],
    ),
  );
}

void _openSystemSettings() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('打开系统设置'),
      content: const Text('将跳转到系统设置页面,您可以在那里管理应用的通知权限。'),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16.r),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            // 这里调用打开系统设置的方法
          },
          child: const Text(
            '前往',
            style: TextStyle(
              color: Color(0xFFE91E63),
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
      ],
    ),
  );
}

权限状态提示让用户知道当前的权限情况,如果权限被关闭,可以引导用户去系统设置中开启。

总结

通知设置页面通过分组和条件显示,让用户可以精细控制通知行为。总开关控制是否接收通知,各类提醒开关控制具体类型,提醒时间和免打扰时段提供更多自定义选项。声音振动设置和测试通知功能让用户体验更好。整体设计清晰易用,满足不同用户的需求。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
-凌凌漆-2 小时前
【vue】选项式api与组合式api
前端·javascript·vue.js
可触的未来,发芽的智生2 小时前
发现:认知的普适节律 发现思维的8次迭代量子
javascript·python·神经网络·程序人生·自然语言处理
液态不合群2 小时前
【面试题】MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别?
android·数据库·mysql
mocoding3 小时前
使用鸿蒙化Flutter图片选择、相机拍照、多图选择三方库image_picker实战教程示例
flutter·前端框架·harmonyos·鸿蒙
雪球Snowball4 小时前
【Android关键流程】资源加载
android
2501_915918414 小时前
常见 iOS 抓包工具的使用,从代理抓包、设备抓包到数据流抓包
android·ios·小程序·https·uni-app·iphone·webview
phltxy4 小时前
Vue3入门指南:从环境搭建到数据响应式,开启高效前端开发之旅
前端·javascript·vue.js
一起养小猫4 小时前
Flutter for OpenHarmony 实战:电子英汉词典完整开发指南
flutter·harmonyos
摘星编程5 小时前
OpenHarmony + RN:ProgressBar进度条组件
javascript·react native·react.js