258 lines
7.9 KiB
Dart
258 lines
7.9 KiB
Dart
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 '../../data/schedule_file_import.dart';
|
|
import 'settings_page.dart';
|
|
import 'import_preview.dart';
|
|
|
|
class ProfilePage extends StatelessWidget {
|
|
const ProfilePage({
|
|
super.key,
|
|
required this.controller,
|
|
required this.onLogin,
|
|
this.authStatus = '未登录',
|
|
this.onLogout,
|
|
});
|
|
final AppController controller;
|
|
final VoidCallback onLogin;
|
|
final VoidCallback? onLogout;
|
|
final String authStatus;
|
|
Future<void> _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<bool>(
|
|
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(
|
|
'Riverside Schedule',
|
|
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(
|
|
'电子科技大学 · 研究生\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: Text(onLogout == null ? '登录学校账户' : '打开学校课表'),
|
|
),
|
|
),
|
|
if (onLogout != null)
|
|
Center(
|
|
child: TextButton(
|
|
onPressed: onLogout,
|
|
child: const Text('退出学校登录'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
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<void>(
|
|
builder: (_) => SettingsPage(controller: controller),
|
|
),
|
|
),
|
|
),
|
|
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,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 25),
|
|
Center(
|
|
child: Column(
|
|
children: [
|
|
const Text(
|
|
'Riverside Schedule',
|
|
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
|
|
),
|
|
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,
|
|
);
|
|
}
|