Flutter---Notification(3)--就寝提醒

效果图

详细描述

这是一个就寝提醒的闹钟通知,根据用户设置的时间,到了时间点就弹出通知,测试通知按钮是测试通知是否可行,一点击按钮就一个弹出一个测试通知。一分钟测试也是测试通知的效果,是根据当前的时间,再加上一分钟,然后设置闹钟通知,用户可以在一分钟后看到通知。

核心逻辑流程图
Dart 复制代码
┌─────────────────────────────────────────────────────┐
│                   应用启动                           │
└─────────────────────────┬───────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│         initState() 初始化                           │
│   1. 创建通知插件实例                                │
│   2. 初始化通知设置                                  │
└─────────────────────────┬───────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────┐
│                   主界面显示                          │
│   - 显示当前设置时间                                │
│   - 显示开关状态                                    │
│   - 提供操作按钮                                    │
└───────────┬────────────┬────────────┬───────────────┘
            │            │            │
    ┌───────┴────┐ ┌─────┴─────┐ ┌─────┴──────┐
    │ 修改时间    │ │ 开关切换  │ │ 测试按钮   │
    └───────┬────┘ └─────┬─────┘ └─────┬──────┘
            │            │            │
    ┌───────▼────┐ ┌─────▼─────┐ ┌─────▼──────┐
    │ 时间对话框  │ │_setAlarm()│ │测试通知    │
    │ 选择新时间  │ │或_cancel()│ │            │
    └───────┬────┘ └─────┬─────┘ └────────────┘
            │            │
            │    ┌───────▼──────────────────┐
            │    │  核心闹钟逻辑_setAlarm()  │
            │    │ 1. 检查权限              │
            │    │ 2. 计算时间              │
            │    │ 3. 设置定时器            │
            └────┼──────────────────────────┤
                 │ 定时器触发               │
                 │ 1. 显示通知              │
                 │ 2. 设置明天的定时器(递归) │
                 └──────────────────────────┘
关键点

1.当闹钟通知打开,是怎么实现闹钟通知每天都触发的?

Dart 复制代码
因为设置了递归
关键代码

// 5. 设置定时器
    _alarmTimer = Timer(delay, () async {
      //设置今天的闹钟
      await _showAlarmNotification();

      //立即设置明天的闹钟
      if (isSleepAlarmOpen && mounted) {
        // 等1秒后设置明天的闹钟
        Timer(const Duration(seconds: 1), () {
          _setAlarm(isRepeating: true);
        });
      }
    });

完整数据流

Dart 复制代码
12月15日 用户设置22:00闹钟
    ↓
第一次调用:_setAlarm()  // isRepeating = false
    ↓
计算:12月15日22:00
    ↓
设置Timer(到12月15日22:00)
    ↓
──────────────────────────────────
12月15日22:00 Timer触发
    ├─ 执行:_showAlarmNotification()  // 今天通知
    └─ 执行:Timer(1秒后) {
         _setAlarm(isRepeating: true)  // 第二次调用!
       }
    ↓
──────────────────────────────────
12月15日22:00:01
    ↓
第二次调用:_setAlarm(isRepeating: true)
    ↓
计算:12月16日22:00  // now.day + 1
    ↓
设置Timer(到12月16日22:00)
    ↓
──────────────────────────────────
12月16日22:00 Timer触发
    ├─ 执行:_showAlarmNotification()  // 明天通知
    └─ 执行:Timer(1秒后) {
         _setAlarm(isRepeating: true)  // 第三次调用!
       }
    ↓
──────────────────────────────────
12月16日22:00:01
    ↓
第三次调用:_setAlarm(isRepeating: true)
    ↓
计算:12月17日22:00  // now.day + 1
    ↓
设置Timer(到12月17日22:00)
    ↓
──────────────────────────────────
12月17日22:00 Timer触发
    ├─ 执行:_showAlarmNotification()  // 后天通知
    └─ 执行:Timer(1秒后) {
         _setAlarm(isRepeating: true)  // 第四次调用!
       }
    ↓
... 无限循环
实现步骤

1.定义一些变量

Dart 复制代码
  // 初始化通知插件
  late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

  // 就寝提醒时间
  int _selectedSleepRemindHour = 0; //用户选择的就寝提醒小时
  int _selectedSleepRemindMinute = 0;//用户设置的就寝提醒分钟
  bool isSleepAlarmOpen = false; //就寝提醒是否开启

  // Timer定时器
  Timer? _alarmTimer; //用于定时检查是否到达提醒时间的计时器

2.初始化

Dart 复制代码
  @override
  void initState() {
    super.initState();
    flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();//初始化通知
    _initializeNotifications(); //初始化通知
  }

  @override
  void dispose() {
    _alarmTimer?.cancel(); //取消定时器
    super.dispose();
  }

3.具体的初始化设置的方法

Dart 复制代码
 //=================================初始化通知设置================================
  Future<void> _initializeNotifications() async {


    //1.Android初始化设置:设置Android平台的通知配置
    const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');

    // 2.iOS初始化设置
    const DarwinInitializationSettings initializationSettingsIOS =
    DarwinInitializationSettings(
      requestAlertPermission: true, //请求显示弹窗权限
      requestBadgePermission: true, //请求角标权限
      requestSoundPermission: true, //请求声音权限
    );

    // 3.合并平台设置
    final InitializationSettings initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );

    //4. 初始化插件
    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) {
        print('通知被点击: ${response.payload}');
      },
    );

    // 5.创建通知通道:就寝通知通道
    const channels = [

      //就寝通知通道
      AndroidNotificationChannel(
        'sleep_alarm_channel', //通道ID
        '就寝提醒', //通道名称
        description: '就寝时间提醒',//通道描述
        importance: Importance.max, //重要性级别
      ),

    ];

    //创建通道
    for (var channel in channels) {
      try {
        await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
            ?.createNotificationChannel(channel);
        print('创建通知通道: ${channel.name}');
      } catch (e) {
        print('创建通道失败: $e');
      }
    }


  }

4.设置闹钟的方法

Dart 复制代码
//=================================设置闹钟的方法=============================
  Future<void> _setAlarm({bool isRepeating = false}) async {

    if (!isSleepAlarmOpen) return;

    // 1. 先取消之前的定时器
    _alarmTimer?.cancel();

    // 2. 检查权限
    if (!await Permission.notification.isGranted) {
      final status = await Permission.notification.request(); //权限获取
      if (!status.isGranted) { //权限未被允许
        _showErrorSnackBar('需要通知权限');
        setState(() => isSleepAlarmOpen = false);
        return;
      }
    }

    // 3. 计算触发时间
    final now = DateTime.now();

    //统一的计算逻辑
    DateTime scheduledTime = DateTime(
      now.year,
      now.month,
      now.day + (isRepeating ? 1 : 0),  // 重复就加1天
      _selectedSleepRemindHour,
      _selectedSleepRemindMinute,
    );

    //即使重复设置也要检查时间是否已过
    if (scheduledTime.isBefore(now)) {
      // 如果时间已过,再加1天
      scheduledTime = scheduledTime.add(const Duration(days: 1));
    }

    // 4. 计算延迟时间
    final delay = scheduledTime.difference(now);

    // 5. 设置定时器
    _alarmTimer = Timer(delay, () async {
      //设置今天的闹钟
      await _showAlarmNotification();

      //立即设置明天的闹钟
      if (isSleepAlarmOpen && mounted) {
        // 等1秒后设置明天的闹钟
        Timer(const Duration(seconds: 1), () {
          _setAlarm(isRepeating: true);
        });
      }
    });

    _showSuccessSnackBar('就寝提醒设置成功');
  }

5.显示闹钟的通知

Dart 复制代码
  //===================================显示闹钟的通知=================================
  Future<void> _showAlarmNotification() async {
    //安卓通知详情配置
    final AndroidNotificationDetails androidPlatformChannelSpecifics =
    AndroidNotificationDetails(
      'sleep_alarm_channel', //通道ID
      '就寝提醒', //通道名称
      channelDescription: '就寝时间提醒', //通道描述
      importance: Importance.max, //重要性
      priority: Priority.high, //优先级
      playSound: true, //播放声音
      enableVibration: true, //启用震动
      vibrationPattern: Int64List.fromList(const [0, 1000, 500, 1000]),
      color: Colors.blue,//通知颜色
      icon: '@mipmap/ic_launcher', // 如果有通知专用图标
      largeIcon: const DrawableResourceAndroidBitmap('@mipmap/ic_launcher'), // 大图标
    );

    //跨平台通知配置
    final NotificationDetails platformChannelSpecifics = NotificationDetails(
      android: androidPlatformChannelSpecifics,
      iOS: const DarwinNotificationDetails(),
    );

    //显示通知
    await flutterLocalNotificationsPlugin.show(
      0,
      '就寝提醒',
      '是时候放松一下了,酣眠时分到',
      platformChannelSpecifics,
      payload: 'sleep_alarm_channel', //使用通道
    );
  }

6.取消闹钟

Dart 复制代码
//=========================================取消闹钟===============================
  Future<void> _cancelAlarm() async {
    _alarmTimer?.cancel();
    await flutterLocalNotificationsPlugin.cancel(0);
    print('✅ 取消闹钟');
  }

7.测试的详细方法

Dart 复制代码
  //=====================================测试通知==================================
  Future<void> _testNotification() async {
    const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
      'sleep_alarm_channel',
      '就寝提醒',
      importance: Importance.max,
      priority: Priority.high,
    );

    const NotificationDetails details = NotificationDetails(
      android: androidDetails,
      iOS: DarwinNotificationDetails(),
    );

    await flutterLocalNotificationsPlugin.show(
      9999,
      '✅ 测试通知',
      '通知功能正常',
      details,
    );

    _showInfoSnackBar('测试通知已发送');
  }

8.就寝提醒的UI组件

Dart 复制代码
 //==================================就寝提醒UI组件============================
  Widget _buildSleepRemind() {
    return Column(
      children: [

        // 标题
        const Row(
          children: [
            SizedBox(width: 20),
            Icon(Icons.alarm, color: Color(0xFFA4A6B3)),
            SizedBox(width: 10),
            Text('就寝提醒', style: TextStyle(color: Color(0xFFA4A6B3), fontSize: 16)),
          ],
        ),
        const SizedBox(height: 10),
        // 文字描述
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 20),
          child: Text(
            '设置每日就寝时间提醒,帮助您养成良好的作息习惯',
            style: TextStyle(color: Color(0xFFA4A6B3), fontSize: 14),
          ),
        ),
        const SizedBox(height: 15),

        // 就寝提醒的闹钟
        GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTap: _buildChangeSleepTimeDialog,
          child: Container(
            margin: const EdgeInsets.symmetric(horizontal: 20),
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: const Color(0xFF25325D),
              borderRadius: BorderRadius.circular(10),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.2),
                  blurRadius: 8,
                  offset: const Offset(0, 4),
                ),
              ],
            ),
            child: Row(
              children: [
                // 左侧时间区域
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // 时间点
                      Text(
                        '${_selectedSleepRemindHour.toString().padLeft(2, '0')}:${_selectedSleepRemindMinute.toString().padLeft(2, '0')}',
                        style: TextStyle(
                          fontSize: 28,
                          fontWeight: FontWeight.bold,
                          color: isSleepAlarmOpen ? Colors.white : Colors.white.withOpacity(0.3),
                        ),
                      ),
                      const SizedBox(height: 4),
                      // 重复日期
                      Text(
                        '每天重复',
                        style: TextStyle(
                          fontSize: 13,
                          color: isSleepAlarmOpen ? Colors.blue[200] : Colors.grey[500],
                        ),
                      ),
                    ],
                  ),
                ),
                // 右侧开关
                Transform.scale(
                  scale: 1.2, //外层缩放
                  child: Switch(
                    value: isSleepAlarmOpen,//当前开关状态 : true→ 开关显示为开启状态,false→ 开关显示为关闭状态
                    onChanged: (bool value) async { //状态修改时的回调
                      //更新UI状态
                      setState(() {
                        isSleepAlarmOpen = value;
                      });

                      if (value) {
                        await _setAlarm();//开启闹钟
                      } else {
                        await _cancelAlarm();//关闭闹钟
                      }
                    },
                    activeColor: Colors.blue, //开启时滑块颜色
                    activeTrackColor: Colors.blue[300],//开启时轨道颜色
                    inactiveThumbColor: Colors.grey[600],//关闭时滑块颜色
                    inactiveTrackColor: Colors.grey[800],//关闭时轨道颜色
                  ),
                ),
              ],
            ),
          ),
        ),
        const SizedBox(height: 20),
        
        // 测试按钮
        Container(
          margin: const EdgeInsets.symmetric(horizontal: 20),
          child: Row(
            children: [
              //测试通知按钮
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: _testNotification,
                  icon: const Icon(Icons.notifications, size: 18),
                  label: const Text('测试通知', style: TextStyle(fontSize: 13)),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.blue[800],
                    foregroundColor: Colors.white,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8),
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 12),
              
              //一分钟测试按钮
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: () async {
                    // 测试1分钟后的闹钟
                    final now = DateTime.now();
                    _selectedSleepRemindHour = now.hour;
                    _selectedSleepRemindMinute = now.minute + 1;
                    if (_selectedSleepRemindMinute >= 60) {
                      _selectedSleepRemindHour = (_selectedSleepRemindHour + 1) % 24;
                      _selectedSleepRemindMinute = 0;
                    }

                    setState(() {
                      isSleepAlarmOpen = true;
                    });
                    
                    await _setAlarm();
                    _showInfoSnackBar('已设置1分钟后测试');
                  },
                  icon: const Icon(Icons.timer, size: 18),
                  label: const Text('1分钟测试', style: TextStyle(fontSize: 13)),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.green[800],
                    foregroundColor: Colors.white,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

9.选择时间的弹窗

Dart 复制代码
  //===================================选择时间的弹窗==============================
  void _buildChangeSleepTimeDialog() {

    // 创建临时变量来保存弹窗中的选择
    int tempSelectedHour = _selectedSleepRemindHour;
    int tempSelectedMinute = _selectedSleepRemindMinute;

    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      builder: (context) {
        return Container(
          height: 450,
          decoration: const BoxDecoration(
            color: Color(0xFF1A2342),
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(20),
              topRight: Radius.circular(20),
            ),
          ),
          child: Column(
            children: [
              SizedBox(height: 30,),
              //标题栏
              Row(
                children: [
                  const SizedBox(width: 23,),
                  // Opacity(opacity:0,child: Image.asset("assets/images/setting.png"),),
                  const Spacer(),
                  Text("设置就寝时间",style: const TextStyle(color: Colors.white,fontSize: 22),),
                  const Spacer(),
                  GestureDetector(
                    onTap: (){
                      Navigator.pop(context);
                    },
                    child: const Icon(
                      Icons.close,
                      color: Colors.white,
                      size: 24, // 可以根据需要调整大小
                    ),
                  ),
                  const SizedBox(width: 23,)
                ],
              ),

              // 时间选择器
              Expanded(
                child: Container(
                  margin: const EdgeInsets.only(top: 20),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      // 小时选择
                      Container(
                        height: 120,
                        width: 80,
                        child: CupertinoPicker(
                          itemExtent: 40,
                          scrollController: FixedExtentScrollController(initialItem: tempSelectedHour),
                          onSelectedItemChanged: (int index){
                            tempSelectedHour = index;
                          },
                          selectionOverlay: null,
                          children: List.generate(24, (index) {
                            return Center(
                              child: Text(
                                index.toString().padLeft(2, '0'),
                                style: TextStyle(fontSize: 24, color: Colors.white),
                              ),
                            );
                          }),
                        ),
                      ),

                      // 分钟选择
                      Container(
                        height: 120,
                        width: 80,
                        child: CupertinoPicker(
                          itemExtent: 40,
                          scrollController: FixedExtentScrollController(initialItem: tempSelectedMinute),
                          onSelectedItemChanged: (int index){
                            tempSelectedMinute= index;
                          },
                          selectionOverlay: null,
                          children: List.generate(60, (index) {
                            return Center(
                              child: Text(
                                index.toString().padLeft(2, '0'),
                                style: TextStyle(fontSize: 24, color: Colors.white),
                              ),
                            );
                          }),
                        ),
                      ),
                    ],
                  ),
                ),
              ),

              //保存按钮
              GestureDetector(
                  onTap: (){
                    setState(() {
                      _selectedSleepRemindHour = tempSelectedHour;
                      _selectedSleepRemindMinute = tempSelectedMinute;
                      isSleepAlarmOpen = true;
                    });
                    Navigator.pop(context);
                    _setAlarm();//设置就寝提醒
                  },
                  child: Container(
                    height: 52,
                    width: 325,
                    decoration: BoxDecoration(
                      color: const Color(0xFF0D6BDF),
                      borderRadius:BorderRadius.circular(26),
                    ),
                    child: Center(
                      child: Text("保存",style: TextStyle(color: Colors.white,fontSize: 15),),
                    ),
                  )
              ),

              const SizedBox(height: 40,),
            ],
          ),
        );
      },
    );
  }

10.一些提示方法

Dart 复制代码
  //==============================SnackBar辅助方法=================================
  void _showSuccessSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.green,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  void _showErrorSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  void _showInfoSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.blue,
        duration: const Duration(seconds: 2),
      ),
    );
  }

11.UI主体

Dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        padding: EdgeInsets.only(top: 50),
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Color(0xFF0F162B), Color(0xFF1A2342)],
          ),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const SizedBox(height: 20),
            _buildSleepRemind(),
            const SizedBox(height: 30),
            // 使用说明
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 20),
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.black.withOpacity(0.2),
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: Colors.blue.withOpacity(0.3)),
              ),
              child: const Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Icon(Icons.info, color: Colors.blue, size: 20),
                      SizedBox(width: 8),
                      Text('使用说明', style: TextStyle(color: Colors.white, fontSize: 16)),
                    ],
                  ),
                  SizedBox(height: 8),
                  Text(
                    '1. 设置就寝时间后,每天会在指定时间提醒您\n'
                        '2. 需要授予通知权限才能正常工作\n'
                        '3. 应用会在后台保持服务以确保准时提醒\n'
                        '4. 点击通知可查看详细信息',
                    style: TextStyle(color: Color(0xFFA4A6B3), fontSize: 13),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

代码实例

Dart 复制代码
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'package:permission_handler/permission_handler.dart';

class DemoPage extends StatefulWidget {
  const DemoPage({Key? key}) : super(key: key);

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {


  // 初始化通知插件
  late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;

  // 就寝提醒时间
  int _selectedSleepRemindHour = 0; //用户选择的就寝提醒小时
  int _selectedSleepRemindMinute = 0;//用户设置的就寝提醒分钟
  bool isSleepAlarmOpen = false; //就寝提醒是否开启

  // Timer定时器
  Timer? _alarmTimer; //用于定时检查是否到达提醒时间的计时器


  @override
  void initState() {
    super.initState();
    flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();//初始化通知
    _initializeNotifications(); //初始化通知
  }

  @override
  void dispose() {
    _alarmTimer?.cancel(); //取消定时器
    super.dispose();
  }

  //=================================初始化通知设置================================
  Future<void> _initializeNotifications() async {


    //1.Android初始化设置:设置Android平台的通知配置
    const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');

    // 2.iOS初始化设置
    const DarwinInitializationSettings initializationSettingsIOS =
    DarwinInitializationSettings(
      requestAlertPermission: true, //请求显示弹窗权限
      requestBadgePermission: true, //请求角标权限
      requestSoundPermission: true, //请求声音权限
    );

    // 3.合并平台设置
    final InitializationSettings initializationSettings = InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );

    //4. 初始化插件
    await flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) {
        print('通知被点击: ${response.payload}');
      },
    );

    // 5.创建通知通道:就寝通知通道
    const channels = [

      //就寝通知通道
      AndroidNotificationChannel(
        'sleep_alarm_channel', //通道ID
        '就寝提醒', //通道名称
        description: '就寝时间提醒',//通道描述
        importance: Importance.max, //重要性级别
      ),

    ];

    //创建通道
    for (var channel in channels) {
      try {
        await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
            ?.createNotificationChannel(channel);
        print('创建通知通道: ${channel.name}');
      } catch (e) {
        print('创建通道失败: $e');
      }
    }


  }


//=================================设置闹钟的方法=============================
  Future<void> _setAlarm({bool isRepeating = false}) async {

    if (!isSleepAlarmOpen) return;

    // 1. 先取消之前的定时器
    _alarmTimer?.cancel();

    // 2. 检查权限
    if (!await Permission.notification.isGranted) {
      final status = await Permission.notification.request(); //权限获取
      if (!status.isGranted) { //权限未被允许
        _showErrorSnackBar('需要通知权限');
        setState(() => isSleepAlarmOpen = false);
        return;
      }
    }

    // 3. 计算触发时间
    final now = DateTime.now();

    //统一的计算逻辑
    DateTime scheduledTime = DateTime(
      now.year,
      now.month,
      now.day + (isRepeating ? 1 : 0),  // 重复就加1天
      _selectedSleepRemindHour,
      _selectedSleepRemindMinute,
    );

    //即使重复设置也要检查时间是否已过
    if (scheduledTime.isBefore(now)) {
      // 如果时间已过,再加1天
      scheduledTime = scheduledTime.add(const Duration(days: 1));
    }

    // 4. 计算延迟时间
    final delay = scheduledTime.difference(now);

    // 5. 设置定时器
    _alarmTimer = Timer(delay, () async {
      //设置今天的闹钟
      await _showAlarmNotification();

      //立即设置明天的闹钟
      if (isSleepAlarmOpen && mounted) {
        // 等1秒后设置明天的闹钟
        Timer(const Duration(seconds: 1), () {
          _setAlarm(isRepeating: true);
        });
      }
    });

    _showSuccessSnackBar('就寝提醒设置成功');
  }


  //===================================显示闹钟的通知=================================
  Future<void> _showAlarmNotification() async {
    //安卓通知详情配置
    final AndroidNotificationDetails androidPlatformChannelSpecifics =
    AndroidNotificationDetails(
      'sleep_alarm_channel', //通道ID
      '就寝提醒', //通道名称
      channelDescription: '就寝时间提醒', //通道描述
      importance: Importance.max, //重要性
      priority: Priority.high, //优先级
      playSound: true, //播放声音
      enableVibration: true, //启用震动
      vibrationPattern: Int64List.fromList(const [0, 1000, 500, 1000]),
      color: Colors.blue,//通知颜色
      icon: '@mipmap/ic_launcher', // 如果有通知专用图标
      largeIcon: const DrawableResourceAndroidBitmap('@mipmap/ic_launcher'), // 大图标
    );

    //跨平台通知配置
    final NotificationDetails platformChannelSpecifics = NotificationDetails(
      android: androidPlatformChannelSpecifics,
      iOS: const DarwinNotificationDetails(),
    );

    //显示通知
    await flutterLocalNotificationsPlugin.show(
      0,
      '就寝提醒',
      '是时候放松一下了,酣眠时分到',
      platformChannelSpecifics,
      payload: 'sleep_alarm_channel', //使用通道
    );
  }



  //=========================================取消闹钟===============================
  Future<void> _cancelAlarm() async {
    _alarmTimer?.cancel();
    await flutterLocalNotificationsPlugin.cancel(0);
    print('✅ 取消闹钟');
  }


  //=====================================测试通知==================================
  Future<void> _testNotification() async {
    const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
      'sleep_alarm_channel',
      '就寝提醒',
      importance: Importance.max,
      priority: Priority.high,
    );

    const NotificationDetails details = NotificationDetails(
      android: androidDetails,
      iOS: DarwinNotificationDetails(),
    );

    await flutterLocalNotificationsPlugin.show(
      9999,
      '✅ 测试通知',
      '通知功能正常',
      details,
    );

    _showInfoSnackBar('测试通知已发送');
  }

  //==================================就寝提醒UI组件============================
  Widget _buildSleepRemind() {
    return Column(
      children: [

        // 标题
        const Row(
          children: [
            SizedBox(width: 20),
            Icon(Icons.alarm, color: Color(0xFFA4A6B3)),
            SizedBox(width: 10),
            Text('就寝提醒', style: TextStyle(color: Color(0xFFA4A6B3), fontSize: 16)),
          ],
        ),
        const SizedBox(height: 10),
        // 文字描述
        const Padding(
          padding: EdgeInsets.symmetric(horizontal: 20),
          child: Text(
            '设置每日就寝时间提醒,帮助您养成良好的作息习惯',
            style: TextStyle(color: Color(0xFFA4A6B3), fontSize: 14),
          ),
        ),
        const SizedBox(height: 15),

        // 就寝提醒的闹钟
        GestureDetector(
          behavior: HitTestBehavior.opaque,
          onTap: _buildChangeSleepTimeDialog,
          child: Container(
            margin: const EdgeInsets.symmetric(horizontal: 20),
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: const Color(0xFF25325D),
              borderRadius: BorderRadius.circular(10),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.2),
                  blurRadius: 8,
                  offset: const Offset(0, 4),
                ),
              ],
            ),
            child: Row(
              children: [
                // 左侧时间区域
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // 时间点
                      Text(
                        '${_selectedSleepRemindHour.toString().padLeft(2, '0')}:${_selectedSleepRemindMinute.toString().padLeft(2, '0')}',
                        style: TextStyle(
                          fontSize: 28,
                          fontWeight: FontWeight.bold,
                          color: isSleepAlarmOpen ? Colors.white : Colors.white.withOpacity(0.3),
                        ),
                      ),
                      const SizedBox(height: 4),
                      // 重复日期
                      Text(
                        '每天重复',
                        style: TextStyle(
                          fontSize: 13,
                          color: isSleepAlarmOpen ? Colors.blue[200] : Colors.grey[500],
                        ),
                      ),
                    ],
                  ),
                ),
                // 右侧开关
                Transform.scale(
                  scale: 1.2, //外层缩放
                  child: Switch(
                    value: isSleepAlarmOpen,//当前开关状态 : true→ 开关显示为开启状态,false→ 开关显示为关闭状态
                    onChanged: (bool value) async { //状态修改时的回调
                      //更新UI状态
                      setState(() {
                        isSleepAlarmOpen = value;
                      });

                      if (value) {
                        await _setAlarm();//开启闹钟
                      } else {
                        await _cancelAlarm();//关闭闹钟
                      }
                    },
                    activeColor: Colors.blue, //开启时滑块颜色
                    activeTrackColor: Colors.blue[300],//开启时轨道颜色
                    inactiveThumbColor: Colors.grey[600],//关闭时滑块颜色
                    inactiveTrackColor: Colors.grey[800],//关闭时轨道颜色
                  ),
                ),
              ],
            ),
          ),
        ),
        const SizedBox(height: 20),

        // 测试按钮
        Container(
          margin: const EdgeInsets.symmetric(horizontal: 20),
          child: Row(
            children: [
              //测试通知按钮
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: _testNotification,
                  icon: const Icon(Icons.notifications, size: 18),
                  label: const Text('测试通知', style: TextStyle(fontSize: 13)),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.blue[800],
                    foregroundColor: Colors.white,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8),
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 12),

              //一分钟测试按钮
              Expanded(
                child: ElevatedButton.icon(
                  onPressed: () async {
                    // 测试1分钟后的闹钟
                    final now = DateTime.now();
                    _selectedSleepRemindHour = now.hour;
                    _selectedSleepRemindMinute = now.minute + 1;
                    if (_selectedSleepRemindMinute >= 60) {
                      _selectedSleepRemindHour = (_selectedSleepRemindHour + 1) % 24;
                      _selectedSleepRemindMinute = 0;
                    }

                    setState(() {
                      isSleepAlarmOpen = true;
                    });

                    await _setAlarm();
                    _showInfoSnackBar('已设置1分钟后测试');
                  },
                  icon: const Icon(Icons.timer, size: 18),
                  label: const Text('1分钟测试', style: TextStyle(fontSize: 13)),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.green[800],
                    foregroundColor: Colors.white,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8),
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }


  //===================================选择时间的弹窗==============================
  void _buildChangeSleepTimeDialog() {

    // 创建临时变量来保存弹窗中的选择
    int tempSelectedHour = _selectedSleepRemindHour;
    int tempSelectedMinute = _selectedSleepRemindMinute;

    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      builder: (context) {
        return Container(
          height: 450,
          decoration: const BoxDecoration(
            color: Color(0xFF1A2342),
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(20),
              topRight: Radius.circular(20),
            ),
          ),
          child: Column(
            children: [
              SizedBox(height: 30,),
              //标题栏
              Row(
                children: [
                  const SizedBox(width: 23,),
                  // Opacity(opacity:0,child: Image.asset("assets/images/setting.png"),),
                  const Spacer(),
                  Text("设置就寝时间",style: const TextStyle(color: Colors.white,fontSize: 22),),
                  const Spacer(),
                  GestureDetector(
                    onTap: (){
                      Navigator.pop(context);
                    },
                    child: const Icon(
                      Icons.close,
                      color: Colors.white,
                      size: 24, // 可以根据需要调整大小
                    ),
                  ),
                  const SizedBox(width: 23,)
                ],
              ),

              // 时间选择器
              Expanded(
                child: Container(
                  margin: const EdgeInsets.only(top: 20),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      // 小时选择
                      Container(
                        height: 120,
                        width: 80,
                        child: CupertinoPicker(
                          itemExtent: 40,
                          scrollController: FixedExtentScrollController(initialItem: tempSelectedHour),
                          onSelectedItemChanged: (int index){
                            tempSelectedHour = index;
                          },
                          selectionOverlay: null,
                          children: List.generate(24, (index) {
                            return Center(
                              child: Text(
                                index.toString().padLeft(2, '0'),
                                style: TextStyle(fontSize: 24, color: Colors.white),
                              ),
                            );
                          }),
                        ),
                      ),

                      // 分钟选择
                      Container(
                        height: 120,
                        width: 80,
                        child: CupertinoPicker(
                          itemExtent: 40,
                          scrollController: FixedExtentScrollController(initialItem: tempSelectedMinute),
                          onSelectedItemChanged: (int index){
                            tempSelectedMinute= index;
                          },
                          selectionOverlay: null,
                          children: List.generate(60, (index) {
                            return Center(
                              child: Text(
                                index.toString().padLeft(2, '0'),
                                style: TextStyle(fontSize: 24, color: Colors.white),
                              ),
                            );
                          }),
                        ),
                      ),
                    ],
                  ),
                ),
              ),

              //保存按钮
              GestureDetector(
                  onTap: (){
                    setState(() {
                      _selectedSleepRemindHour = tempSelectedHour;
                      _selectedSleepRemindMinute = tempSelectedMinute;
                      isSleepAlarmOpen = true;
                    });
                    Navigator.pop(context);
                    _setAlarm();//设置就寝提醒
                  },
                  child: Container(
                    height: 52,
                    width: 325,
                    decoration: BoxDecoration(
                      color: const Color(0xFF0D6BDF),
                      borderRadius:BorderRadius.circular(26),
                    ),
                    child: Center(
                      child: Text("保存",style: TextStyle(color: Colors.white,fontSize: 15),),
                    ),
                  )
              ),

              const SizedBox(height: 40,),
            ],
          ),
        );
      },
    );
  }

  //==============================SnackBar辅助方法=================================
  void _showSuccessSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.green,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  void _showErrorSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  void _showInfoSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.blue,
        duration: const Duration(seconds: 2),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        padding: EdgeInsets.only(top: 50),
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Color(0xFF0F162B), Color(0xFF1A2342)],
          ),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const SizedBox(height: 20),
            _buildSleepRemind(),
            const SizedBox(height: 30),
            // 使用说明
            Container(
              margin: const EdgeInsets.symmetric(horizontal: 20),
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.black.withOpacity(0.2),
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: Colors.blue.withOpacity(0.3)),
              ),
              child: const Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Icon(Icons.info, color: Colors.blue, size: 20),
                      SizedBox(width: 8),
                      Text('使用说明', style: TextStyle(color: Colors.white, fontSize: 16)),
                    ],
                  ),
                  SizedBox(height: 8),
                  Text(
                    '1. 设置就寝时间后,每天会在指定时间提醒您\n'
                        '2. 需要授予通知权限才能正常工作\n'
                        '3. 应用会在后台保持服务以确保准时提醒\n'
                        '4. 点击通知可查看详细信息',
                    style: TextStyle(color: Color(0xFFA4A6B3), fontSize: 13),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }


}
相关推荐
程序员Ctrl喵4 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难5 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡6 小时前
flutter列表中实现置顶动画
flutter
始持7 小时前
第十二讲 风格与主题统一
前端·flutter
始持7 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持7 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜7 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴8 小时前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区8 小时前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎9 小时前
树形选择器组件封装
前端·flutter