import 'package:flutter/material.dart'; import '../models/catalog.dart'; /// 管理页:单元集 / 计划 / 算法三个维度。 class ManagePage extends StatelessWidget { const ManagePage({super.key}); @override Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( title: const Text('管理'), bottom: const TabBar( tabs: [ Tab(text: '单元集'), Tab(text: '计划'), Tab(text: '算法'), ], ), ), body: const TabBarView( children: [ _DeckTab(), _PlanTab(), _AlgorithmTab(), ], ), ), ); } } class _DeckTab extends StatelessWidget { const _DeckTab(); @override Widget build(BuildContext context) { return ListView.separated( padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), itemCount: DeckInfo.samples.length + 1, separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 12), itemBuilder: (BuildContext context, int index) { if (index == 0) { return _CountLabel(text: '共 ${DeckInfo.samples.length} 个单元集'); } return _DeckCard(deck: DeckInfo.samples[index - 1]); }, ); } } class _DeckCard extends StatelessWidget { const _DeckCard({required this.deck}); final DeckInfo deck; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colors = theme.colorScheme; final TextTheme text = theme.textTheme; return _ManageCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _Badge(icon: Icons.menu_book_rounded, color: colors.primary), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( deck.title, style: text.titleMedium, maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( deck.description, style: text.bodySmall?.copyWith( color: colors.onSurfaceVariant, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), const SizedBox(width: 8), _Chip(label: deck.algorithm), ], ), const SizedBox(height: 14), ClipRRect( borderRadius: BorderRadius.circular(6), child: LinearProgressIndicator( value: deck.progress, minHeight: 6, backgroundColor: colors.surfaceContainerHighest, ), ), const SizedBox(height: 10), Row( children: [ Expanded( child: _MetaText( '${deck.activatedItems} / ${deck.totalItems} · ${deck.progressPercent}', ), ), _MetaText('最近 ${deck.lastStudied}'), ], ), const SizedBox(height: 6), _MetaText('标识 ${deck.id}'), ], ), ); } } class _PlanTab extends StatelessWidget { const _PlanTab(); @override Widget build(BuildContext context) { return ListView.separated( padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), itemCount: PlanInfo.samples.length + 1, separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 12), itemBuilder: (BuildContext context, int index) { if (index == 0) { return _CountLabel(text: '共 ${PlanInfo.samples.length} 个计划'); } return _PlanCard(plan: PlanInfo.samples[index - 1]); }, ); } } class _PlanCard extends StatelessWidget { const _PlanCard({required this.plan}); final PlanInfo plan; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colors = theme.colorScheme; final TextTheme text = theme.textTheme; return _ManageCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _Badge(icon: Icons.event_note_rounded, color: colors.tertiary), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(plan.title, style: text.titleMedium), const SizedBox(height: 2), Text( plan.description, style: text.bodySmall?.copyWith( color: colors.onSurfaceVariant, ), ), ], ), ), const SizedBox(width: 8), _Chip( label: plan.enabled ? '启用' : '停用', emphasized: plan.enabled, ), ], ), const SizedBox(height: 14), Row( children: [ Expanded( child: _Metric( label: '新识记', value: '${plan.newPerDay}', suffix: '/ 天', ), ), Expanded( child: _Metric( label: '复习上限', value: '${plan.reviewPerDay}', suffix: '/ 天', ), ), ], ), const SizedBox(height: 10), _MetaText('时段 ${plan.window}'), ], ), ); } } class _AlgorithmTab extends StatelessWidget { const _AlgorithmTab(); @override Widget build(BuildContext context) { return ListView.separated( padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), itemCount: AlgorithmInfo.samples.length + 1, separatorBuilder: (BuildContext context, int index) => const SizedBox(height: 12), itemBuilder: (BuildContext context, int index) { if (index == 0) { return _CountLabel( text: '共 ${AlgorithmInfo.samples.length} 个调度算法', ); } return _AlgorithmCard(algorithm: AlgorithmInfo.samples[index - 1]); }, ); } } class _AlgorithmCard extends StatelessWidget { const _AlgorithmCard({required this.algorithm}); final AlgorithmInfo algorithm; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colors = theme.colorScheme; final TextTheme text = theme.textTheme; return _ManageCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ _Badge(icon: Icons.functions_rounded, color: colors.secondary), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(algorithm.name, style: text.titleMedium), const SizedBox(width: 6), _MetaText('v${algorithm.version}'), ], ), const SizedBox(height: 2), Text( algorithm.description, style: text.bodySmall?.copyWith( color: colors.onSurfaceVariant, ), ), ], ), ), if (algorithm.isDefault) ...[ const SizedBox(width: 8), const _Chip(label: '默认', emphasized: true), ], ], ), const SizedBox(height: 12), _MetaText(algorithm.parameters), ], ), ); } } /// 卡片统一外观。 class _ManageCard extends StatelessWidget { const _ManageCard({required this.child}); final Widget child; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerLow, borderRadius: BorderRadius.circular(20), ), child: child, ); } } class _Badge extends StatelessWidget { const _Badge({required this.icon, required this.color}); final IconData icon; final Color color; @override Widget build(BuildContext context) { return Container( width: 40, height: 40, decoration: BoxDecoration( color: color.withValues(alpha: 0.16), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, size: 20, color: color), ); } } class _Chip extends StatelessWidget { const _Chip({required this.label, this.emphasized = false}); final String label; final bool emphasized; @override Widget build(BuildContext context) { final ColorScheme colors = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: ShapeDecoration( color: emphasized ? colors.primaryContainer : colors.surfaceContainerHighest, shape: const StadiumBorder(), ), child: Text( label, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: emphasized ? colors.onPrimaryContainer : colors.onSurfaceVariant, fontWeight: FontWeight.w600, ), ), ); } } class _Metric extends StatelessWidget { const _Metric({required this.label, required this.value, this.suffix}); final String label; final String value; final String? suffix; @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colors = theme.colorScheme; final String? suffix = this.suffix; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: theme.textTheme.bodySmall?.copyWith( color: colors.onSurfaceVariant, ), ), const SizedBox(height: 2), Row( crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ Text( value, style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, ), ), if (suffix != null) ...[ const SizedBox(width: 4), Text( suffix, style: theme.textTheme.bodySmall?.copyWith( color: colors.onSurfaceVariant, ), ), ], ], ), ], ); } } class _MetaText extends StatelessWidget { const _MetaText(this.value); final String value; @override Widget build(BuildContext context) { return Text( value, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), maxLines: 1, overflow: TextOverflow.ellipsis, ); } } class _CountLabel extends StatelessWidget { const _CountLabel({required this.text}); final String text; @override Widget build(BuildContext context) { return Text( text, style: Theme.of(context).textTheme.labelMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ); } }