rtc_prd/airhub_app/lib/widgets/glass_dialog.dart
seaislee1209 f9666d4aa3 feat: UI规范化 + 故事吸入动画 + 音乐页面优化
- 全局字体统一(Outfit/DM Sans), 头部/按钮/Toast规范化
- 故事详情页: Genie Suck吸入动画(标题+卡片一起缩小模糊消失)
- 书架页: bookPop弹出+粒子效果(三段式动画完整链路)
- 音乐页面: 心情卡片emoji换Material图标+彩色圆块横排布局
- 音乐页面: 进度条胶囊宽度对齐, 播放按钮位置修复, 间距均匀化
- 音乐播放: 接入just_audio, 支持播放暂停进度拖拽自动切歌
- 新增: iOS风格毛玻璃Toast, 渐变背景组件, 通知页面
- 阶段总结文档更新

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 19:34:53 +08:00

197 lines
6.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'gradient_button.dart';
import '../theme/app_colors.dart' as appclr;
class GlassDialog extends StatelessWidget {
final String title;
final String? description;
final Widget? content; // For custom content like TextField
final String cancelText;
final String confirmText;
final VoidCallback onCancel;
final VoidCallback onConfirm;
final bool
isDanger; // If we need a specific danger style, though CSS shows Pink Gradient default
const GlassDialog({
super.key,
required this.title,
this.description,
this.content,
this.cancelText = '取消',
this.confirmText = '确定',
required this.onCancel,
required this.onConfirm,
this.isDanger = false,
});
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.transparent,
elevation: 0,
insetPadding: const EdgeInsets.symmetric(horizontal: 40),
child: Container(
// Clean white card style
width: double.infinity,
constraints: const BoxConstraints(maxWidth: 300),
padding: const EdgeInsets.fromLTRB(24, 32, 24, 24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
offset: const Offset(0, 10),
blurRadius: 30,
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Title
Text(
title,
style: GoogleFonts.outfit(
fontSize: 20,
fontWeight: FontWeight.w600,
color: const Color(0xFF4B2404),
height: 1.2,
letterSpacing: -0.5,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// Description
if (description != null)
Padding(
padding: const EdgeInsets.only(bottom: 24),
child: Text(
description!,
style: const TextStyle(
fontSize: 15,
color: Color(0xFF6B7280),
height: 1.6,
),
textAlign: TextAlign.center,
),
),
// Custom Content
if (content != null)
Padding(
padding: const EdgeInsets.only(bottom: 24),
child: content!,
),
// Button (Confirm only design often used in modals)
// But preserving Row for Cancel if needed, though PRD screenshot shows single "Confirm" style mostly.
// Screenshot 1 (Help): Single "Confirm" button.
// Screenshot 2 (Bind Phone): "Confirm" button.
// Let's keep Row but make Confirm prominent.
if (cancelText.isEmpty || onCancel == () {}) ...[
// Single Button Layout
GradientButton(
text: confirmText,
height: 48,
gradient: appclr.AppColors.btnPlushGradient,
onPressed: onConfirm,
),
] else ...[
// Two Buttons Layout
Row(
children: [
Expanded(
child: GestureDetector(
onTap: onCancel,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: const Color(0xFFF3F4F6),
borderRadius: BorderRadius.circular(24),
),
alignment: Alignment.center,
child: Text(
cancelText,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Color(0xFF6B7280),
),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: GradientButton(
text: confirmText,
height: 44,
gradient: appclr.AppColors.btnPlushGradient,
onPressed: onConfirm,
),
),
],
),
],
],
),
),
);
}
}
// Helper to show the dialog with animation scaling
Future<T?> showGlassDialog<T>({
required BuildContext context,
required String title,
String? description,
Widget? content,
String cancelText = '取消',
String confirmText = '确定',
required VoidCallback onConfirm,
bool isDanger = false,
}) {
return showGeneralDialog<T>(
context: context,
barrierDismissible: true,
barrierLabel: 'Dismiss',
barrierColor: Colors.black.withOpacity(
0.4,
), // .modal-overlay background: rgba(0,0,0,0.4)
// Actually modal-overlay in CSS might be defined in lines I didn't see.
// Assuming standard dim.
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, anim1, anim2) {
return GlassDialog(
title: title,
description: description,
content: content,
cancelText: cancelText,
confirmText: confirmText,
onCancel: () => Navigator.of(context).pop(),
onConfirm: onConfirm,
isDanger: isDanger,
);
},
transitionBuilder: (context, anim1, anim2, child) {
// CSS: transform: scale(0.9) -> scale(1)
// cubic-bezier(0.175, 0.885, 0.32, 1.275)
// Actually standard ScaleTransition with curve is easier
return ScaleTransition(
scale: Tween<double>(begin: 0.9, end: 1.0).animate(
CurvedAnimation(
parent: anim1,
curve: const Cubic(0.175, 0.885, 0.32, 1.275),
),
),
child: FadeTransition(opacity: anim1, child: child),
);
},
);
}