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),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }


}
相关推荐
结局无敌7 小时前
Flutter跨平台开发:从原生交互到全端适配的实战拆解
flutter·交互
山屿落星辰7 小时前
Flutter 状态管理终极指南(一):从 setState 到 Riverpod 2.0
flutter·交互
遝靑7 小时前
Flutter 状态管理深度解析:Provider 与 Riverpod 核心原理及实战对比
flutter
小a杰.7 小时前
Flutter 图片内存优化指南(完整版)
jvm·flutter
鹏多多8 小时前
flutter使用package_info_plus库获取应用信息的教程
android·前端·flutter
走在路上的菜鸟8 小时前
Android学Dart学习笔记第十五节 类
android·笔记·学习·flutter
BigPomme8 小时前
Flutter IOS 出现实机运行问题和transpoter传包异常
flutter·ios
嗝o゚8 小时前
Flutter引擎裁剪与鸿蒙方舟编译协同优化
flutter·华为·harmonyos