import 'dart:convert'; import 'dart:typed_data'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import '../../app/app_controller.dart'; import '../../app/app_theme.dart'; import '../../app/app_version.dart'; import '../../data/schedule_file_import.dart'; import '../feedback/feedback_page.dart'; import '../feedback/feedback_submission.dart'; import 'settings_page.dart'; import 'import_preview.dart'; class ProfilePage extends StatelessWidget { const ProfilePage({ super.key, required this.controller, required this.onLogin, this.schoolName, this.authStatus = '未登录', this.onLogout, this.logoutLabel = '退出学校登录', this.onUpdateTerm, this.feedbackSender = const UnconfiguredFeedbackSender(), this.onCheckUpdates, this.checkingUpdates = false, }); final AppController controller; final VoidCallback onLogin; final String? schoolName; final VoidCallback? onLogout; final VoidCallback? onUpdateTerm; final String authStatus, logoutLabel; final FeedbackSender feedbackSender; final VoidCallback? onCheckUpdates; final bool checkingUpdates; Future _import(BuildContext context) async { try { final file = await FilePicker.pickFile( type: FileType.custom, allowedExtensions: ['json', 'html', 'htm'], ); if (file == null) return; final bytes = BytesBuilder(copy: false); var count = 0; await for (final chunk in file.readAsByteStream()) { count += chunk.length; if (count > 2 * 1024 * 1024) { throw const FormatException('课表文件不能超过 2 MB'); } bytes.add(chunk); } final draft = ScheduleFileImport.parse(utf8.decode(bytes.takeBytes())); if (!context.mounted) return; await showModalBottomSheet( context: context, showDragHandle: true, isScrollControlled: true, backgroundColor: canvas, builder: (_) => ImportPreview(draft: draft, controller: controller), ); } catch (error) { if (context.mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('导入未完成:$error'))); } } } @override Widget build(BuildContext context) { final imported = controller.snapshot.importMetadata; return ListView( padding: EdgeInsets.fromLTRB( 22, 10, 22, 145 + MediaQuery.paddingOf(context).bottom, ), children: [ const Text( 'TPSchedule', style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700), ), const SizedBox(height: 16), SurfaceCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( width: 55, height: 55, decoration: BoxDecoration( color: river.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(19), ), child: const Icon( Icons.person_outline_rounded, size: 30, color: river, ), ), const SizedBox(height: 16), const Text( '学校课表', style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700), ), const SizedBox(height: 7), Text( '${schoolName ?? '选择学校后连接教务系统'}\n$authStatus', style: TextStyle( fontSize: 12, height: 1.8, color: ink.withValues(alpha: 0.55), ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: FilledButton( onPressed: onLogin, child: const Text('选择学校'), ), ), if (onLogout != null) Center( child: TextButton( onPressed: onLogout, child: Text(logoutLabel), ), ), ], ), ), const SizedBox(height: 18), SurfaceCard( child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${controller.snapshot.courses.length}', style: const TextStyle( fontSize: 30, fontWeight: FontWeight.w700, color: river, ), ), const Text('已导入的课程安排', style: TextStyle(fontSize: 12)), ], ), ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( imported == null ? '尚未导入' : '已在本机保存', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 7), Text( imported == null ? '离线查看,更安心' : '${imported.importedAt.month} 月 ${imported.importedAt.day} 日导入', style: TextStyle( fontSize: 11, color: ink.withValues(alpha: 0.4), ), ), ], ), ), ], ), ), const SizedBox(height: 25), const Text( '安排你的每一天', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600), ), const SizedBox(height: 12), SurfaceCard( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 5), child: Column( children: [ _tile( Icons.tune_rounded, '课表设置', '颜色、提醒、日历和学期', () => Navigator.push( context, MaterialPageRoute( builder: (_) => SettingsPage(controller: controller), ), ), ), Divider(height: 1, color: ink.withValues(alpha: 0.06)), _tile( Icons.date_range_rounded, '更新学期', controller.snapshot.schoolTerm?.label ?? '选择学校查询学期', onUpdateTerm ?? onLogin, ), Divider(height: 1, color: ink.withValues(alpha: 0.06)), _tile( Icons.file_download_outlined, '从文件导入', '学校 HTML / JSON · 先预览再导入', () => _import(context), ), Divider(height: 1, color: ink.withValues(alpha: 0.06)), _tile( Icons.auto_awesome_outlined, '体验示例课表', '无需登录,预览课表样式', controller.showDemo, ), Divider(height: 1, color: ink.withValues(alpha: 0.06)), _tile( Icons.chat_bubble_outline_rounded, '反馈与共建', '反馈问题和建议 · 我要参与开发', () => Navigator.push( context, MaterialPageRoute( builder: (_) => FeedbackPage(sender: feedbackSender), ), ), ), Divider(height: 1, color: ink.withValues(alpha: 0.06)), ListTile( key: const Key('check-app-update'), contentPadding: EdgeInsets.zero, leading: const Icon( Icons.system_update_rounded, color: river, size: 23, ), title: const Text( '检查更新', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), subtitle: Text( checkingUpdates ? '正在检查…' : '当前版本 $appVersion', style: const TextStyle(fontSize: 11), ), trailing: checkingUpdates ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.chevron_right_rounded), onTap: checkingUpdates ? null : onCheckUpdates, ), ], ), ), const SizedBox(height: 25), Center( child: Column( children: [ const Text( 'TPSchedule', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600), ), const SizedBox(height: 6), Text( '版本 $appVersion', style: TextStyle( fontSize: 11, color: ink.withValues(alpha: 0.45), ), ), const SizedBox(height: 6), Text( appFilingNumber, style: TextStyle( fontSize: 11, color: ink.withValues(alpha: 0.45), ), ), const SizedBox(height: 6), Text( '今天也有值得期待的小事。', style: TextStyle( fontSize: 11, color: ink.withValues(alpha: 0.35), ), ), ], ), ), ], ); } Widget _tile( IconData icon, String title, String subtitle, VoidCallback action, ) => ListTile( contentPadding: EdgeInsets.zero, leading: Icon(icon, color: river, size: 23), title: Text( title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), subtitle: Text( subtitle, style: TextStyle( fontSize: 11, height: 1.6, color: ink.withValues(alpha: 0.4), ), ), trailing: Icon( Icons.chevron_right_rounded, color: ink.withValues(alpha: 0.3), ), onTap: action, ); }