import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:sunny_mochi/core/crash/crash_datasource.dart'; import 'package:sunny_mochi/core/crash/pending_crash_report.dart'; import 'package:sunny_mochi/core/extensions/context_x.dart'; import 'package:sunny_mochi/core/widgets/app_toast.dart'; /// 崩溃报告对话框 — 下次启动时强制展示。 /// /// 用户体验: /// - "发送报告":POST 上报 → 关闭弹窗,后台继续正常启动 /// - "跳过":直接关闭弹窗,继续正常启动 class CrashDialog extends ConsumerStatefulWidget { const CrashDialog({super.key, required this.report}); final PendingCrashReport report; @override ConsumerState createState() => _CrashDialogState(); } class _CrashDialogState extends ConsumerState { bool _sending = false; bool _showDetail = false; Future _send() async { setState(() => _sending = true); try { await ref .read(crashDatasourceProvider) .sendCrashReport(widget.report); if (mounted) { Navigator.of(context).pop(); context.showToast('崩溃报告已发送,感谢您的反馈', type: ToastType.success); } } catch (e, st) { if (mounted) { setState(() => _sending = false); context.showError(ref, e, st); } } } @override Widget build(BuildContext context) { final report = widget.report; final time = report.occurredAtLocal; final timeStr = time == null ? '未知时间' : '${time.year}-${time.month.toString().padLeft(2, '0')}-' '${time.day.toString().padLeft(2, '0')} ' '${time.hour.toString().padLeft(2, '0')}:' '${time.minute.toString().padLeft(2, '0')}'; return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), title: Row( children: [ Icon( Icons.warning_amber_rounded, color: const Color(0xFFFF9F0A), size: 22.sp, ), SizedBox(width: 8.w), Text( '上次发生了意外退出', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), ), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '应用在 $timeStr 崩溃退出。发送崩溃报告有助于我们修复问题,报告不包含您的个人数据。', style: TextStyle( fontSize: 14.sp, color: const Color(0xFF636366), height: 1.5, ), ), SizedBox(height: 12.h), _InfoRow('设备', '${report.deviceModel} / ${report.osVersion}'), _InfoRow('版本', '${report.appVersion}+${report.appBuild}'), SizedBox(height: 8.h), GestureDetector( onTap: () => setState(() => _showDetail = !_showDetail), child: Row( children: [ Text( '错误详情', style: TextStyle( fontSize: 13.sp, color: Theme.of(context).colorScheme.primary, fontWeight: FontWeight.w500, ), ), Icon( _showDetail ? Icons.expand_less_rounded : Icons.expand_more_rounded, size: 18.sp, color: Theme.of(context).colorScheme.primary, ), ], ), ), if (_showDetail) ...[ SizedBox(height: 8.h), Container( constraints: BoxConstraints(maxHeight: 160.h), decoration: BoxDecoration( color: const Color(0xFFF2F2F7), borderRadius: BorderRadius.circular(8.r), ), child: SingleChildScrollView( padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h), child: Text( '${report.error}\n\n${_shortStack(report.stackTrace)}', style: TextStyle( fontSize: 11.sp, color: const Color(0xFF3A3A3C), fontFamily: 'monospace', height: 1.4, ), ), ), ), ], ], ), actions: [ TextButton( onPressed: _sending ? null : () => Navigator.of(context).pop(), child: Text( '跳过', style: TextStyle( color: const Color(0xFF8E8E93), fontSize: 15.sp, ), ), ), FilledButton( onPressed: _sending ? null : _send, child: _sending ? SizedBox( width: 16.w, height: 16.w, child: const CircularProgressIndicator.adaptive( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Text('发送报告', style: TextStyle(fontSize: 15.sp)), ), ], ); } String _shortStack(String stack) { final lines = stack.split('\n'); if (lines.length <= 20) return stack; return '${lines.take(20).join('\n')}\n...(${lines.length - 20} more lines)'; } } class _InfoRow extends StatelessWidget { const _InfoRow(this.label, this.value); final String label; final String value; @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only(bottom: 4.h), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 36.w, child: Text( label, style: TextStyle( fontSize: 12.sp, color: const Color(0xFFAEAEB2), ), ), ), Expanded( child: Text( value, style: TextStyle( fontSize: 12.sp, color: const Color(0xFF636366), ), ), ), ], ), ); } }