226 lines
7.4 KiB
Dart
226 lines
7.4 KiB
Dart
import 'dart:io' show Platform;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:forui/forui.dart' as forui;
|
|
|
|
import '../../../shared/widgets/all_versions_sheet.dart';
|
|
import '../../../shared/widgets/hover_scrollable_row.dart';
|
|
import '../../../shared/widgets/version_filter.dart';
|
|
import '../../../shared/widgets/version_grouping.dart';
|
|
import '../../../shared/widgets/version_source_card.dart';
|
|
import 'player_panel_shell.dart';
|
|
|
|
|
|
class VersionPanel extends StatefulWidget {
|
|
final List<VersionSourceCardData> entries;
|
|
final VoidCallback onClose;
|
|
|
|
const VersionPanel({super.key, required this.entries, required this.onClose});
|
|
|
|
@override
|
|
State<VersionPanel> createState() => _VersionPanelState();
|
|
}
|
|
|
|
class _VersionPanelState extends State<VersionPanel> {
|
|
final ScrollController _scrollController = ScrollController();
|
|
String? _selectedBucket;
|
|
|
|
@override
|
|
void dispose() {
|
|
_scrollController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final entries = widget.entries;
|
|
final allGroups = groupVersionEntries(entries);
|
|
|
|
final bucketsPresent = <String>{
|
|
for (final e in entries) versionBucketOf(e.resolutionLabel),
|
|
};
|
|
final buckets = kVersionBucketOrder
|
|
.where(bucketsPresent.contains)
|
|
.toList(growable: false);
|
|
|
|
final activeEntry = entries
|
|
.where((entry) => entry.isCurrent || entry.selected)
|
|
.firstOrNull;
|
|
final defaultBucket = activeEntry == null
|
|
? (buckets.isNotEmpty ? buckets.first : null)
|
|
: versionBucketOf(activeEntry.resolutionLabel);
|
|
final selected =
|
|
_selectedBucket != null && bucketsPresent.contains(_selectedBucket)
|
|
? _selectedBucket!
|
|
: defaultBucket;
|
|
if (selected != _selectedBucket) {
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!mounted) return;
|
|
setState(() => _selectedBucket = selected);
|
|
});
|
|
}
|
|
|
|
final filteredGroups = selected == null
|
|
? allGroups
|
|
: allGroups
|
|
.where(
|
|
(group) =>
|
|
versionBucketOf(group.representative.resolutionLabel) ==
|
|
selected,
|
|
)
|
|
.toList(growable: false);
|
|
final inlineLimit = inlineVersionLimit();
|
|
final inlineGroups = filteredGroups
|
|
.take(inlineLimit)
|
|
.toList(growable: true);
|
|
final activeGroupIndex = filteredGroups.indexWhere(
|
|
(group) => group.hasActiveMember,
|
|
);
|
|
if (activeGroupIndex >= inlineLimit && inlineGroups.isNotEmpty) {
|
|
inlineGroups[inlineGroups.length - 1] = filteredGroups[activeGroupIndex];
|
|
}
|
|
final hasMergedGroup = allGroups.any((group) => group.members.length > 1);
|
|
final showAllVersionsButton =
|
|
filteredGroups.length > inlineLimit || hasMergedGroup;
|
|
final itemCount = inlineGroups.length;
|
|
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: kPanelBg,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
_header(
|
|
buckets,
|
|
selected,
|
|
allGroups: allGroups,
|
|
sourceCount: entries.length,
|
|
showAllVersionsButton: showAllVersionsButton,
|
|
),
|
|
panelDivider(),
|
|
Padding(
|
|
padding: EdgeInsets.only(
|
|
top: Platform.isAndroid ? 8 : 12,
|
|
bottom: Platform.isAndroid ? 10 : 14,
|
|
),
|
|
child: SizedBox(
|
|
height: Platform.isAndroid ? 56 : 120,
|
|
child: HoverScrollableRow(
|
|
controller: _scrollController,
|
|
builder: (_, controller) => ListView.builder(
|
|
controller: controller,
|
|
scrollDirection: Axis.horizontal,
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: Platform.isAndroid ? 10 : 16,
|
|
),
|
|
itemCount: itemCount,
|
|
itemBuilder: (_, i) => Padding(
|
|
padding: EdgeInsets.only(
|
|
right: i < itemCount - 1
|
|
? (Platform.isAndroid ? 6 : 12)
|
|
: 0,
|
|
),
|
|
child: VersionSourceCard(
|
|
data: inlineGroups[i].displayData,
|
|
compact: Platform.isAndroid,
|
|
onMergedServersTap: inlineGroups[i].members.length > 1
|
|
? () => showAllVersionsSheet(
|
|
context: context,
|
|
groups: [inlineGroups[i]],
|
|
title: '选择服务器',
|
|
)
|
|
: null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _header(
|
|
List<String> buckets,
|
|
String? selected, {
|
|
required List<VersionSourceGroup> allGroups,
|
|
required int sourceCount,
|
|
required bool showAllVersionsButton,
|
|
}) {
|
|
final isAndroid = Platform.isAndroid;
|
|
return Padding(
|
|
padding: isAndroid
|
|
? const EdgeInsets.fromLTRB(16, 10, 8, 8)
|
|
: const EdgeInsets.fromLTRB(16, 12, 8, 10),
|
|
child: Row(
|
|
children: [
|
|
const Icon(
|
|
Icons.video_file_outlined,
|
|
color: Colors.white70,
|
|
size: 18,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'切换版本',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: isAndroid ? 14 : 15,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
if (showAllVersionsButton) ...[
|
|
const SizedBox(width: 10),
|
|
forui.FButton(
|
|
variant: forui.FButtonVariant.secondary,
|
|
size: forui.FButtonSizeVariant.xs,
|
|
mainAxisSize: MainAxisSize.min,
|
|
onPress: () =>
|
|
showAllVersionsSheet(context: context, groups: allGroups),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text('全部 $sourceCount 个版本'),
|
|
const SizedBox(width: 2),
|
|
const Icon(Icons.chevron_right_rounded, size: 16),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: Row(
|
|
children: [
|
|
for (var i = 0; i < buckets.length; i++) ...[
|
|
if (i > 0) const SizedBox(width: 8),
|
|
VersionFilterChip(
|
|
label: buckets[i],
|
|
selected: selected == buckets[i],
|
|
showDot: true,
|
|
onTap: () => setState(() => _selectedBucket = buckets[i]),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
IconButton(
|
|
icon: const Icon(Icons.close, color: Colors.white54, size: 18),
|
|
onPressed: widget.onClose,
|
|
padding: EdgeInsets.zero,
|
|
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|