import 'dart:io' show Platform; import 'package:flutter/material.dart'; import '../utils/format_utils.dart'; import 'adaptive_modal.dart'; import 'app_loading_ring.dart'; import 'app_snack_bar.dart'; import 'version_filter.dart'; import 'version_grouping.dart'; import 'version_source_card.dart'; const int kCompactInlineVersionLimit = 6; const int kDesktopInlineVersionLimit = 8; int inlineVersionLimit() { return Platform.isAndroid ? kCompactInlineVersionLimit : kDesktopInlineVersionLimit; } Future showAllVersionsSheet({ required BuildContext context, required List groups, String title = '全部版本', }) async { final isSingleVersionPicker = groups.length == 1; await showAdaptiveModal( context: context, maxWidth: isSingleVersionPicker ? 520 : 720, maxHeight: isSingleVersionPicker ? 300 : 640, compactMaxHeightFactor: isSingleVersionPicker ? 0.55 : 0.92, compactHeightFactor: isSingleVersionPicker ? null : 0.82, builder: (modalContext) => AllVersionsSheet(groups: groups, title: title), ); } class AllVersionsSheet extends StatelessWidget { final List groups; final String title; const AllVersionsSheet({ super.key, required this.groups, this.title = '全部版本', }); @override Widget build(BuildContext context) { final groupsByBucket = >{}; for (final group in groups) { final bucket = versionBucketOf(group.representative.resolutionLabel); groupsByBucket.putIfAbsent(bucket, () => []).add(group); } final concreteSourceCount = groups.fold( 0, (count, group) => count + group.members.length, ); return Material( color: const Color(0xFF171A20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.fromLTRB(20, 8, 8, 10), child: Row( children: [ const Icon( Icons.video_library_outlined, color: Colors.white70, size: 20, ), const SizedBox(width: 10), Text( title, style: const TextStyle( color: Colors.white, fontSize: 17, fontWeight: FontWeight.w700, ), ), const SizedBox(width: 8), Text( '$concreteSourceCount 个来源', style: TextStyle( color: Colors.white.withValues(alpha: 0.55), fontSize: 12, ), ), const Spacer(), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close, color: Colors.white60), tooltip: '关闭', ), ], ), ), Divider(height: 1, color: Colors.white.withValues(alpha: 0.08)), Flexible( fit: FlexFit.loose, child: ListView( shrinkWrap: true, padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), children: [ for (final bucket in kVersionBucketOrder) if (groupsByBucket[bucket] case final bucketGroups?) ...[ _BucketHeader(label: bucket, count: bucketGroups.length), const SizedBox(height: 8), for ( var index = 0; index < bucketGroups.length; index++ ) ...[ _VersionGroupRow(group: bucketGroups[index]), if (index < bucketGroups.length - 1) const SizedBox(height: 10), ], const SizedBox(height: 20), ], ], ), ), ], ), ); } } class _BucketHeader extends StatelessWidget { final String label; final int count; const _BucketHeader({required this.label, required this.count}); @override Widget build(BuildContext context) { return Row( children: [ Text( label, style: const TextStyle( color: Colors.white, fontSize: 15, fontWeight: FontWeight.w700, ), ), const SizedBox(width: 8), Text( '$count 个版本', style: TextStyle( color: Colors.white.withValues(alpha: 0.5), fontSize: 12, ), ), ], ); } } class _VersionGroupRow extends StatelessWidget { final VersionSourceGroup group; const _VersionGroupRow({required this.group}); @override Widget build(BuildContext context) { final representative = group.representative; final sizeBytes = representative.sizeBytes; final bitrate = representative.bitrate; final details = [ if (sizeBytes != null && sizeBytes > 0) formatFileSize(sizeBytes), if (bitrate != null && bitrate > 0) '${(bitrate / 1e6).toStringAsFixed(2)}Mbps', ]; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.06), borderRadius: BorderRadius.circular(14), border: Border.all(color: Colors.white.withValues(alpha: 0.08)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( representative.topLabel, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 15, fontWeight: FontWeight.w700, ), ), ), if (details.isNotEmpty) Text( details.join(' · '), style: TextStyle( color: Colors.white.withValues(alpha: 0.58), fontSize: 12, ), ), ], ), const SizedBox(height: 12), Wrap( spacing: 8, runSpacing: 8, children: [ for (final member in group.members) _ServerSourceChip(data: member), ], ), ], ), ); } } class _ServerSourceChip extends StatefulWidget { final VersionSourceCardData data; const _ServerSourceChip({required this.data}); @override State<_ServerSourceChip> createState() => _ServerSourceChipState(); } class _ServerSourceChipState extends State<_ServerSourceChip> { bool _loading = false; Future _selectSource() async { final data = widget.data; if (_loading || data.isCurrent || data.selected || data.onTap == null) { return; } setState(() => _loading = true); try { await data.onTap!(); if (mounted) Navigator.of(context).pop(); } catch (_) { if (mounted) { setState(() => _loading = false); showAppSnackBar(context, '切换服务器失败', tone: AppSnackTone.error); } } } @override Widget build(BuildContext context) { final data = widget.data; final active = data.isCurrent || data.selected; return Tooltip( message: active ? '${data.serverName}(当前选择)' : '切换到 ${data.serverName}', child: InkWell( onTap: active ? null : _selectSource, borderRadius: BorderRadius.circular(999), child: AnimatedContainer( duration: const Duration(milliseconds: 160), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( color: active ? const Color(0xFF4F8DFF).withValues(alpha: 0.24) : Colors.white.withValues(alpha: 0.07), borderRadius: BorderRadius.circular(999), border: Border.all( color: active ? const Color(0xFF4F8DFF) : Colors.white.withValues(alpha: 0.12), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (_loading) const AppLoadingRing(size: 12, color: Colors.white) else Icon( active ? Icons.check_circle : Icons.dns_outlined, size: 14, color: active ? const Color(0xFF77A7FF) : Colors.white60, ), const SizedBox(width: 6), Text( data.serverName, style: TextStyle( color: Colors.white.withValues(alpha: active ? 1 : 0.78), fontSize: 12, fontWeight: active ? FontWeight.w600 : FontWeight.w500, ), ), if (data.isCurrent) ...[ const SizedBox(width: 6), const Text( '当前', style: TextStyle( color: Color(0xFF9FC0FF), fontSize: 10, fontWeight: FontWeight.w600, ), ), ], ], ), ), ), ); } }