import 'package:flutter/material.dart'; import 'package:airhub_app/theme/design_tokens.dart'; import 'package:airhub_app/widgets/animated_gradient_background.dart'; /// 消息通知页面 — 还原 notifications.html class NotificationPage extends StatefulWidget { const NotificationPage({super.key}); @override State createState() => _NotificationPageState(); } class _NotificationPageState extends State { /// 当前展开的通知 index(-1 表示全部折叠,手风琴模式) int _expandedIndex = -1; /// 已读标记(index set) final Set _readIndices = {}; /// 示例通知数据 final List<_NotificationData> _notifications = [ _NotificationData( type: _NotifType.system, icon: Icons.warning_amber_rounded, title: '系统更新', time: '10:30', desc: 'Airhub V1.2.0 版本更新已准备就绪', detail: 'Airhub V1.2.0 版本更新说明:\n\n' '• 新增"喂养指南"功能,现在您可以查看详细的电子宠物养成手册了。\n' '• 优化了设备连接的稳定性,修复了部分机型搜索不到设备的问题。\n' '• 提升了整体界面的流畅度,增加了更多微交互动画。\n\n' '建议您连接 Wi-Fi 后进行更新,以获得最佳体验。', isUnread: true, ), _NotificationData( type: _NotifType.activity, emojiIcon: '🎁', title: '新春活动', time: '昨天', desc: '领取您的新春限定水豚皮肤"招财进宝"', detail: '🎉 新春限定皮肤上线啦!\n\n' '为了庆祝即将到来的春节,我们特别推出了水豚的"招财进宝"限定皮肤。\n\n' '活动亮点:\n' '• 限定版红色唐装外观\n' '• 专属的春节互动音效\n' '• 限时免费领取的节庆道具\n\n' '活动截止日期:2月15日', isUnread: false, ), _NotificationData( type: _NotifType.system, icon: Icons.person_add_alt_1_outlined, title: '新设备绑定', time: '1月20日', desc: '您的新设备"Airhub_5G"已成功绑定', detail: '恭喜!您已成功绑定新设备 Airhub_5G。\n\n' '接下来的几步可以帮助您快速上手:\n' '• 前往角色记忆页面,注入您喜欢的角色人格。\n' '• 进入设置页面配置您的偏好设置。\n' '• 查看帮助中心的入门指南,解锁更多互动玩法。\n\n' '祝您开启一段奇妙的 AI 陪伴旅程!', isUnread: false, ), ]; void _toggleNotification(int index) { setState(() { if (_expandedIndex == index) { _expandedIndex = -1; // 折叠 } else { _expandedIndex = index; // 展开,并标记已读 _readIndices.add(index); } }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.transparent, body: Stack( children: [ const AnimatedGradientBackground(), Column( children: [ _buildHeader(context), Expanded( child: ShaderMask( shaderCallback: (Rect rect) { return const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black, Colors.black, Colors.transparent, ], stops: [0.0, 0.03, 0.95, 1.0], ).createShader(rect); }, blendMode: BlendMode.dstIn, child: _notifications.isEmpty ? _buildEmptyState() : _buildNotificationList(context), ), ), ], ), ], ), ); } // ─── Header ─── Widget _buildHeader(BuildContext context) { return Container( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 20, left: AppSpacing.lg, right: AppSpacing.lg, bottom: AppSpacing.md, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: () => Navigator.pop(context), child: Container( width: 40, height: 40, decoration: BoxDecoration( color: AppColors.iconBtnBg, borderRadius: BorderRadius.circular(AppRadius.button), ), child: const Icon( Icons.arrow_back_ios_new, color: AppColors.textPrimary, size: 18, ), ), ), Text('消息通知', style: AppTextStyles.title), const SizedBox(width: 40), // 右侧占位保持标题居中 ], ), ); } // ─── 空状态 ─── Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 100, height: 100, decoration: BoxDecoration( color: const Color(0x1A9CA3AF), shape: BoxShape.circle, ), child: const Icon( Icons.notifications_none_rounded, size: 48, color: AppColors.textSecondary, ), ), const SizedBox(height: 20), const Text( '暂无新消息', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: AppColors.textSecondary, ), ), const SizedBox(height: 8), const Text( '新的通知会在这里显示', style: TextStyle( fontSize: 13, color: AppColors.textHint, ), ), ], ), ); } // ─── 通知列表 ─── Widget _buildNotificationList(BuildContext context) { return ListView.builder( padding: EdgeInsets.only( top: 8, left: 20, right: 20, bottom: 40 + MediaQuery.of(context).padding.bottom, ), itemCount: _notifications.length, itemBuilder: (context, index) { return _buildNotificationCard(index); }, ); } // ─── 单条通知卡片 ─── Widget _buildNotificationCard(int index) { final notif = _notifications[index]; final isExpanded = _expandedIndex == index; final isUnread = notif.isUnread && !_readIndices.contains(index); return Padding( padding: const EdgeInsets.only(bottom: 12), child: AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, decoration: BoxDecoration( color: isExpanded ? const Color(0xD9FFFFFF) // 0.85 opacity : const Color(0xB3FFFFFF), // 0.7 opacity borderRadius: BorderRadius.circular(16), border: Border.all( color: const Color(0x66FFFFFF), // rgba(255,255,255,0.4) ), boxShadow: const [AppShadows.card], ), child: Column( children: [ // ── 卡片头部(可点击) ── GestureDetector( onTap: () => _toggleNotification(index), behavior: HitTestBehavior.opaque, child: Padding( padding: const EdgeInsets.all(16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 图标 _buildNotifIcon(notif), const SizedBox(width: 14), // 文字区域 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( notif.title, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), Text( notif.time, style: const TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ], ), const SizedBox(height: 4), Text( notif.desc, style: const TextStyle( fontSize: 13, color: Color(0xFF6B7280), height: 1.5, ), ), ], ), ), const SizedBox(width: 8), // 箭头 + 未读红点 Column( children: [ AnimatedRotation( turns: isExpanded ? 0.25 : 0, duration: const Duration(milliseconds: 300), child: const Icon( Icons.chevron_right, color: AppColors.textHint, size: 20, ), ), if (isUnread) ...[ const SizedBox(height: 6), Container( width: 8, height: 8, decoration: const BoxDecoration( color: AppColors.notificationDot, shape: BoxShape.circle, ), ), ], ], ), ], ), ), ), // ── 展开详情区域 ── AnimatedCrossFade( firstChild: const SizedBox.shrink(), secondChild: Container( width: double.infinity, decoration: const BoxDecoration( color: Color(0x80F9FAFB), // rgba(249, 250, 251, 0.5) border: Border( top: BorderSide( color: Color(0x0D000000), // rgba(0,0,0,0.05) ), ), ), padding: const EdgeInsets.all(20), child: Text( notif.detail, style: const TextStyle( fontSize: 14, color: Color(0xFF374151), height: 1.7, ), ), ), crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, duration: const Duration(milliseconds: 300), sizeCurve: Curves.easeInOut, ), ], ), ), ); } // ─── 通知图标 ─── Widget _buildNotifIcon(_NotificationData notif) { final isSystem = notif.type == _NotifType.system; return Container( width: 40, height: 40, decoration: BoxDecoration( color: isSystem ? const Color(0xFFEFF6FF) // 蓝色系统背景 : const Color(0xFFFFF7ED), // 橙色活动背景 borderRadius: BorderRadius.circular(20), ), alignment: Alignment.center, child: notif.emojiIcon != null ? Text( notif.emojiIcon!, style: const TextStyle(fontSize: 18), ) : Icon( notif.icon ?? Icons.info_outline, size: 20, color: isSystem ? const Color(0xFF3B82F6) : const Color(0xFFF97316), ), ); } } // ─── 数据模型 ─── enum _NotifType { system, activity } class _NotificationData { final _NotifType type; final IconData? icon; final String? emojiIcon; final String title; final String time; final String desc; final String detail; final bool isUnread; _NotificationData({ required this.type, this.icon, this.emojiIcon, required this.title, required this.time, required this.desc, required this.detail, this.isUnread = false, }); }