mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-15 23:10:09 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af40e489bc | ||
|
|
361eb4c614 | ||
|
|
7ace981f24 | ||
|
|
bfb2becb2d |
@@ -1,6 +1,3 @@
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show clampDouble;
|
||||
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart'
|
||||
@@ -10,18 +7,14 @@ import 'package:flutter/rendering.dart'
|
||||
MultiChildLayoutParentData;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum TooltipType { top, right }
|
||||
|
||||
class CustomTooltip extends StatefulWidget {
|
||||
const CustomTooltip({
|
||||
super.key,
|
||||
this.type = TooltipType.top,
|
||||
required this.overlayWidget,
|
||||
required this.child,
|
||||
required this.indicator,
|
||||
});
|
||||
|
||||
final TooltipType type;
|
||||
final Widget child;
|
||||
final ValueGetter<Widget> overlayWidget;
|
||||
final ValueGetter<Widget> indicator;
|
||||
@@ -51,27 +44,20 @@ class _CustomTooltipState extends State<CustomTooltip> {
|
||||
longPressRecognizer.addPointer(event);
|
||||
}
|
||||
|
||||
Widget _buildCustomTooltipOverlay(BuildContext context) {
|
||||
final OverlayState overlayState = Overlay.of(
|
||||
context,
|
||||
debugRequiredFor: widget,
|
||||
Widget _buildCustomTooltipOverlay(
|
||||
BuildContext context,
|
||||
OverlayChildLayoutInfo layoutInfo,
|
||||
) {
|
||||
final target = MatrixUtils.transformPoint(
|
||||
layoutInfo.childPaintTransform,
|
||||
layoutInfo.childSize.topCenter(Offset.zero),
|
||||
);
|
||||
final RenderBox box = this.context.findRenderObject()! as RenderBox;
|
||||
final Offset target = box.localToGlobal(
|
||||
box.size.center(Offset.zero),
|
||||
ancestor: overlayState.context.findRenderObject(),
|
||||
);
|
||||
|
||||
final _CustomTooltipOverlay overlayChild = _CustomTooltipOverlay(
|
||||
verticalOffset: box.size.height / 2,
|
||||
horizontalOffset: box.size.width / 2,
|
||||
type: widget.type,
|
||||
target: target,
|
||||
onDismiss: _scheduleDismissTooltip,
|
||||
overlayWidget: widget.overlayWidget,
|
||||
indicator: widget.indicator,
|
||||
);
|
||||
|
||||
return SelectionContainer.maybeOf(context) == null
|
||||
? overlayChild
|
||||
: SelectionContainer.disabled(child: overlayChild);
|
||||
@@ -105,7 +91,7 @@ class _CustomTooltipState extends State<CustomTooltip> {
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
return OverlayPortal(
|
||||
return OverlayPortal.overlayChildLayoutBuilder(
|
||||
controller: _overlayController,
|
||||
overlayChildBuilder: _buildCustomTooltipOverlay,
|
||||
child: result,
|
||||
@@ -117,18 +103,12 @@ enum _ChildType { overlay, indicator }
|
||||
|
||||
class _CustomTooltipOverlay extends StatelessWidget {
|
||||
const _CustomTooltipOverlay({
|
||||
required this.verticalOffset,
|
||||
required this.horizontalOffset,
|
||||
required this.type,
|
||||
required this.target,
|
||||
required this.onDismiss,
|
||||
required this.overlayWidget,
|
||||
required this.indicator,
|
||||
});
|
||||
|
||||
final double verticalOffset;
|
||||
final double horizontalOffset;
|
||||
final TooltipType type;
|
||||
final Offset target;
|
||||
final VoidCallback onDismiss;
|
||||
final ValueGetter<Widget> overlayWidget;
|
||||
@@ -137,10 +117,7 @@ class _CustomTooltipOverlay extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _ToolTip(
|
||||
type: type,
|
||||
target: target,
|
||||
verticalOffset: verticalOffset,
|
||||
horizontalOffset: horizontalOffset,
|
||||
preferBelow: false,
|
||||
onTap: PlatformUtils.isMobile ? onDismiss : null,
|
||||
children: [
|
||||
@@ -161,28 +138,19 @@ class _ToolTip extends MultiChildRenderObjectWidget {
|
||||
const _ToolTip({
|
||||
super.children,
|
||||
this.onTap,
|
||||
required this.type,
|
||||
required this.target,
|
||||
required this.verticalOffset,
|
||||
required this.horizontalOffset,
|
||||
required this.preferBelow,
|
||||
});
|
||||
|
||||
final VoidCallback? onTap;
|
||||
final TooltipType type;
|
||||
final Offset target;
|
||||
final double verticalOffset;
|
||||
final double horizontalOffset;
|
||||
final bool preferBelow;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _RenderToolTip(
|
||||
onTap: onTap,
|
||||
type: type,
|
||||
target: target,
|
||||
verticalOffset: verticalOffset,
|
||||
horizontalOffset: horizontalOffset,
|
||||
preferBelow: preferBelow,
|
||||
);
|
||||
}
|
||||
@@ -192,8 +160,6 @@ class _ToolTip extends MultiChildRenderObjectWidget {
|
||||
renderObject
|
||||
..onTap = onTap
|
||||
..target = target
|
||||
..verticalOffset = verticalOffset
|
||||
..horizontalOffset = horizontalOffset
|
||||
..preferBelow = preferBelow;
|
||||
}
|
||||
}
|
||||
@@ -204,15 +170,9 @@ class _RenderToolTip extends RenderBox
|
||||
RenderBoxContainerDefaultsMixin<RenderBox, MultiChildLayoutParentData> {
|
||||
_RenderToolTip({
|
||||
VoidCallback? onTap,
|
||||
required TooltipType type,
|
||||
required Offset target,
|
||||
required double verticalOffset,
|
||||
required double horizontalOffset,
|
||||
required bool preferBelow,
|
||||
}) : _type = type,
|
||||
_target = target,
|
||||
_verticalOffset = verticalOffset,
|
||||
_horizontalOffset = horizontalOffset,
|
||||
}) : _target = target,
|
||||
_preferBelow = preferBelow,
|
||||
_hitTestSelf = onTap != null {
|
||||
if (onTap != null) {
|
||||
@@ -246,8 +206,6 @@ class _RenderToolTip extends RenderBox
|
||||
}
|
||||
}
|
||||
|
||||
final TooltipType _type;
|
||||
|
||||
Offset _target;
|
||||
Offset get target => _target;
|
||||
set target(Offset value) {
|
||||
@@ -256,22 +214,6 @@ class _RenderToolTip extends RenderBox
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
double _verticalOffset;
|
||||
double get verticalOffset => _verticalOffset;
|
||||
set verticalOffset(double value) {
|
||||
if (_verticalOffset == value) return;
|
||||
_verticalOffset = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
double _horizontalOffset;
|
||||
double get horizontalOffset => _horizontalOffset;
|
||||
set horizontalOffset(double value) {
|
||||
if (_horizontalOffset == value) return;
|
||||
_horizontalOffset = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
bool _preferBelow;
|
||||
bool get preferBelow => _preferBelow;
|
||||
set preferBelow(bool value) {
|
||||
@@ -302,40 +244,18 @@ class _RenderToolTip extends RenderBox
|
||||
indicator.parentData as MultiChildLayoutParentData;
|
||||
final overlayParentData = overlay.parentData as MultiChildLayoutParentData;
|
||||
|
||||
switch (_type) {
|
||||
case TooltipType.top:
|
||||
Offset offset = _positionDependentBox(
|
||||
type: _type,
|
||||
size: size,
|
||||
childSize: overlaySize,
|
||||
target: target,
|
||||
verticalOffset: verticalOffset,
|
||||
horizontalOffset: horizontalOffset,
|
||||
preferBelow: preferBelow,
|
||||
);
|
||||
offset = Offset(offset.dx, offset.dy - indicatorSize.height + 1);
|
||||
overlayParentData.offset = offset;
|
||||
indicatorParentData.offset = Offset(
|
||||
target.dx - indicatorSize.width / 2,
|
||||
offset.dy + overlaySize.height - 1,
|
||||
);
|
||||
case TooltipType.right:
|
||||
Offset offset = _positionDependentBox(
|
||||
type: _type,
|
||||
size: size,
|
||||
childSize: overlaySize,
|
||||
target: target,
|
||||
verticalOffset: verticalOffset,
|
||||
horizontalOffset: horizontalOffset,
|
||||
preferBelow: preferBelow,
|
||||
);
|
||||
offset = Offset(offset.dx + indicatorSize.height - 1, offset.dy);
|
||||
overlayParentData.offset = offset;
|
||||
Offset(
|
||||
offset.dx - indicatorSize.width + 1,
|
||||
target.dy - indicatorSize.height / 2,
|
||||
);
|
||||
}
|
||||
Offset offset = positionDependentBox(
|
||||
size: size,
|
||||
childSize: overlaySize,
|
||||
target: target,
|
||||
preferBelow: preferBelow,
|
||||
);
|
||||
offset = Offset(offset.dx, offset.dy - indicatorSize.height + 1);
|
||||
overlayParentData.offset = offset;
|
||||
indicatorParentData.offset = Offset(
|
||||
target.dx - indicatorSize.width / 2,
|
||||
offset.dy + overlaySize.height - 1,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -357,19 +277,16 @@ class Triangle extends LeafRenderObjectWidget {
|
||||
super.key,
|
||||
required this.color,
|
||||
required this.size,
|
||||
this.type = .top,
|
||||
});
|
||||
|
||||
final Color color;
|
||||
final Size size;
|
||||
final TooltipType type;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderTriangle(
|
||||
color: color,
|
||||
preferredSize: size,
|
||||
type: type,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -388,10 +305,8 @@ class RenderTriangle extends RenderBox {
|
||||
RenderTriangle({
|
||||
required Color color,
|
||||
required Size preferredSize,
|
||||
required TooltipType type,
|
||||
}) : _color = color,
|
||||
_preferredSize = preferredSize,
|
||||
_type = type;
|
||||
_preferredSize = preferredSize;
|
||||
|
||||
Color _color;
|
||||
Color get color => _color;
|
||||
@@ -408,8 +323,6 @@ class RenderTriangle extends RenderBox {
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
final TooltipType _type;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = constraints.constrain(_preferredSize);
|
||||
@@ -422,21 +335,11 @@ class RenderTriangle extends RenderBox {
|
||||
..color = color
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
Path path;
|
||||
switch (_type) {
|
||||
case TooltipType.top:
|
||||
path = Path()
|
||||
..moveTo(0, 0)
|
||||
..lineTo(size.width, 0)
|
||||
..lineTo(size.width / 2, size.height)
|
||||
..close();
|
||||
case TooltipType.right:
|
||||
path = Path()
|
||||
..moveTo(0, size.height / 2)
|
||||
..lineTo(size.width, 0)
|
||||
..lineTo(size.width, size.height)
|
||||
..close();
|
||||
}
|
||||
final path = Path()
|
||||
..moveTo(0, 0)
|
||||
..lineTo(size.width, 0)
|
||||
..lineTo(size.width / 2, size.height)
|
||||
..close();
|
||||
|
||||
context.canvas.drawPath(path, paint);
|
||||
}
|
||||
@@ -444,50 +347,3 @@ class RenderTriangle extends RenderBox {
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
|
||||
Offset _positionDependentBox({
|
||||
required TooltipType type,
|
||||
required Size size,
|
||||
required Size childSize,
|
||||
required Offset target,
|
||||
required bool preferBelow,
|
||||
double verticalOffset = 0.0,
|
||||
double horizontalOffset = 0.0,
|
||||
double margin = 10.0,
|
||||
}) {
|
||||
switch (type) {
|
||||
case TooltipType.top:
|
||||
// VERTICAL DIRECTION
|
||||
final bool fitsBelow =
|
||||
target.dy + verticalOffset + childSize.height <= size.height - margin;
|
||||
final bool fitsAbove =
|
||||
target.dy - verticalOffset - childSize.height >= margin;
|
||||
final bool tooltipBelow = fitsAbove == fitsBelow
|
||||
? preferBelow
|
||||
: fitsBelow;
|
||||
final double y;
|
||||
if (tooltipBelow) {
|
||||
y = math.min(target.dy + verticalOffset, size.height - margin);
|
||||
} else {
|
||||
y = math.max(target.dy - verticalOffset - childSize.height, margin);
|
||||
} // HORIZONTAL DIRECTION
|
||||
final double flexibleSpace = size.width - childSize.width;
|
||||
final double x = flexibleSpace <= 2 * margin
|
||||
// If there's not enough horizontal space for margin + child, center the
|
||||
// child.
|
||||
? flexibleSpace / 2.0
|
||||
: clampDouble(
|
||||
target.dx - childSize.width / 2,
|
||||
margin,
|
||||
flexibleSpace - margin,
|
||||
);
|
||||
return Offset(x, y);
|
||||
case TooltipType.right:
|
||||
final double dy = math.max(margin, target.dy - childSize.height / 2);
|
||||
final double dx = math.min(
|
||||
target.dx + horizontalOffset,
|
||||
size.width - childSize.width - margin,
|
||||
);
|
||||
return Offset(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
enum SkipType {
|
||||
import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
|
||||
enum SkipType implements EnumWithLabel {
|
||||
alwaysSkip('总是跳过'),
|
||||
skipOnce('跳过一次'),
|
||||
skipManually('手动跳过'),
|
||||
@@ -6,6 +8,7 @@ enum SkipType {
|
||||
disable('禁用')
|
||||
;
|
||||
|
||||
final String title;
|
||||
const SkipType(this.title);
|
||||
@override
|
||||
final String label;
|
||||
const SkipType(this.label);
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ mixin CommonSlideMixin<T extends CommonSlidePage> on State<T>, TickerProvider {
|
||||
final isRTL = dx >= _maxWidth - offset;
|
||||
if (isLTR || isRTL) {
|
||||
_isRTL = isRTL;
|
||||
_downDx = dx;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
)
|
||||
..onStart = _onDragStart
|
||||
..onUpdate = _onDragUpdate
|
||||
..onEnd = _onDragEnd
|
||||
..onCancel = _onDragEnd;
|
||||
@@ -101,6 +101,10 @@ mixin CommonSlideMixin<T extends CommonSlidePage> on State<T>, TickerProvider {
|
||||
_downDx = null;
|
||||
}
|
||||
|
||||
void _onDragStart(DragStartDetails details) {
|
||||
_downDx = details.localPosition.dx;
|
||||
}
|
||||
|
||||
void _onDragUpdate(DragUpdateDetails details) {
|
||||
final from = _downDx!;
|
||||
final to = details.localPosition.dx;
|
||||
|
||||
@@ -134,54 +134,12 @@ List<SettingsModel> get extraSettings => [
|
||||
],
|
||||
),
|
||||
),
|
||||
NormalModel(
|
||||
leading: const Icon(MdiIcons.debugStepOver),
|
||||
getPopupMenuModel(
|
||||
title: '番剧片头/片尾跳过类型',
|
||||
getTrailing: () => Builder(
|
||||
builder: (context) {
|
||||
final pgcSkipType = Pref.pgcSkipType;
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
final color = pgcSkipType == SkipType.disable
|
||||
? colorScheme.outline
|
||||
: colorScheme.secondary;
|
||||
return PopupMenuButton<SkipType>(
|
||||
initialValue: pgcSkipType,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text.rich(
|
||||
style: TextStyle(fontSize: 14, height: 1, color: color),
|
||||
strutStyle: const StrutStyle(
|
||||
leading: 0,
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: pgcSkipType.title),
|
||||
WidgetSpan(
|
||||
alignment: .middle,
|
||||
child: Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: 14,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onSelected: (value) async {
|
||||
await GStorage.setting.put(SettingBoxKey.pgcSkipType, value.index);
|
||||
if (context.mounted) {
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => SkipType.values
|
||||
.map((e) => PopupMenuItem(value: e, child: Text(e.title)))
|
||||
.toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
leading: const Icon(MdiIcons.debugStepOver),
|
||||
key: SettingBoxKey.pgcSkipType,
|
||||
values: SkipType.values,
|
||||
defaultIndex: SkipType.skipOnce.index,
|
||||
),
|
||||
SwitchModel(
|
||||
title: '检查未读动态',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
import 'package:PiliPlus/pages/setting/widgets/normal_item.dart';
|
||||
import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart';
|
||||
import 'package:PiliPlus/pages/setting/widgets/switch_item.dart';
|
||||
@@ -7,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show FilteringTextInputFormatter;
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
@immutable
|
||||
sealed class SettingsModel {
|
||||
@@ -258,3 +260,61 @@ SettingsModel getVideoFilterSelectModel({
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
SettingsModel getPopupMenuModel({
|
||||
required String title,
|
||||
Widget? leading,
|
||||
String? subtitle,
|
||||
required String key,
|
||||
required List<EnumWithLabel> values,
|
||||
int defaultIndex = 0,
|
||||
}) {
|
||||
// final globalKey = GlobalKey<PopupMenuButtonState<EnumWithLabel>>();
|
||||
return NormalModel(
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
leading: leading,
|
||||
// onTap: (context, setState) => globalKey.currentState?.showButtonMenu(),
|
||||
getTrailing: () => Builder(
|
||||
builder: (context) {
|
||||
final color = ColorScheme.of(context).secondary;
|
||||
final v = values[GStorage.setting.get(key, defaultValue: defaultIndex)];
|
||||
return PopupMenuButton(
|
||||
// key: globalKey,
|
||||
padding: .zero,
|
||||
initialValue: v,
|
||||
onSelected: (value) async {
|
||||
await GStorage.setting.put(key, value.index);
|
||||
if (context.mounted) {
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => values
|
||||
.map((i) => PopupMenuItem(value: i, child: Text(i.label)))
|
||||
.toList(),
|
||||
child: Padding(
|
||||
padding: const .symmetric(vertical: 8),
|
||||
child: Text.rich(
|
||||
style: TextStyle(fontSize: 14, height: 1, color: color),
|
||||
strutStyle: const StrutStyle(leading: 0, height: 1, fontSize: 14),
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: v.label),
|
||||
WidgetSpan(
|
||||
alignment: .middle,
|
||||
child: Icon(
|
||||
size: 14,
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
style: TextStyle(color: color),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ import 'package:PiliPlus/models/common/video/video_quality.dart';
|
||||
import 'package:PiliPlus/pages/setting/models/model.dart';
|
||||
import 'package:PiliPlus/pages/setting/widgets/ordered_multi_select_dialog.dart';
|
||||
import 'package:PiliPlus/pages/setting/widgets/select_dialog.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/audio_output_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/hwdec_type.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/storage_key.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:PiliPlus/utils/video_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
@@ -317,13 +319,32 @@ List<SettingsModel> get videoSettings => [
|
||||
}
|
||||
},
|
||||
),
|
||||
if (Platform.isAndroid)
|
||||
const SwitchModel(
|
||||
title: '优先使用 OpenSL ES 输出音频',
|
||||
leading: Icon(Icons.speaker_outlined),
|
||||
subtitle: '关闭则优先使用AAudio输出音频(此项即mpv的--ao),若遇系统音效丢失、无声、音画不同步等问题请尝试打开。',
|
||||
setKey: SettingBoxKey.useOpenSLES,
|
||||
defaultVal: false,
|
||||
if (kDebugMode || Platform.isAndroid)
|
||||
NormalModel(
|
||||
title: '音频输出设备',
|
||||
leading: const Icon(Icons.speaker_outlined),
|
||||
getSubtitle: () => '当前:${Pref.audioOutput}',
|
||||
onTap: (context, setState) async {
|
||||
final result = await showDialog<List<String>>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return OrderedMultiSelectDialog<String>(
|
||||
title: '音频输出设备',
|
||||
initValues: Pref.audioOutput.split(','),
|
||||
values: {
|
||||
for (final e in AudioOutput.values) e.name: e.label,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null && result.isNotEmpty) {
|
||||
await GStorage.setting.put(
|
||||
SettingBoxKey.audioOutput,
|
||||
result.join(','),
|
||||
);
|
||||
setState();
|
||||
}
|
||||
},
|
||||
),
|
||||
const SwitchModel(
|
||||
title: '扩大缓冲区',
|
||||
|
||||
@@ -594,7 +594,7 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
.map(
|
||||
(item) => PopupMenuItem<SkipType>(
|
||||
value: item,
|
||||
child: Text(item.title),
|
||||
child: Text(item.label),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -617,7 +617,7 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(text: item.second.title),
|
||||
TextSpan(text: item.second.label),
|
||||
WidgetSpan(
|
||||
alignment: .middle,
|
||||
child: Icon(
|
||||
|
||||
@@ -659,7 +659,7 @@ class VideoDetailController extends GetxController
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
item.skipType.title,
|
||||
item.skipType.label,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
if (item.segment.second != 0)
|
||||
|
||||
@@ -1344,13 +1344,13 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
|
||||
required double height,
|
||||
bool isPipMode = false,
|
||||
}) => PopScope(
|
||||
key: videoDetailController.videoPlayerKey,
|
||||
canPop:
|
||||
!isFullScreen &&
|
||||
!videoDetailController.plPlayerController.isDesktopPip &&
|
||||
(videoDetailController.horizontalScreen || isPortrait),
|
||||
onPopInvokedWithResult: _onPopInvokedWithResult,
|
||||
child: Obx(
|
||||
key: videoDetailController.videoPlayerKey,
|
||||
() =>
|
||||
videoDetailController.videoState.value is! Success ||
|
||||
!videoDetailController.autoPlay.value ||
|
||||
|
||||
@@ -796,8 +796,7 @@ class PlPlayerController {
|
||||
await pp.setProperty("af", "scaletempo2=max-speed=8");
|
||||
if (Platform.isAndroid) {
|
||||
await pp.setProperty("volume-max", "100");
|
||||
final ao = Pref.useOpenSLES ? "opensles,aaudio" : "aaudio,opensles";
|
||||
await pp.setProperty("ao", ao);
|
||||
await pp.setProperty("ao", Pref.audioOutput);
|
||||
}
|
||||
// video-sync=display-resample
|
||||
await pp.setProperty("video-sync", Pref.videoSync);
|
||||
|
||||
14
lib/plugin/pl_player/models/audio_output_type.dart
Normal file
14
lib/plugin/pl_player/models/audio_output_type.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
|
||||
enum AudioOutput implements EnumWithLabel {
|
||||
opensles('OpenSL ES'),
|
||||
aaudio('AAudio'),
|
||||
audiotrack('AudioTrack')
|
||||
;
|
||||
|
||||
static final defaultValue = values.map((e) => e.name).join(',');
|
||||
|
||||
@override
|
||||
final String label;
|
||||
const AudioOutput(this.label);
|
||||
}
|
||||
@@ -98,46 +98,47 @@ abstract final class ReplyUtils {
|
||||
await Future.delayed(const Duration(seconds: 8));
|
||||
}
|
||||
void showReplyCheckResult(String message, {bool isBan = false}) {
|
||||
final actions = [
|
||||
if (isBan)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
String? uri;
|
||||
switch (type) {
|
||||
case 1:
|
||||
uri = IdUtils.av2bv(oid);
|
||||
case 17:
|
||||
uri = 'https://www.bilibili.com/opus/$oid';
|
||||
}
|
||||
if (uri != null) {
|
||||
Utils.copyText(uri);
|
||||
}
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url':
|
||||
'https://www.bilibili.com/h5/comment/appeal?${Utils.themeUrl(Get.isDarkMode)}',
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('申诉'),
|
||||
),
|
||||
if (!isManual)
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'关闭',
|
||||
style: TextStyle(color: Get.theme.colorScheme.outline),
|
||||
),
|
||||
),
|
||||
];
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
barrierDismissible: isManual,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('评论检查结果'),
|
||||
content: SelectableText(message),
|
||||
actions: [
|
||||
if (isBan)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
String? uri;
|
||||
switch (type) {
|
||||
case 1:
|
||||
uri = IdUtils.av2bv(oid);
|
||||
case 17:
|
||||
uri = 'https://www.bilibili.com/opus/$oid';
|
||||
}
|
||||
if (uri != null) {
|
||||
Utils.copyText(uri);
|
||||
}
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url':
|
||||
'https://www.bilibili.com/h5/comment/appeal?${Utils.themeUrl(Get.isDarkMode)}',
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('申诉'),
|
||||
),
|
||||
if (!isManual)
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'关闭',
|
||||
style: TextStyle(color: Get.theme.colorScheme.outline),
|
||||
),
|
||||
),
|
||||
],
|
||||
actions: actions.isEmpty ? null : actions,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -316,6 +316,31 @@ abstract final class RequestUtils {
|
||||
clearCookie: true,
|
||||
);
|
||||
final isSuccess = res.isSuccess;
|
||||
final actions = [
|
||||
if (!isSuccess)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Utils.copyText('https://www.bilibili.com/opus/$id');
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url':
|
||||
'https://www.bilibili.com/h5/comment/appeal?${Utils.themeUrl(Get.isDarkMode)}',
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('申诉'),
|
||||
),
|
||||
if (!isManual)
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'关闭',
|
||||
style: TextStyle(color: Get.theme.colorScheme.outline),
|
||||
),
|
||||
),
|
||||
];
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
barrierDismissible: isManual,
|
||||
@@ -324,31 +349,7 @@ abstract final class RequestUtils {
|
||||
content: SelectableText(
|
||||
'${isSuccess ? '无账号状态下找到了你的动态,动态正常!' : '你的动态被shadow ban(仅自己可见)!'}${dynText != null ? ' \n\n动态内容: $dynText' : ''}',
|
||||
),
|
||||
actions: [
|
||||
if (!isSuccess)
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
Utils.copyText('https://www.bilibili.com/opus/$id');
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url':
|
||||
'https://www.bilibili.com/h5/comment/appeal?${Utils.themeUrl(Get.isDarkMode)}',
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text('申诉'),
|
||||
),
|
||||
if (!isManual)
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'关闭',
|
||||
style: TextStyle(color: Get.theme.colorScheme.outline),
|
||||
),
|
||||
),
|
||||
],
|
||||
actions: actions.isEmpty ? null : actions,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ abstract final class SettingBoxKey {
|
||||
defaultToastOp = 'defaultToastOp',
|
||||
defaultPicQa = 'defaultPicQa',
|
||||
enableHA = 'enableHA',
|
||||
useOpenSLES = 'useOpenSLES',
|
||||
audioOutput = 'audioOutput',
|
||||
expandBuffer = 'expandBuffer',
|
||||
hardwareDecoding = 'hardwareDecoding',
|
||||
videoSync = 'videoSync',
|
||||
|
||||
@@ -24,6 +24,7 @@ import 'package:PiliPlus/models/common/video/video_decode_type.dart';
|
||||
import 'package:PiliPlus/models/common/video/video_quality.dart';
|
||||
import 'package:PiliPlus/models/user/danmaku_rule.dart';
|
||||
import 'package:PiliPlus/models/user/info.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/audio_output_type.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/fullscreen_mode.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/hwdec_type.dart';
|
||||
@@ -786,8 +787,10 @@ abstract final class Pref {
|
||||
static bool get expandBuffer =>
|
||||
_setting.get(SettingBoxKey.expandBuffer, defaultValue: false);
|
||||
|
||||
static bool get useOpenSLES =>
|
||||
_setting.get(SettingBoxKey.useOpenSLES, defaultValue: false);
|
||||
static String get audioOutput => _setting.get(
|
||||
SettingBoxKey.audioOutput,
|
||||
defaultValue: AudioOutput.defaultValue,
|
||||
);
|
||||
|
||||
static bool get enableAi =>
|
||||
_setting.get(SettingBoxKey.enableAi, defaultValue: false);
|
||||
|
||||
@@ -1480,10 +1480,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: safe_local_storage
|
||||
sha256: "608354d4cdfdabb29428bdb330c4e54dbc31aab238b09ba59fe73660580553cc"
|
||||
sha256: "287ea1f667c0b93cdc127dccc707158e2d81ee59fba0459c31a0c7da4d09c755"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.0.3"
|
||||
saver_gallery:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
Reference in New Issue
Block a user