Initial commit
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user