mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-01 16:48:16 +08:00
feat: OrderedMultiSelectDialog (#1290)
* tweak * feat: OrderedMultiSelectDialog
This commit is contained in:
committed by
GitHub
parent
96586f130f
commit
96539cc64c
@@ -25,7 +25,7 @@ class MemberAudioItem extends StatelessWidget {
|
|||||||
onTap: () async {
|
onTap: () async {
|
||||||
// TODO music play
|
// TODO music play
|
||||||
final aid = item.aid;
|
final aid = item.aid;
|
||||||
if (aid != null) {
|
if (aid != null && aid != 0) {
|
||||||
final cid = await SearchHttp.ab2c(aid: aid);
|
final cid = await SearchHttp.ab2c(aid: aid);
|
||||||
if (cid != null) {
|
if (cid != null) {
|
||||||
PageUtils.toVideoPage(cid: cid, aid: aid);
|
PageUtils.toVideoPage(cid: cid, aid: aid);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:PiliPlus/models/common/video/live_quality.dart';
|
|||||||
import 'package:PiliPlus/models/common/video/video_decode_type.dart';
|
import 'package:PiliPlus/models/common/video/video_decode_type.dart';
|
||||||
import 'package:PiliPlus/models/common/video/video_quality.dart';
|
import 'package:PiliPlus/models/common/video/video_quality.dart';
|
||||||
import 'package:PiliPlus/pages/setting/models/model.dart';
|
import 'package:PiliPlus/pages/setting/models/model.dart';
|
||||||
import 'package:PiliPlus/pages/setting/widgets/multi_select_dialog.dart';
|
import 'package:PiliPlus/pages/setting/widgets/ordered_multi_select_dialog.dart';
|
||||||
import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart';
|
import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/models/hwdec_type.dart';
|
import 'package:PiliPlus/plugin/pl_player/models/hwdec_type.dart';
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
@@ -345,10 +345,10 @@ List<SettingsModel> get videoSettings => [
|
|||||||
leading: const Icon(Icons.memory_outlined),
|
leading: const Icon(Icons.memory_outlined),
|
||||||
getSubtitle: () => '当前:${Pref.hardwareDecoding}(此项即mpv的--hwdec)',
|
getSubtitle: () => '当前:${Pref.hardwareDecoding}(此项即mpv的--hwdec)',
|
||||||
onTap: (setState) async {
|
onTap: (setState) async {
|
||||||
final result = await showDialog<Set<String>>(
|
final result = await showDialog<List<String>>(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return MultiSelectDialog<String>(
|
return OrderedMultiSelectDialog<String>(
|
||||||
title: '硬解模式',
|
title: '硬解模式',
|
||||||
initValues: Pref.hardwareDecoding.split(','),
|
initValues: Pref.hardwareDecoding.split(','),
|
||||||
values: {
|
values: {
|
||||||
|
|||||||
58
lib/pages/setting/widgets/checkbox_num.dart
Normal file
58
lib/pages/setting/widgets/checkbox_num.dart
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class OrderedCheckbox extends StatelessWidget {
|
||||||
|
const OrderedCheckbox({
|
||||||
|
super.key,
|
||||||
|
required this.value,
|
||||||
|
required this.onChanged,
|
||||||
|
}) : assert(value == null || value < 100);
|
||||||
|
|
||||||
|
final int? value;
|
||||||
|
final ValueChanged<int?>? onChanged;
|
||||||
|
bool get selected => value != null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
final child = DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(1.5)),
|
||||||
|
border: Border.all(
|
||||||
|
color: selected
|
||||||
|
? theme.colorScheme.primary
|
||||||
|
: theme.colorScheme.onSurface,
|
||||||
|
width: 1.6,
|
||||||
|
strokeAlign: BorderSide.strokeAlignCenter,
|
||||||
|
),
|
||||||
|
color: selected ? theme.colorScheme.primary : null,
|
||||||
|
),
|
||||||
|
child: selected
|
||||||
|
? SizedBox.square(
|
||||||
|
dimension: 16.5,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
value.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
inherit: false,
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
shadows: theme.iconTheme.shadows,
|
||||||
|
height: 1.0,
|
||||||
|
leadingDistribution: TextLeadingDistribution.even,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.square(dimension: 16.5),
|
||||||
|
);
|
||||||
|
if (onChanged != null) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () => onChanged!(value),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
214
lib/pages/setting/widgets/checkbox_num_list_tile.dart
Normal file
214
lib/pages/setting/widgets/checkbox_num_list_tile.dart
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import 'package:PiliPlus/pages/setting/widgets/checkbox_num.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class OrderedCheckboxListTile extends StatelessWidget {
|
||||||
|
/// Creates a combination of a list tile and a checkbox.
|
||||||
|
///
|
||||||
|
/// The checkbox tile itself does not maintain any state. Instead, when the
|
||||||
|
/// state of the checkbox changes, the widget calls the [onChanged] callback.
|
||||||
|
/// Most widgets that use a checkbox will listen for the [onChanged] callback
|
||||||
|
/// and rebuild the checkbox tile with a new [value] to update the visual
|
||||||
|
/// appearance of the checkbox.
|
||||||
|
///
|
||||||
|
/// The following arguments are required:
|
||||||
|
///
|
||||||
|
/// * [value], which determines whether the checkbox is checked. The [value]
|
||||||
|
/// can only be null if [tristate] is true.
|
||||||
|
/// * [onChanged], which is called when the value of the checkbox should
|
||||||
|
/// change. It can be set to null to disable the checkbox.
|
||||||
|
const OrderedCheckboxListTile({
|
||||||
|
super.key,
|
||||||
|
required this.value,
|
||||||
|
required this.onChanged,
|
||||||
|
this.activeColor,
|
||||||
|
this.visualDensity,
|
||||||
|
this.focusNode,
|
||||||
|
this.autofocus = false,
|
||||||
|
this.shape,
|
||||||
|
this.tileColor,
|
||||||
|
this.title,
|
||||||
|
this.subtitle,
|
||||||
|
this.isThreeLine,
|
||||||
|
this.dense,
|
||||||
|
this.trailing,
|
||||||
|
this.contentPadding,
|
||||||
|
this.selectedTileColor,
|
||||||
|
this.onFocusChange,
|
||||||
|
this.enableFeedback,
|
||||||
|
this.checkboxScaleFactor = 1.0,
|
||||||
|
this.titleAlignment,
|
||||||
|
this.internalAddSemanticForOnTap = false,
|
||||||
|
}) : assert(isThreeLine != true || subtitle != null);
|
||||||
|
|
||||||
|
/// Whether this checkbox is checked.
|
||||||
|
final int? value;
|
||||||
|
|
||||||
|
/// Called when the value of the checkbox should change.
|
||||||
|
///
|
||||||
|
/// The checkbox passes the new value to the callback but does not actually
|
||||||
|
/// change state until the parent widget rebuilds the checkbox tile with the
|
||||||
|
/// new value.
|
||||||
|
///
|
||||||
|
/// If null, the checkbox will be displayed as disabled.
|
||||||
|
///
|
||||||
|
/// {@tool snippet}
|
||||||
|
///
|
||||||
|
/// The callback provided to [onChanged] should update the state of the parent
|
||||||
|
/// [StatefulWidget] using the [State.setState] method, so that the parent
|
||||||
|
/// gets rebuilt; for example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// CheckboxListTile(
|
||||||
|
/// value: _throwShotAway,
|
||||||
|
/// onChanged: (bool? newValue) {
|
||||||
|
/// setState(() {
|
||||||
|
/// _throwShotAway = newValue;
|
||||||
|
/// });
|
||||||
|
/// },
|
||||||
|
/// title: const Text('Throw away your shot'),
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
/// {@end-tool}
|
||||||
|
final ValueChanged<int?>? onChanged;
|
||||||
|
|
||||||
|
/// The color to use when this checkbox is checked.
|
||||||
|
///
|
||||||
|
/// Defaults to [ColorScheme.secondary] of the current [Theme].
|
||||||
|
final Color? activeColor;
|
||||||
|
|
||||||
|
/// Defines how compact the list tile's layout will be.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.material.themedata.visualDensity}
|
||||||
|
final VisualDensity? visualDensity;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.Focus.focusNode}
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.Focus.autofocus}
|
||||||
|
final bool autofocus;
|
||||||
|
|
||||||
|
/// {@macro flutter.material.ListTile.shape}
|
||||||
|
final ShapeBorder? shape;
|
||||||
|
|
||||||
|
/// {@macro flutter.material.ListTile.tileColor}
|
||||||
|
final Color? tileColor;
|
||||||
|
|
||||||
|
/// The primary content of the list tile.
|
||||||
|
///
|
||||||
|
/// Typically a [Text] widget.
|
||||||
|
final Widget? title;
|
||||||
|
|
||||||
|
/// Additional content displayed below the title.
|
||||||
|
///
|
||||||
|
/// Typically a [Text] widget.
|
||||||
|
final Widget? subtitle;
|
||||||
|
|
||||||
|
/// A widget to display on the opposite side of the tile from the checkbox.
|
||||||
|
///
|
||||||
|
/// Typically an [Icon] widget.
|
||||||
|
final Widget? trailing;
|
||||||
|
|
||||||
|
/// Whether this list tile is intended to display three lines of text.
|
||||||
|
///
|
||||||
|
/// If null, the value from [ListTileThemeData.isThreeLine] is used.
|
||||||
|
/// If that is also null, the value from [ThemeData.listTileTheme] is used.
|
||||||
|
/// If still null, the default value is `false`.
|
||||||
|
final bool? isThreeLine;
|
||||||
|
|
||||||
|
/// Whether this list tile is part of a vertically dense list.
|
||||||
|
///
|
||||||
|
/// If this property is null then its value is based on [ListTileThemeData.dense].
|
||||||
|
final bool? dense;
|
||||||
|
|
||||||
|
/// Defines insets surrounding the tile's contents.
|
||||||
|
///
|
||||||
|
/// This value will surround the [Checkbox], [title], [subtitle], and [trailing]
|
||||||
|
/// widgets in [OrderedCheckboxListTile].
|
||||||
|
///
|
||||||
|
/// When the value is null, the [contentPadding] is `EdgeInsets.symmetric(horizontal: 16.0)`.
|
||||||
|
final EdgeInsetsGeometry? contentPadding;
|
||||||
|
|
||||||
|
/// If non-null, defines the background color when [OrderedCheckboxListTile.selected] is true.
|
||||||
|
final Color? selectedTileColor;
|
||||||
|
|
||||||
|
/// {@macro flutter.material.inkwell.onFocusChange}
|
||||||
|
final ValueChanged<bool>? onFocusChange;
|
||||||
|
|
||||||
|
/// {@macro flutter.material.ListTile.enableFeedback}
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [Feedback] for providing platform-specific feedback to certain actions.
|
||||||
|
final bool? enableFeedback;
|
||||||
|
|
||||||
|
/// Defines how [ListTile.leading] and [ListTile.trailing] are
|
||||||
|
/// vertically aligned relative to the [ListTile]'s titles
|
||||||
|
/// ([ListTile.title] and [ListTile.subtitle]).
|
||||||
|
///
|
||||||
|
/// If this property is null then [ListTileThemeData.titleAlignment]
|
||||||
|
/// is used. If that is also null then [ListTileTitleAlignment.threeLine]
|
||||||
|
/// is used.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s
|
||||||
|
/// [ListTileThemeData].
|
||||||
|
final ListTileTitleAlignment? titleAlignment;
|
||||||
|
|
||||||
|
/// Whether to add button:true to the semantics if onTap is provided.
|
||||||
|
/// This is a temporary flag to help changing the behavior of ListTile onTap semantics.
|
||||||
|
///
|
||||||
|
// TODO(hangyujin): Remove this flag after fixing related g3 tests and flipping
|
||||||
|
// the default value to true.
|
||||||
|
final bool internalAddSemanticForOnTap;
|
||||||
|
|
||||||
|
/// Controls the scaling factor applied to the [Checkbox] within the [OrderedCheckboxListTile].
|
||||||
|
///
|
||||||
|
/// Defaults to 1.0.
|
||||||
|
final double checkboxScaleFactor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget control;
|
||||||
|
|
||||||
|
control = OrderedCheckbox(value: value, onChanged: null);
|
||||||
|
if (checkboxScaleFactor != 1.0) {
|
||||||
|
control = Transform.scale(scale: checkboxScaleFactor, child: control);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
final CheckboxThemeData checkboxTheme = CheckboxTheme.of(context);
|
||||||
|
final Set<WidgetState> states = <WidgetState>{
|
||||||
|
if (value != null) WidgetState.selected,
|
||||||
|
};
|
||||||
|
final Color effectiveActiveColor =
|
||||||
|
activeColor ??
|
||||||
|
checkboxTheme.fillColor?.resolve(states) ??
|
||||||
|
theme.colorScheme.secondary;
|
||||||
|
return MergeSemantics(
|
||||||
|
child: ListTile(
|
||||||
|
selectedColor: effectiveActiveColor,
|
||||||
|
leading: control,
|
||||||
|
title: title,
|
||||||
|
subtitle: subtitle,
|
||||||
|
trailing: trailing,
|
||||||
|
isThreeLine: isThreeLine,
|
||||||
|
dense: dense,
|
||||||
|
enabled: onChanged != null,
|
||||||
|
onTap: onChanged != null ? () => onChanged!(value) : null,
|
||||||
|
selected: value != null,
|
||||||
|
autofocus: autofocus,
|
||||||
|
contentPadding: contentPadding,
|
||||||
|
shape: shape,
|
||||||
|
selectedTileColor: selectedTileColor,
|
||||||
|
tileColor: tileColor,
|
||||||
|
visualDensity: visualDensity,
|
||||||
|
focusNode: focusNode,
|
||||||
|
onFocusChange: onFocusChange,
|
||||||
|
enableFeedback: enableFeedback,
|
||||||
|
titleAlignment: titleAlignment,
|
||||||
|
internalAddSemanticForOnTap: internalAddSemanticForOnTap,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,12 +33,12 @@ class _MultiSelectDialogState<T> extends State<MultiSelectDialog<T>> {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
title: Text(widget.title),
|
title: Text(widget.title),
|
||||||
contentPadding: const EdgeInsets.only(top: 12),
|
contentPadding: const EdgeInsets.only(top: 12),
|
||||||
content: StatefulBuilder(
|
content: SingleChildScrollView(
|
||||||
builder: (context, StateSetter setState) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: widget.values.entries.map((i) {
|
children: widget.values.entries.map((i) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
bool isChecked = _tempValues.contains(i.key);
|
bool isChecked = _tempValues.contains(i.key);
|
||||||
return CheckboxListTile(
|
return CheckboxListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -52,13 +52,13 @@ class _MultiSelectDialogState<T> extends State<MultiSelectDialog<T>> {
|
|||||||
isChecked
|
isChecked
|
||||||
? _tempValues.remove(i.key)
|
? _tempValues.remove(i.key)
|
||||||
: _tempValues.add(i.key);
|
: _tempValues.add(i.key);
|
||||||
setState(() {});
|
(context as Element).markNeedsBuild();
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
actionsPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
|
actionsPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
96
lib/pages/setting/widgets/ordered_multi_select_dialog.dart
Normal file
96
lib/pages/setting/widgets/ordered_multi_select_dialog.dart
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import 'package:PiliPlus/pages/setting/widgets/checkbox_num_list_tile.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class OrderedMultiSelectDialog<T> extends StatefulWidget {
|
||||||
|
final Iterable<T> initValues;
|
||||||
|
final String title;
|
||||||
|
final Map<T, String> values;
|
||||||
|
|
||||||
|
const OrderedMultiSelectDialog({
|
||||||
|
super.key,
|
||||||
|
required this.initValues,
|
||||||
|
required this.values,
|
||||||
|
required this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OrderedMultiSelectDialog<T>> createState() =>
|
||||||
|
_OrderedMultiSelectDialogState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OrderedMultiSelectDialogState<T>
|
||||||
|
extends State<OrderedMultiSelectDialog<T>> {
|
||||||
|
late Map<T, int> _tempValues;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_tempValues = {for (var (i, j) in widget.initValues.indexed) j: i + 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return AlertDialog(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
title: Text(widget.title),
|
||||||
|
contentPadding: const EdgeInsets.only(top: 12),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: widget.values.entries.map((i) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
return OrderedCheckboxListTile(
|
||||||
|
dense: true,
|
||||||
|
value: _tempValues[i.key],
|
||||||
|
title: Text(
|
||||||
|
i.value,
|
||||||
|
style: theme.textTheme.titleMedium!,
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) {
|
||||||
|
_tempValues[i.key] = _tempValues.length + 1;
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
|
} else {
|
||||||
|
final pos = _tempValues.remove(i.key)!;
|
||||||
|
if (pos == _tempValues.length + 1) {
|
||||||
|
(context as Element).markNeedsBuild();
|
||||||
|
} else {
|
||||||
|
_tempValues.updateAll(
|
||||||
|
(key, value) => value > pos ? value - 1 : value,
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actionsPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 12),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
assert(_tempValues.values.isSorted((a, b) => a.compareTo(b)));
|
||||||
|
Get.back(result: _tempValues.keys.toList());
|
||||||
|
},
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,7 +47,10 @@ class _SettingsSearchPageState
|
|||||||
(item.title ?? item.getTitle!()).toLowerCase().contains(
|
(item.title ?? item.getTitle!()).toLowerCase().contains(
|
||||||
value,
|
value,
|
||||||
) ||
|
) ||
|
||||||
item.subtitle?.toLowerCase().contains(value) == true,
|
(item.subtitle ?? item.getSubtitle?.call())
|
||||||
|
?.toLowerCase()
|
||||||
|
.contains(value) ==
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -648,10 +648,10 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
color: theme.colorScheme.surface,
|
color: theme.colorScheme.surface,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
child: ListView(
|
child: CustomScrollView(
|
||||||
padding: EdgeInsets.zero,
|
slivers: [
|
||||||
children: [
|
SliverToBoxAdapter(
|
||||||
SizedBox(
|
child: SizedBox(
|
||||||
height: 45,
|
height: 45,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => SmartDialog.showToast(
|
onTap: () => SmartDialog.showToast(
|
||||||
@@ -671,7 +671,10 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...List.generate(totalQaSam, (index) {
|
),
|
||||||
|
SliverList.builder(
|
||||||
|
itemCount: totalQaSam,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
final item = videoFormat[index];
|
final item = videoFormat[index];
|
||||||
return ListTile(
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -700,7 +703,9 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
},
|
},
|
||||||
// 可能包含会员解锁画质
|
// 可能包含会员解锁画质
|
||||||
enabled: index >= totalQaSam - userfulQaSam,
|
enabled: index >= totalQaSam - userfulQaSam,
|
||||||
contentPadding: const EdgeInsets.only(left: 20, right: 20),
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
title: Text(item.newDesc!),
|
title: Text(item.newDesc!),
|
||||||
trailing: currentVideoQa.code == item.quality
|
trailing: currentVideoQa.code == item.quality
|
||||||
? Icon(
|
? Icon(
|
||||||
@@ -712,7 +717,8 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -734,17 +740,21 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
color: theme.colorScheme.surface,
|
color: theme.colorScheme.surface,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
child: ListView(
|
child: CustomScrollView(
|
||||||
padding: EdgeInsets.zero,
|
slivers: [
|
||||||
children: [
|
const SliverToBoxAdapter(
|
||||||
const SizedBox(
|
child: SizedBox(
|
||||||
height: 45,
|
height: 45,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text('选择音质', style: titleStyle),
|
child: Text('选择音质', style: titleStyle),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
for (final AudioItem i in audio) ...[
|
),
|
||||||
ListTile(
|
SliverList.builder(
|
||||||
|
itemCount: audio.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final i = audio[index];
|
||||||
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (currentAudioQa.code == i.id) {
|
if (currentAudioQa.code == i.id) {
|
||||||
@@ -769,7 +779,9 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contentPadding: const EdgeInsets.only(left: 20, right: 20),
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
title: Text(i.quality),
|
title: Text(i.quality),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
i.codecs!,
|
i.codecs!,
|
||||||
@@ -781,9 +793,10 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -825,42 +838,42 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView(
|
child: CustomScrollView(
|
||||||
padding: EdgeInsets.zero,
|
slivers: [
|
||||||
children: [
|
SliverList.builder(
|
||||||
for (var i in list) ...[
|
itemCount: list.length,
|
||||||
ListTile(
|
itemBuilder: (context, index) {
|
||||||
|
final i = list[index];
|
||||||
|
final format = VideoDecodeFormatType.fromString(i);
|
||||||
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (currentDecodeFormats.codes.any(i.startsWith)) {
|
if (currentDecodeFormats.codes.any(
|
||||||
|
i.startsWith,
|
||||||
|
)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
videoDetailCtr
|
videoDetailCtr
|
||||||
..currentDecodeFormats =
|
..currentDecodeFormats = format
|
||||||
VideoDecodeFormatType.fromString(i)
|
|
||||||
..updatePlayer();
|
..updatePlayer();
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
contentPadding: const EdgeInsets.only(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
left: 20,
|
horizontal: 20,
|
||||||
right: 20,
|
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(format.description),
|
||||||
VideoDecodeFormatType.fromString(i).description,
|
subtitle: Text(i, style: subTitleStyle),
|
||||||
),
|
trailing:
|
||||||
subtitle: Text(
|
currentDecodeFormats.codes.any(i.startsWith)
|
||||||
i,
|
|
||||||
style: subTitleStyle,
|
|
||||||
),
|
|
||||||
trailing: currentDecodeFormats.codes.any(i.startsWith)
|
|
||||||
? Icon(
|
? Icon(
|
||||||
Icons.done,
|
Icons.done,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1845,23 +1858,29 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
color: theme.colorScheme.surface,
|
color: theme.colorScheme.surface,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
child: ListView(
|
child: CustomScrollView(
|
||||||
padding: EdgeInsets.zero,
|
slivers: [
|
||||||
children: [
|
const SliverToBoxAdapter(
|
||||||
const SizedBox(
|
child: SizedBox(
|
||||||
height: 45,
|
height: 45,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text('选择播放顺序', style: titleStyle),
|
child: Text('选择播放顺序', style: titleStyle),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
for (final PlayRepeat i in PlayRepeat.values) ...[
|
),
|
||||||
ListTile(
|
SliverList.builder(
|
||||||
|
itemCount: PlayRepeat.values.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final i = PlayRepeat.values[index];
|
||||||
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
plPlayerController.setPlayRepeat(i);
|
plPlayerController.setPlayRepeat(i);
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
contentPadding: const EdgeInsets.only(left: 20, right: 20),
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
title: Text(i.desc),
|
title: Text(i.desc),
|
||||||
trailing: plPlayerController.playRepeat == i
|
trailing: plPlayerController.playRepeat == i
|
||||||
? Icon(
|
? Icon(
|
||||||
@@ -1869,9 +1888,10 @@ class HeaderControlState extends TripleState<HeaderControl> {
|
|||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user