diff --git a/assets/images/big-vip.png b/assets/images/big-vip.png
deleted file mode 100644
index bb0091546..000000000
Binary files a/assets/images/big-vip.png and /dev/null differ
diff --git a/assets/images/big-vip.svg b/assets/images/big-vip.svg
new file mode 100644
index 000000000..089eeeae4
--- /dev/null
+++ b/assets/images/big-vip.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/lv/lv0.png b/assets/images/lv/lv0.png
deleted file mode 100644
index f9ed49b62..000000000
Binary files a/assets/images/lv/lv0.png and /dev/null differ
diff --git a/assets/images/lv/lv1.png b/assets/images/lv/lv1.png
deleted file mode 100644
index ad8b70ce0..000000000
Binary files a/assets/images/lv/lv1.png and /dev/null differ
diff --git a/assets/images/lv/lv2.png b/assets/images/lv/lv2.png
deleted file mode 100644
index c369f5ada..000000000
Binary files a/assets/images/lv/lv2.png and /dev/null differ
diff --git a/assets/images/lv/lv3.png b/assets/images/lv/lv3.png
deleted file mode 100644
index 80a0db3fa..000000000
Binary files a/assets/images/lv/lv3.png and /dev/null differ
diff --git a/assets/images/lv/lv4.png b/assets/images/lv/lv4.png
deleted file mode 100644
index 5441967b2..000000000
Binary files a/assets/images/lv/lv4.png and /dev/null differ
diff --git a/assets/images/lv/lv5.png b/assets/images/lv/lv5.png
deleted file mode 100644
index dedd1309c..000000000
Binary files a/assets/images/lv/lv5.png and /dev/null differ
diff --git a/assets/images/lv/lv6.png b/assets/images/lv/lv6.png
deleted file mode 100644
index 399e585a7..000000000
Binary files a/assets/images/lv/lv6.png and /dev/null differ
diff --git a/assets/images/lv/lv6_s.png b/assets/images/lv/lv6_s.png
deleted file mode 100644
index 4b867500c..000000000
Binary files a/assets/images/lv/lv6_s.png and /dev/null differ
diff --git a/assets/images/play.png b/assets/images/play.png
deleted file mode 100644
index b5d0299c5..000000000
Binary files a/assets/images/play.png and /dev/null differ
diff --git a/lib/common/assets.dart b/lib/common/assets.dart
index 87589b600..ed429dc80 100644
--- a/lib/common/assets.dart
+++ b/lib/common/assets.dart
@@ -6,11 +6,10 @@ abstract final class Assets {
static const logoIco = 'assets/images/logo/ico/app_icon.ico';
static const logoLarge = 'assets/images/logo/desktop/logo_large.png';
- static const vipIcon = 'assets/images/big-vip.png';
+ static const vipIcon = 'assets/images/big-vip.svg';
static const avatarPlaceHolder = 'assets/images/noface.jpeg';
static const loading = 'assets/images/loading.png';
static const buffering = 'assets/images/loading.webp';
- static const play = 'assets/images/play.png';
static const topicHeader = 'assets/images/topic-header-bg.png';
static const trendingBanner = 'assets/images/trending_banner.png';
static const ai = 'assets/images/ai.png';
diff --git a/lib/common/widgets/avatars.dart b/lib/common/widgets/avatars.dart
index c49db91af..4fc165d18 100644
--- a/lib/common/widgets/avatars.dart
+++ b/lib/common/widgets/avatars.dart
@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/model_owner.dart';
+import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
Widget avatars({
@@ -28,19 +29,19 @@ Widget avatars({
width: offset * users.length + gap,
child: Stack(
clipBehavior: .none,
- children: users.indexed
- .map(
- (e) => Positioned(
+ children: users
+ .mapIndexed(
+ (i, e) => Positioned(
top: 0,
bottom: 0,
width: size,
- left: e.$1 * offset,
+ left: i * offset,
child: DecoratedBox(
decoration: decoration,
child: Padding(
padding: const .all(padding),
child: NetworkImgLayer(
- src: e.$2.face,
+ src: e.face,
width: imgSize,
height: imgSize,
type: .avatar,
diff --git a/lib/common/widgets/dialog/dialog.dart b/lib/common/widgets/dialog/dialog.dart
index 9d9ff7b22..db4b717f5 100644
--- a/lib/common/widgets/dialog/dialog.dart
+++ b/lib/common/widgets/dialog/dialog.dart
@@ -64,42 +64,39 @@ void showPgcFollowDialog({
showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ...const [
- (followStatus: 3, title: '看过'),
- (followStatus: 2, title: '在看'),
- (followStatus: 1, title: '想看'),
- ].map(
- (item) => statusItem(
- enabled: followStatus != item.followStatus,
- text: item.title,
- onTap: () {
- Get.back();
- onUpdateStatus(item.followStatus);
- },
- ),
- ),
- ListTile(
- dense: true,
- title: Padding(
- padding: const EdgeInsets.only(left: 10),
- child: Text(
- '取消$type',
- style: const TextStyle(fontSize: 14),
- ),
- ),
+ children: [
+ ...const [
+ (followStatus: 3, title: '看过'),
+ (followStatus: 2, title: '在看'),
+ (followStatus: 1, title: '想看'),
+ ].map(
+ (item) => statusItem(
+ enabled: followStatus != item.followStatus,
+ text: item.title,
onTap: () {
Get.back();
- onUpdateStatus(-1);
+ onUpdateStatus(item.followStatus);
},
),
- ],
- ),
+ ),
+ ListTile(
+ dense: true,
+ title: Padding(
+ padding: const EdgeInsets.only(left: 10),
+ child: Text(
+ '取消$type',
+ style: const TextStyle(fontSize: 14),
+ ),
+ ),
+ onTap: () {
+ Get.back();
+ onUpdateStatus(-1);
+ },
+ ),
+ ],
),
);
}
diff --git a/lib/common/widgets/dialog/export_import.dart b/lib/common/widgets/dialog/export_import.dart
index 26deeb8a0..4b8076686 100644
--- a/lib/common/widgets/dialog/export_import.dart
+++ b/lib/common/widgets/dialog/export_import.dart
@@ -2,6 +2,7 @@ import 'dart:async' show FutureOr;
import 'dart:convert' show utf8, jsonDecode;
import 'package:PiliPlus/common/style.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/storage_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
@@ -214,21 +215,19 @@ Future showImportExportDialog(
builder: (context) {
const style = TextStyle(fontSize: 15);
return SimpleDialog(
- clipBehavior: Clip.hardEdge,
+ clipBehavior: .hardEdge,
title: Text('导入/导出$title'),
children: [
- ListTile(
- dense: true,
- title: const Text('导出至剪贴板', style: style),
- onTap: () {
+ DialogOption(
+ child: const Text('导出至剪贴板', style: style),
+ onPressed: () {
Get.back();
exportToClipBoard(onExport: onExport);
},
),
- ListTile(
- dense: true,
- title: const Text('导出文件至本地', style: style),
- onTap: () {
+ DialogOption(
+ child: const Text('导出文件至本地', style: style),
+ onPressed: () {
Get.back();
exportToLocalFile(onExport: onExport, localFileName: localFileName);
},
@@ -237,18 +236,16 @@ Future showImportExportDialog(
height: 1,
color: ColorScheme.of(context).outline.withValues(alpha: 0.1),
),
- ListTile(
- dense: true,
- title: const Text('输入', style: style),
- onTap: () {
+ DialogOption(
+ child: const Text('输入', style: style),
+ onPressed: () {
Get.back();
importFromInput(context, title: title, onImport: onImport);
},
),
- ListTile(
- dense: true,
- title: const Text('从剪贴板导入', style: style),
- onTap: () {
+ DialogOption(
+ child: const Text('从剪贴板导入', style: style),
+ onPressed: () {
Get.back();
importFromClipBoard(
context,
@@ -258,10 +255,9 @@ Future showImportExportDialog(
);
},
),
- ListTile(
- dense: true,
- title: const Text('从本地文件导入', style: style),
- onTap: () {
+ DialogOption(
+ child: const Text('从本地文件导入', style: style),
+ onPressed: () {
Get.back();
importFromLocalFile(onImport: onImport);
},
diff --git a/lib/common/widgets/dialog/simple_dialog_option.dart b/lib/common/widgets/dialog/simple_dialog_option.dart
new file mode 100644
index 000000000..26a8d907e
--- /dev/null
+++ b/lib/common/widgets/dialog/simple_dialog_option.dart
@@ -0,0 +1,29 @@
+import 'package:PiliPlus/utils/platform_utils.dart';
+import 'package:flutter/material.dart';
+
+final EdgeInsets _padding = PlatformUtils.isMobile
+ ? const .symmetric(horizontal: 16, vertical: 14)
+ : const .symmetric(horizontal: 16, vertical: 10);
+
+class DialogOption extends StatelessWidget {
+ const DialogOption({
+ super.key,
+ this.onPressed,
+ this.child,
+ });
+
+ final VoidCallback? onPressed;
+
+ final Widget? child;
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onTap: onPressed,
+ child: Padding(
+ padding: _padding,
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/lib/common/widgets/image_viewer/gallery_viewer.dart b/lib/common/widgets/image_viewer/gallery_viewer.dart
index 30ba4be0a..77d77f5f6 100644
--- a/lib/common/widgets/image_viewer/gallery_viewer.dart
+++ b/lib/common/widgets/image_viewer/gallery_viewer.dart
@@ -18,6 +18,7 @@
import 'dart:io' show File, Platform;
import 'package:PiliPlus/common/widgets/colored_box_transition.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
import 'package:PiliPlus/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart';
import 'package:PiliPlus/common/widgets/image_viewer/image.dart';
@@ -534,76 +535,67 @@ class _GalleryViewerState extends State
HapticFeedback.mediumImpact();
showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (PlatformUtils.isMobile)
- ListTile(
- onTap: () {
- Get.back();
- ImageUtils.onShareImg(item.url);
- },
- dense: true,
- title: const Text('分享', style: TextStyle(fontSize: 14)),
- ),
- ListTile(
- onTap: () {
+ children: [
+ if (PlatformUtils.isMobile)
+ DialogOption(
+ onPressed: () {
Get.back();
- Utils.copyText(item.url);
+ ImageUtils.onShareImg(item.url);
},
- dense: true,
- title: const Text('复制链接', style: TextStyle(fontSize: 14)),
+ child: const Text('分享', style: TextStyle(fontSize: 14)),
),
- ListTile(
- onTap: () {
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ Utils.copyText(item.url);
+ },
+ child: const Text('复制链接', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ ImageUtils.downloadImg([item.url]);
+ },
+ child: const Text('保存图片', style: TextStyle(fontSize: 14)),
+ ),
+ if (PlatformUtils.isDesktop)
+ DialogOption(
+ onPressed: () {
Get.back();
- ImageUtils.downloadImg([item.url]);
+ PageUtils.launchURL(item.url);
},
- dense: true,
- title: const Text('保存图片', style: TextStyle(fontSize: 14)),
+ child: const Text('网页打开', style: TextStyle(fontSize: 14)),
+ )
+ else if (widget.sources.length > 1)
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ ImageUtils.downloadImg(
+ widget.sources.map((item) => item.url).toList(),
+ );
+ },
+ child: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
),
- if (PlatformUtils.isDesktop)
- ListTile(
- onTap: () {
- Get.back();
- PageUtils.launchURL(item.url);
- },
- dense: true,
- title: const Text('网页打开', style: TextStyle(fontSize: 14)),
- )
- else if (widget.sources.length > 1)
- ListTile(
- onTap: () {
- Get.back();
- ImageUtils.downloadImg(
- widget.sources.map((item) => item.url).toList(),
- );
- },
- dense: true,
- title: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
+ if (item.sourceType == SourceType.livePhoto)
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ ImageUtils.downloadLivePhoto(
+ url: item.url,
+ liveUrl: item.liveUrl!,
+ width: item.width!,
+ height: item.height!,
+ );
+ },
+ child: Text(
+ '保存${Platform.isIOS ? ' Live Photo' : '视频'}',
+ style: const TextStyle(fontSize: 14),
),
- if (item.sourceType == SourceType.livePhoto)
- ListTile(
- onTap: () {
- Get.back();
- ImageUtils.downloadLivePhoto(
- url: item.url,
- liveUrl: item.liveUrl!,
- width: item.width!,
- height: item.height!,
- );
- },
- dense: true,
- title: Text(
- '保存${Platform.isIOS ? ' Live Photo' : '视频'}',
- style: const TextStyle(fontSize: 14),
- ),
- ),
- ],
- ),
+ ),
+ ],
),
);
}
diff --git a/lib/common/widgets/pendant_avatar.dart b/lib/common/widgets/pendant_avatar.dart
index 9589e381c..5c88faa4f 100644
--- a/lib/common/widgets/pendant_avatar.dart
+++ b/lib/common/widgets/pendant_avatar.dart
@@ -4,10 +4,10 @@ import 'package:PiliPlus/common/widgets/extra_hittest_stack.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/common/avatar_badge_type.dart';
import 'package:PiliPlus/models/common/image_type.dart';
-import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_svg/svg.dart';
class PendantAvatar extends StatelessWidget {
const PendantAvatar(
@@ -142,12 +142,11 @@ class PendantAvatar extends StatelessWidget {
Widget _buildBadge(BuildContext context, ColorScheme colorScheme) {
final child = switch (badgeType) {
- .vip => Image.asset(
+ .vip => SvgPicture.asset(
Assets.vipIcon,
width: badgeSize,
height: badgeSize,
- cacheWidth: badgeSize.cacheSize(context),
- semanticLabel: badgeType.desc,
+ semanticsLabel: badgeType.desc,
),
_ => Icon(
Icons.offline_bolt,
diff --git a/lib/common/widgets/progress_bar/segment_progress_bar.dart b/lib/common/widgets/progress_bar/segment_progress_bar.dart
index e2cbae375..d83d3cc05 100644
--- a/lib/common/widgets/progress_bar/segment_progress_bar.dart
+++ b/lib/common/widgets/progress_bar/segment_progress_bar.dart
@@ -207,13 +207,7 @@ class RenderViewPointProgressBar
),
),
)
- ..pushStyle(
- ui.TextStyle(
- color: Colors.white,
- fontSize: size,
- height: 1,
- ),
- )
+ ..pushStyle(.new(color: Colors.white, fontSize: size, height: 1))
..addText(title);
return builder.build()
..layout(const ui.ParagraphConstraints(width: double.infinity));
diff --git a/lib/common/widgets/self_sized_horizontal_list.dart b/lib/common/widgets/self_sized_horizontal_list.dart
index dea354ea8..777267adb 100644
--- a/lib/common/widgets/self_sized_horizontal_list.dart
+++ b/lib/common/widgets/self_sized_horizontal_list.dart
@@ -28,6 +28,9 @@ class _SelfSizedHorizontalListState extends State {
@override
Widget build(BuildContext context) {
if (_height == null) {
+ if (widget.itemCount == 0) {
+ return const SizedBox.shrink();
+ }
return OnlyLayoutWidget(
onPerformLayout: (Size size) {
if (!mounted) return;
diff --git a/lib/common/widgets/svg/level_icon.dart b/lib/common/widgets/svg/level_icon.dart
new file mode 100644
index 000000000..71a17746a
--- /dev/null
+++ b/lib/common/widgets/svg/level_icon.dart
@@ -0,0 +1,293 @@
+// dart format width=120
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/semantics.dart';
+
+class UserLevel extends LeafRenderObjectWidget {
+ const UserLevel(
+ this.level, {
+ super.key,
+ this.height = 11,
+ this.flash = false,
+ });
+
+ final double height;
+ final int level;
+ final bool flash;
+
+ @override
+ RenderObject createRenderObject(BuildContext context) {
+ return RenderLevel(height, level, flash);
+ }
+
+ @override
+ void updateRenderObject(
+ BuildContext context,
+ RenderLevel renderObject,
+ ) {
+ renderObject
+ ..height = height
+ ..level = level
+ ..flash = flash;
+ }
+}
+
+class RenderLevel extends RenderBox {
+ RenderLevel(this._height, this._level, this._flash);
+
+ double _height;
+ set height(double value) {
+ if (_height == value) return;
+ _height = value;
+ markNeedsLayout();
+ }
+
+ int _level;
+ set level(int value) {
+ if (_level == value) return;
+ _level = value;
+ markNeedsPaint();
+ markNeedsSemanticsUpdate();
+ }
+
+ bool _flash;
+ set flash(bool value) {
+ if (_flash == value) return;
+ _flash = value;
+ markNeedsLayout();
+ }
+
+ @override
+ Size computeDryLayout(covariant BoxConstraints constraints) {
+ return constraints.constrainSizeAndAttemptToPreserveAspectRatio(
+ Size(
+ (_flash ? LevelCanvas._extendR : LevelCanvas._totalR) * _height / LevelCanvas._totalB,
+ _height,
+ ),
+ );
+ }
+
+ @override
+ void performLayout() {
+ size = computeDryLayout(constraints);
+ }
+
+ @override
+ void paint(PaintingContext context, Offset offset) {
+ final paint = Paint()..color = lookupBackgroundColor(_level);
+ LevelCanvas(context.canvas)
+ ..save()
+ ..translate(offset.dx, offset.dy)
+ ..scale(size.height / LevelCanvas._totalB)
+ ..drawLevelBack(paint, bolt: _flash)
+ ..drawLevelLv()
+ ..drawLEDigit(_level, paint..color = Colors.white)
+ ..restore();
+ }
+
+ @override
+ void describeSemanticsConfiguration(SemanticsConfiguration config) {
+ super.describeSemanticsConfiguration(config);
+ config.label = '${_flash ? "硬核" : ""}$_level级';
+ }
+
+ static Color lookupBackgroundColor(int level) {
+ return switch (level) {
+ 0 || 1 => const Color(0xFFC0C0C0),
+ 2 => const Color(0xFF8BD29B),
+ 3 => const Color(0xFF7BCDEF),
+ 4 => const Color(0xFFFEBB8B),
+ 5 => const Color(0xFFEE672A),
+ _ => const Color(0xFFF04C49),
+ };
+ }
+}
+
+extension type LevelCanvas(Canvas _) implements Canvas {
+ // ========== 布局常量 ==========
+ static const _r = Radius.circular(20);
+
+ static const double _left = 629;
+ static const double _right = 877;
+ static const double _colW = 68; // 竖段宽度
+ static const double _lColR = _left + _colW; // 697
+ static const double _rColL = _right - _colW; // 810
+
+ // 三条横线的边界
+ static const double _rowH = 68;
+ static const double _rowSp = 146;
+ static const double _topY = 55;
+ static const double _topYB = _topY + _rowH; // 123
+ static const double _midY = _topY + _rowSp; // 201
+ static const double _midYB = _midY + _rowH; // 269
+ static const double _botY = _midY + _rowSp; // 347
+ static const double _botYB = _botY + _rowH; // 415
+
+ // 竖段拼接用的中心线
+ static const double _midMid = (_midY + _midYB) / 2; // 235
+
+ static final _boltIcon =
+ (ParagraphBuilder(
+ ParagraphStyle(
+ fontSize: 460,
+ fontFamily: Icons.bolt_rounded.fontFamily,
+ height: 1,
+ fontWeight: FontWeight.w900,
+ textDirection: TextDirection.ltr,
+ ),
+ )..addText(.fromCharCode(Icons.bolt_rounded.codePoint))).build()
+ ..layout(const ParagraphConstraints(width: double.infinity));
+ void drawBolt() => drawParagraph(_boltIcon, const Offset(840, 5));
+
+ void _draw1(Paint paint) {
+ drawRRect(const .fromLTRBXY(673, _botY, 833, _botYB, 20, 20), paint);
+ drawRRect(.fromLTRBAndCorners(673, _topY, 787, _topYB, topLeft: _r, bottomLeft: _r, topRight: _r), paint);
+ drawRect(const .fromLTRB(719, _topYB, 787, _botY), paint);
+ }
+
+ void drawLEDigit(int digit, Paint paint) {
+ if (digit == 1) return _draw1(paint);
+ final bits = switch (digit) {
+ 0 => 0x7E,
+ 2 => 0x6D,
+ 3 => 0x79,
+ 4 => 0x33,
+ 5 => 0x5B,
+ 6 => 0x5F,
+ 7 => 0x70,
+ 8 => 0x7F,
+ 9 => 0x7B,
+ // _ => throw ArgumentError('Unsupported digit: $digit'),
+ _ => 0x4F, // `E`
+ };
+
+ _drawSegments(
+ bits & 0x40 != 0,
+ bits & 0x20 != 0,
+ bits & 0x10 != 0,
+ bits & 0x08 != 0,
+ bits & 0x04 != 0,
+ bits & 0x02 != 0,
+ bits & 0x01 != 0,
+ paint,
+ );
+ }
+
+ void _drawSegments(bool a, bool b, bool c, bool d, bool e, bool f, bool g, Paint paint) {
+ // 横段
+ if (a) {
+ _drawRRect(_left, _topY, _right, _topYB, _r, _r, f ? .zero : _r, b ? .zero : _r, paint);
+ }
+ if (g) {
+ _drawRRect(_left, _midY, _right, _midYB, f ? .zero : _r, b ? .zero : _r, e ? .zero : _r, c ? .zero : _r, paint);
+ }
+ if (d) {
+ _drawRRect(_left, _botY, _right, _botYB, e ? .zero : _r, c ? .zero : _r, _r, _r, paint);
+ }
+
+ // 竖段
+ // 左上竖段 f
+ if (f) {
+ final top = (a ? _topYB : _topY) - 1; // 有上横则齐底,否则到顶
+ final bottom = (g ? _midY : (e ? _midMid : _midYB)) + 1;
+ final rTop = a ? Radius.zero : _r;
+ final rBot = g || e ? Radius.zero : _r;
+ _drawRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, paint);
+ }
+
+ // 右上竖段 b
+ if (b) {
+ final top = (a ? _topYB : _topY) - 1;
+ final bottom = (g ? _midY : (c ? _midMid : _midYB)) + 1;
+ final rTop = a ? Radius.zero : _r;
+ final rBot = g || c ? Radius.zero : _r;
+ _drawRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, paint);
+ }
+
+ // 左下竖段 e
+ if (e) {
+ final top = (g ? _midYB : (f ? _midMid : _midY)) - 1;
+ final bottom = (d ? _botY : _botYB) + 1;
+ final rTop = g || f ? Radius.zero : _r;
+ final rBot = d ? Radius.zero : _r;
+ _drawRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, paint);
+ }
+
+ // 右下竖段 c
+ if (c) {
+ final top = (g ? _midYB : (b ? _midMid : _midY)) - 1;
+ final bottom = (d ? _botY : _botYB) + 1;
+ final rTop = g || b ? Radius.zero : _r;
+ final rBot = d ? Radius.zero : _r;
+ _drawRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, paint);
+ }
+ }
+
+ /// 绘制圆角矩形,四角全零时退化为矩形
+ void _drawRRect(double l, double t, double r, double b, Radius tl, Radius tr, Radius bl, Radius br, Paint paint) {
+ if (tl == .zero && tr == .zero && bl == .zero && br == .zero) {
+ drawRect(.fromLTRB(l, t, r, b), paint);
+ } else {
+ drawRRect(.fromLTRBAndCorners(l, t, r, b, topLeft: tl, topRight: tr, bottomLeft: bl, bottomRight: br), paint);
+ }
+ }
+
+ static final _lvPicture = () {
+ final recorder = PictureRecorder();
+ final paint = Paint()..color = Colors.white;
+ final canvas = Canvas(recorder);
+
+ const double vLeft = 296;
+ const double lvTop = 106;
+ const double llr = 123;
+ const double vtb = 282;
+
+ canvas
+ // L
+ ..drawRRect(.fromLTRBAndCorners(56, lvTop, llr, _botYB, topLeft: _r, topRight: _r, bottomLeft: _r), paint)
+ ..drawRRect(.fromLTRBAndCorners(llr - 1, _botY, 256, _botYB, topRight: _r, bottomRight: _r), paint)
+ // V
+ ..drawRRect(.fromLTRBAndCorners(vLeft, lvTop, 363, vtb + 1, topLeft: _r, topRight: _r), paint)
+ ..drawRRect(.fromLTRBAndCorners(476, lvTop, 543, vtb + 1, topLeft: _r, topRight: _r), paint)
+ ..drawPath(
+ Path()
+ ..moveTo(vLeft, vtb)
+ ..lineTo(vLeft, 292)
+ ..arcToPoint(const Offset(300, 313), radius: const .circular(50), clockwise: false)
+ ..lineTo(395, 408)
+ ..arcToPoint(const Offset(444, 408), radius: const .circular(50), clockwise: false)
+ ..lineTo(539, 313)
+ ..arcToPoint(const Offset(543, 292), radius: const .circular(50), clockwise: false)
+ ..lineTo(543, vtb)
+ ..lineTo(476, vtb)
+ ..lineTo(419.5, 340)
+ ..lineTo(363, vtb)
+ ..close(),
+ paint,
+ );
+ return recorder.endRecording();
+ }();
+
+ void drawLevelLv() => drawPicture(_lvPicture);
+
+ static const double _totalR = 930;
+ static const double _extendR = 1250;
+ static const double _totalB = 466;
+
+ void drawLevelBack(Paint paint, {bool bolt = false}) {
+ const radius = Radius.circular(27);
+ final double right = bolt ? _extendR : _totalR;
+ const double blockTop = 48;
+ drawRRect(
+ RRect.fromLTRBAndCorners(0, blockTop, right, _totalB, topLeft: radius, bottomLeft: radius, bottomRight: radius),
+ paint,
+ );
+ drawRRect(
+ RRect.fromLTRBAndCorners(576, 0, right, blockTop + 1, topLeft: radius, topRight: radius),
+ paint,
+ );
+
+ if (bolt) drawBolt();
+ }
+}
diff --git a/lib/common/widgets/svg/play_icon.dart b/lib/common/widgets/svg/play_icon.dart
new file mode 100644
index 000000000..2b64a0958
--- /dev/null
+++ b/lib/common/widgets/svg/play_icon.dart
@@ -0,0 +1,218 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/semantics.dart';
+
+class PlayIcon extends LeafRenderObjectWidget {
+ const PlayIcon({super.key, this.size = 60});
+
+ final double size;
+
+ @override
+ RenderObject createRenderObject(BuildContext context) {
+ return RenderPlay(size);
+ }
+
+ @override
+ void updateRenderObject(BuildContext context, RenderPlay renderObject) {
+ renderObject.imgSize = size;
+ }
+}
+
+class RenderPlay extends RenderBox {
+ RenderPlay(this._imgSize);
+
+ double _imgSize;
+ set imgSize(double value) {
+ if (_imgSize == value) return;
+ _imgSize = value;
+ markNeedsLayout();
+ }
+
+ @override
+ Size computeDryLayout(covariant BoxConstraints constraints) {
+ return constraints.constrainDimensions(_imgSize, _imgSize);
+ }
+
+ @override
+ void performLayout() {
+ size = computeDryLayout(constraints);
+ }
+
+ @override
+ void paint(PaintingContext context, Offset offset) {
+ final canvas = context.canvas;
+ final size = this.size.shortestSide;
+ if (offset != .zero || size != 60) {
+ canvas.save();
+ if (offset != .zero) canvas.translate(offset.dx, offset.dy);
+ if (size != 60) {
+ canvas.scale(size / 60);
+ }
+ }
+ canvas.drawPicture(_picture);
+ if (offset != .zero || size != 60) {
+ canvas.restore();
+ }
+ }
+
+ @override
+ void describeSemanticsConfiguration(SemanticsConfiguration config) {
+ super.describeSemanticsConfiguration(config);
+ config.label = '播放';
+ }
+
+ /// [SvgPicture] can not parse mask filter
+ /// fom i0.hdslb.com/bfs/static/player/img/play.svg
+ /// scale size from 80 to 60
+ static final _picture = () {
+ final rec = PictureRecorder();
+ final canvas = Canvas(rec);
+ final path = Path()
+ ..moveTo(41.576, 7.318)
+ ..cubicTo(41.244, 5.892, 39.91, 4.886, 38.41, 5.011)
+ ..cubicTo(38.068, 5.039, 37.813, 5.13, 37.59, 5.245)
+ ..cubicTo(37.37, 5.361, 37.187, 5.506, 37.034, 5.672)
+ ..cubicTo(36.957, 5.754, 36.891, 5.844, 36.824, 5.934)
+ ..lineTo(36.622, 6.203)
+ ..lineTo(36.222, 6.743)
+ ..cubicTo(35.694, 7.467, 35.178, 8.2, 34.678, 8.945)
+ ..cubicTo(34.179, 9.69, 33.694, 10.445, 33.231, 11.217)
+ ..cubicTo(33.092, 11.449, 32.954, 11.683, 32.819, 11.917)
+ ..cubicTo(32.258, 11.909, 31.697, 11.902, 31.137, 11.898)
+ ..cubicTo(29.094, 11.884, 27.051, 11.891, 25.008, 11.926)
+ ..cubicTo(24.871, 11.688, 24.732, 11.452, 24.591, 11.217)
+ ..cubicTo(24.128, 10.445, 23.643, 9.69, 23.144, 8.945)
+ ..cubicTo(22.645, 8.2, 22.129, 7.467, 21.6, 6.743)
+ ..lineTo(21.2, 6.203)
+ ..lineTo(20.998, 5.934)
+ ..cubicTo(20.931, 5.844, 20.865, 5.754, 20.788, 5.672)
+ ..cubicTo(20.635, 5.506, 20.452, 5.361, 20.232, 5.245)
+ ..cubicTo(20.009, 5.13, 19.754, 5.039, 19.412, 5.011)
+ ..cubicTo(17.956, 4.888, 16.59, 5.85, 16.246, 7.318)
+ ..cubicTo(16.168, 7.652, 16.176, 7.924, 16.217, 8.172)
+ ..cubicTo(16.26, 8.418, 16.34, 8.636, 16.451, 8.833)
+ ..cubicTo(16.506, 8.931, 16.571, 9.023, 16.635, 9.114)
+ ..lineTo(16.829, 9.389)
+ ..lineTo(17.219, 9.936)
+ ..cubicTo(17.743, 10.663, 18.281, 11.381, 18.834, 12.086)
+ ..cubicTo(18.845, 12.099, 18.855, 12.112, 18.865, 12.124)
+ ..cubicTo(18.025, 12.164, 17.184, 12.209, 16.344, 12.26)
+ ..cubicTo(15.523, 12.311, 14.701, 12.365, 13.88, 12.428)
+ ..lineTo(12.648, 12.525)
+ ..lineTo(12.032, 12.577)
+ ..lineTo(11.68, 12.616)
+ ..cubicTo(11.562, 12.63, 11.445, 12.651, 11.328, 12.668)
+ ..cubicTo(10.39, 12.827, 9.477, 13.141, 8.641, 13.595)
+ ..cubicTo(7.804, 14.049, 7.043, 14.641, 6.399, 15.34)
+ ..cubicTo(5.754, 16.04, 5.224, 16.845, 4.837, 17.716)
+ ..cubicTo(4.45, 18.586, 4.208, 19.521, 4.12, 20.467)
+ ..cubicTo(3.808, 23.756, 3.603, 27.055, 3.529, 30.365)
+ ..cubicTo(3.453, 33.676, 3.53, 36.99, 3.722, 40.289)
+ ..cubicTo(3.77, 41.114, 3.825, 41.939, 3.887, 42.763)
+ ..lineTo(3.986, 43.998)
+ ..lineTo(4.039, 44.616)
+ ..lineTo(4.046, 44.693)
+ ..lineTo(4.056, 44.782)
+ ..lineTo(4.075, 44.961)
+ ..cubicTo(4.087, 45.08, 4.107, 45.198, 4.126, 45.317)
+ ..cubicTo(4.278, 46.264, 4.586, 47.189, 5.037, 48.037)
+ ..cubicTo(5.486, 48.887, 6.078, 49.66, 6.777, 50.319)
+ ..cubicTo(7.475, 50.978, 8.283, 51.522, 9.16, 51.921)
+ ..cubicTo(10.035, 52.319, 10.978, 52.575, 11.935, 52.664)
+ ..cubicTo(11.998, 52.672, 12.047, 52.675, 12.098, 52.68)
+ ..lineTo(12.252, 52.693)
+ ..lineTo(12.56, 52.72)
+ ..lineTo(13.176, 52.771)
+ ..lineTo(14.408, 52.868)
+ ..cubicTo(15.23, 52.927, 16.052, 52.985, 16.874, 53.033)
+ ..cubicTo(23.449, 53.424, 30.03, 53.502, 36.609, 53.259)
+ ..cubicTo(38.254, 53.199, 39.898, 53.118, 41.542, 53.016)
+ ..cubicTo(42.364, 52.963, 43.186, 52.908, 44.008, 52.843)
+ ..lineTo(45.241, 52.743)
+ ..lineTo(45.857, 52.689)
+ ..lineTo(46.214, 52.65)
+ ..cubicTo(46.334, 52.635, 46.452, 52.614, 46.571, 52.596)
+ ..cubicTo(47.52, 52.432, 48.443, 52.112, 49.288, 51.649)
+ ..cubicTo(50.134, 51.188, 50.902, 50.586, 51.553, 49.878)
+ ..cubicTo(52.204, 49.17, 52.739, 48.353, 53.127, 47.471)
+ ..cubicTo(53.321, 47.03, 53.479, 46.573, 53.598, 46.107)
+ ..cubicTo(53.631, 45.991, 53.656, 45.873, 53.681, 45.755)
+ ..lineTo(53.719, 45.579)
+ ..lineTo(53.749, 45.401)
+ ..cubicTo(53.77, 45.283, 53.79, 45.164, 53.803, 45.045)
+ ..cubicTo(53.818, 44.927, 53.834, 44.8, 53.843, 44.704)
+ ..cubicTo(54.179, 41.414, 54.402, 38.111, 54.476, 34.794)
+ ..cubicTo(54.553, 31.475, 54.442, 28.153, 54.205, 24.853)
+ ..cubicTo(54.145, 24.028, 54.078, 23.204, 54.002, 22.38)
+ ..lineTo(53.884, 21.145)
+ ..lineTo(53.82, 20.528)
+ ..lineTo(53.804, 20.374)
+ ..lineTo(53.794, 20.29)
+ ..lineTo(53.782, 20.201)
+ ..cubicTo(53.766, 20.083, 53.754, 19.964, 53.731, 19.846)
+ ..cubicTo(53.578, 18.901, 53.266, 17.979, 52.813, 17.136)
+ ..cubicTo(52.362, 16.291, 51.771, 15.522, 51.073, 14.869)
+ ..cubicTo(50.375, 14.215, 49.57, 13.677, 48.698, 13.284)
+ ..cubicTo(47.827, 12.89, 46.89, 12.64, 45.94, 12.552)
+ ..lineTo(45.854, 12.544)
+ ..lineTo(45.777, 12.537)
+ ..lineTo(45.623, 12.524)
+ ..lineTo(45.315, 12.499)
+ ..lineTo(44.698, 12.449)
+ ..lineTo(43.466, 12.357)
+ ..cubicTo(42.644, 12.3, 41.822, 12.247, 41.0, 12.202)
+ ..cubicTo(40.326, 12.164, 39.651, 12.131, 38.977, 12.1)
+ ..cubicTo(38.98, 12.096, 38.984, 12.091, 38.988, 12.086)
+ ..cubicTo(39.542, 11.381, 40.079, 10.663, 40.603, 9.936)
+ ..lineTo(40.994, 9.389)
+ ..lineTo(41.187, 9.114)
+ ..cubicTo(41.252, 9.023, 41.316, 8.931, 41.371, 8.833)
+ ..cubicTo(41.482, 8.636, 41.563, 8.418, 41.605, 8.172)
+ ..cubicTo(41.646, 7.924, 41.654, 7.652, 41.576, 7.318)
+ ..close()
+ ..moveTo(21.283, 26.038)
+ ..cubicTo(21.321, 25.666, 21.427, 25.305, 21.597, 24.973)
+ ..cubicTo(22.351, 23.498, 24.158, 22.913, 25.634, 23.667)
+ ..lineTo(26.683, 24.211)
+ ..cubicTo(28.428, 25.126, 30.148, 26.088, 31.842, 27.097)
+ ..cubicTo(34.726, 28.814, 34.726, 28.814, 37.376, 30.628)
+ ..cubicTo(37.694, 30.846, 37.967, 31.123, 38.18, 31.444)
+ ..cubicTo(39.096, 32.824, 38.72, 34.686, 37.34, 35.603)
+ ..lineTo(36.265, 36.309)
+ ..cubicTo(34.823, 37.245, 33.349, 38.161, 31.842, 39.058)
+ ..cubicTo(28.87, 40.828, 28.87, 40.828, 25.698, 42.513)
+ ..cubicTo(25.352, 42.697, 24.973, 42.811, 24.583, 42.849)
+ ..cubicTo(22.934, 43.01, 21.466, 41.805, 21.305, 40.156)
+ ..lineTo(21.221, 39.247)
+ ..cubicTo(21.04, 37.126, 20.949, 35.005, 20.949, 32.884)
+ ..cubicTo(20.949, 29.361, 20.949, 29.361, 21.283, 26.038)
+ ..close();
+
+ final paint = Paint()
+ ..color = Colors.black.withValues(alpha: 0.3 * 0.8)
+ ..maskFilter = const .blur(.normal, 1.0);
+
+ // feOffset dy="2"
+ canvas
+ ..save()
+ ..translate(0, 2)
+ ..drawPath(path, paint)
+ ..restore();
+
+ // dy=0, blur=3.5
+ paint
+ ..color = Colors.black.withValues(alpha: 0.2 * 0.8)
+ ..maskFilter = const .blur(.normal, 3.5);
+
+ canvas.drawPath(path, paint);
+
+ paint
+ ..color = Colors.white.withValues(alpha: 0.8)
+ ..maskFilter = null;
+
+ canvas.drawPath(path, paint);
+
+ return rec.endRecording();
+ }();
+}
diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart
index cde21a56f..165037467 100644
--- a/lib/common/widgets/video_popup_menu.dart
+++ b/lib/common/widgets/video_popup_menu.dart
@@ -147,134 +147,115 @@ class VideoPopupMenu extends StatelessWidget {
showDialog(
context: context,
builder: (context) {
- return AlertDialog(
- content: SingleChildScrollView(
- child: Column(
- crossAxisAlignment: .start,
- children: [
- if (tp.dislikeReasons != null) ...[
- const Text('我不想看'),
- const SizedBox(height: 5),
- Wrap(
- spacing: 8.0,
- runSpacing: 8.0,
- children: tp.dislikeReasons!.map((
- item,
- ) {
- return actionButton(item, null);
- }).toList(),
- ),
- ],
- if (tp.feedbacks != null) ...[
- const SizedBox(height: 5),
- const Text('反馈'),
- const SizedBox(height: 5),
- Wrap(
- spacing: 8.0,
- runSpacing: 8.0,
- children: tp.feedbacks!.map((item) {
- return actionButton(null, item);
- }).toList(),
- ),
- ],
- const Divider(),
- Center(
- child: FilledButton.tonal(
- onPressed: () async {
- SmartDialog.showLoading(
- msg: '正在提交',
+ return SimpleDialog(
+ contentPadding: const .fromLTRB(24, 16, 24, 24),
+ children: [
+ if (tp.dislikeReasons != null) ...[
+ const Text('我不想看'),
+ const SizedBox(height: 5),
+ Wrap(
+ spacing: 8.0,
+ runSpacing: 8.0,
+ children: tp.dislikeReasons!
+ .map((item) => actionButton(item, null))
+ .toList(),
+ ),
+ ],
+ if (tp.feedbacks != null) ...[
+ const SizedBox(height: 5),
+ const Text('反馈'),
+ const SizedBox(height: 5),
+ Wrap(
+ spacing: 8.0,
+ runSpacing: 8.0,
+ children: tp.feedbacks!
+ .map((item) => actionButton(null, item))
+ .toList(),
+ ),
+ ],
+ const Divider(),
+ Center(
+ child: FilledButton.tonal(
+ onPressed: () async {
+ SmartDialog.showLoading(
+ msg: '正在提交',
+ );
+ final res =
+ await VideoHttp.feedDislikeCancel(
+ id: item.param!,
+ goto: item.goto!,
);
- final res =
- await VideoHttp.feedDislikeCancel(
- id: item.param!,
- goto: item.goto!,
- );
- SmartDialog.dismiss();
- SmartDialog.showToast(
- res.isSuccess
- ? "成功"
- : res.toString(),
- );
- Get.back();
- },
- style: FilledButton.styleFrom(
- visualDensity: VisualDensity.compact,
- ),
- child: const Text("撤销"),
- ),
+ SmartDialog.dismiss();
+ SmartDialog.showToast(
+ res.isSuccess ? "成功" : res.toString(),
+ );
+ Get.back();
+ },
+ style: FilledButton.styleFrom(
+ visualDensity: VisualDensity.compact,
),
- ],
+ child: const Text("撤销"),
+ ),
),
- ),
+ ],
);
},
);
} else {
showDialog(
context: context,
- builder: (context) => AlertDialog(
- content: SingleChildScrollView(
- child: Column(
+ builder: (context) => SimpleDialog(
+ contentPadding: const .all(24),
+ children: [
+ const Center(child: Text("web端暂不支持精细选择")),
+ const SizedBox(height: 5),
+ Wrap(
+ spacing: 5.0,
+ runSpacing: 2.0,
+ alignment: .center,
children: [
- const SizedBox(height: 5),
- const Text("web端暂不支持精细选择"),
- const SizedBox(height: 5),
- Wrap(
- spacing: 5.0,
- runSpacing: 2.0,
- children: [
- FilledButton.tonal(
- onPressed: () async {
- Get.back();
- SmartDialog.showLoading(
- msg: '正在提交',
- );
- final res =
- await VideoHttp.dislikeVideo(
- bvid: videoItem.bvid!,
- type: true,
- );
- SmartDialog.dismiss();
- if (res.isSuccess) {
- SmartDialog.showToast('点踩成功');
- onRemove?.call();
- } else {
- res.toast();
- }
- },
- style: FilledButton.styleFrom(
- visualDensity: VisualDensity.compact,
- ),
- child: const Text("点踩"),
- ),
- FilledButton.tonal(
- onPressed: () async {
- Get.back();
- SmartDialog.showLoading(
- msg: '正在提交',
- );
- final res =
- await VideoHttp.dislikeVideo(
- bvid: videoItem.bvid!,
- type: false,
- );
- SmartDialog.dismiss();
- SmartDialog.showToast(
- res.isSuccess
- ? '取消踩'
- : res.toString(),
- );
- },
- style: FilledButton.styleFrom(
- visualDensity: VisualDensity.compact,
- ),
- child: const Text("撤销"),
- ),
- ],
+ FilledButton.tonal(
+ onPressed: () async {
+ Get.back();
+ SmartDialog.showLoading(msg: '正在提交');
+ final res = await VideoHttp.dislikeVideo(
+ bvid: videoItem.bvid!,
+ type: true,
+ );
+ SmartDialog.dismiss();
+ if (res.isSuccess) {
+ SmartDialog.showToast('点踩成功');
+ onRemove?.call();
+ } else {
+ res.toast();
+ }
+ },
+ style: FilledButton.styleFrom(
+ visualDensity: .compact,
+ ),
+ child: const Text("点踩"),
+ ),
+ FilledButton.tonal(
+ onPressed: () async {
+ Get.back();
+ SmartDialog.showLoading(msg: '正在提交');
+ final res = await VideoHttp.dislikeVideo(
+ bvid: videoItem.bvid!,
+ type: false,
+ );
+ SmartDialog.dismiss();
+ SmartDialog.showToast(
+ res.isSuccess ? '取消踩' : res.toString(),
+ );
+ },
+ style: FilledButton.styleFrom(
+ visualDensity: .compact,
+ ),
+ child: const Text("撤销"),
),
],
),
- ),
+ ],
),
);
}
@@ -298,9 +279,7 @@ class VideoPopupMenu extends StatelessWidget {
child: Text(
'点错了',
style: TextStyle(
- color: Theme.of(
- context,
- ).colorScheme.outline,
+ color: ColorScheme.of(context).outline,
),
),
),
diff --git a/lib/grpc/dm.dart b/lib/grpc/dm.dart
index 5c5c1f447..74ffb7bdc 100644
--- a/lib/grpc/dm.dart
+++ b/lib/grpc/dm.dart
@@ -21,4 +21,12 @@ abstract final class DmGrpc {
isolate: true,
);
}
+
+ static Future> dmView(int aid, int cid) {
+ return GrpcReq.request(
+ GrpcUrl.dmView,
+ DmViewReq(pid: Int64(aid), oid: Int64(cid), type: 1),
+ DmViewReply.fromBuffer,
+ );
+ }
}
diff --git a/lib/grpc/url.dart b/lib/grpc/url.dart
index 6ace7c1cc..78238ef02 100644
--- a/lib/grpc/url.dart
+++ b/lib/grpc/url.dart
@@ -14,6 +14,7 @@ abstract final class GrpcUrl {
// danmaku
static const dmSegMobile = '/bilibili.community.service.dm.v1.DM/DmSegMobile';
+ static const dmView = '/bilibili.community.service.dm.v1.DM/DmView';
// reply
static const reply = '/bilibili.main.community.reply.v1.Reply';
diff --git a/lib/http/browser_ua.dart b/lib/http/browser_ua.dart
index 9414fbd1c..8211c2ebc 100644
--- a/lib/http/browser_ua.dart
+++ b/lib/http/browser_ua.dart
@@ -7,5 +7,5 @@ abstract final class BrowserUa {
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15';
static const mob =
- 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36';
+ 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36 os/android build/8430300 osVer/10 sdkInt/29 network/2 BiliApp/8430300 mobi_app/android_q channel/master innerVer/8430300';
}
diff --git a/lib/http/download.dart b/lib/http/download.dart
index 66244d8f5..9c14f50f2 100644
--- a/lib/http/download.dart
+++ b/lib/http/download.dart
@@ -2,7 +2,6 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/common/video/audio_quality.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_type.dart';
import 'package:PiliPlus/models/video/play/url.dart';
@@ -40,9 +39,9 @@ abstract final class DownloadHttp {
},
);
if (res case Success(:final response)) {
- final Dash? dash = response.dash;
+ final dash = response.dash;
if (dash != null) {
- final List videoList = dash.video!;
+ final videoList = dash.video!;
final curHighestVideoQa = videoList.first.quality.code;
final preferVideoQa = entry.preferedVideoQuality;
int targetVideoQa = curHighestVideoQa;
@@ -55,19 +54,18 @@ abstract final class DownloadHttp {
);
}
- /// 取出符合当前画质的videoList
- final List videosList = videoList
- .where((e) => e.quality.code == targetVideoQa)
- .toList();
-
/// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式
- final List supportFormats = response.supportFormats!;
+ final supportFormats = response.supportFormats!;
// 根据画质选编码格式
- final FormatItem targetSupportFormats = supportFormats.firstWhere(
+ final targetSupportFormats = supportFormats.firstWhere(
(e) => e.quality == targetVideoQa,
orElse: () => supportFormats.first,
);
- final List supportDecodeFormats = targetSupportFormats.codecs!;
+
+ final currentDecodeFormats = VideoUtils.selectCodec(
+ targetSupportFormats.codecs!,
+ Pref.preferCodecs,
+ );
entry
..typeTag = targetVideoQa.toString()
@@ -77,31 +75,10 @@ abstract final class DownloadHttp {
targetSupportFormats.newDesc ??
VideoQuality.fromCode(targetVideoQa).desc;
- String preferDecode = Pref.defaultDecode; // def avc
- String preferSecondDecode = Pref.secondDecode; // def av1
-
- // 默认从设置中取AV1
- VideoDecodeFormatType currentDecodeFormats =
- VideoDecodeFormatType.fromString(preferDecode);
- VideoDecodeFormatType secondDecodeFormats =
- VideoDecodeFormatType.fromString(preferSecondDecode);
- // 当前视频没有对应格式返回第一个
- int flag = 0;
- for (final e in supportDecodeFormats) {
- if (currentDecodeFormats.codes.any(e.startsWith)) {
- flag = 1;
- break;
- } else if (secondDecodeFormats.codes.any(e.startsWith)) {
- flag = 2;
- }
- }
- if (flag == 2) {
- currentDecodeFormats = secondDecodeFormats;
- } else if (flag == 0) {
- currentDecodeFormats = VideoDecodeFormatType.fromString(
- supportDecodeFormats.first,
- );
- }
+ /// 取出符合当前画质的videoList
+ final videosList = videoList
+ .where((e) => e.quality.code == targetVideoQa)
+ .toList();
/// 取出符合当前解码格式的videoItem
final videoDash = videosList.firstWhere(
diff --git a/lib/http/sponsor_block.dart b/lib/http/sponsor_block.dart
index 553f2f051..0dfaa69b6 100644
--- a/lib/http/sponsor_block.dart
+++ b/lib/http/sponsor_block.dart
@@ -79,7 +79,7 @@ abstract final class SponsorBlock {
int? type,
SegmentType? category,
}) async {
- assert((type == null) == (category == null));
+ assert((type == null) != (category == null));
final res = await Request().post(
_api(SponsorBlockApi.voteOnSponsorTime),
queryParameters: {
diff --git a/lib/main.dart b/lib/main.dart
index c3fe0d64f..05b649822 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -110,8 +110,6 @@ void main() async {
..lazyPut(DownloadService.new);
HttpOverrides.global = _CustomHttpOverrides();
- CacheManager.autoClearCache();
-
if (PlatformUtils.isMobile) {
if (Platform.isAndroid) MaxScreenSize.init();
await Future.wait([
diff --git a/lib/models/common/setting_type.dart b/lib/models/common/setting_type.dart
index 366623f60..64abaa232 100644
--- a/lib/models/common/setting_type.dart
+++ b/lib/models/common/setting_type.dart
@@ -1,3 +1,11 @@
+import 'package:PiliPlus/pages/setting/models/extra_settings.dart';
+import 'package:PiliPlus/pages/setting/models/model.dart';
+import 'package:PiliPlus/pages/setting/models/play_settings.dart';
+import 'package:PiliPlus/pages/setting/models/privacy_settings.dart';
+import 'package:PiliPlus/pages/setting/models/recommend_settings.dart';
+import 'package:PiliPlus/pages/setting/models/style_settings.dart';
+import 'package:PiliPlus/pages/setting/models/video_settings.dart';
+
enum SettingType {
privacySetting('隐私设置'),
recommendSetting('推荐流设置'),
@@ -11,4 +19,14 @@ enum SettingType {
final String title;
const SettingType(this.title);
+
+ List get settings => switch (this) {
+ .privacySetting => privacySettings,
+ .recommendSetting => recommendSettings,
+ .videoSetting => videoSettings,
+ .playSetting => playSettings,
+ .styleSetting => styleSettings,
+ .extraSetting => extraSettings,
+ _ => throw UnimplementedError(),
+ };
}
diff --git a/lib/models/common/video/video_decode_type.dart b/lib/models/common/video/video_decode_type.dart
index f17ec8c67..4020de3f2 100644
--- a/lib/models/common/video/video_decode_type.dart
+++ b/lib/models/common/video/video_decode_type.dart
@@ -12,9 +12,6 @@ enum VideoDecodeFormatType {
const VideoDecodeFormatType(this.codes);
- static VideoDecodeFormatType fromCode(String code) =>
- values.firstWhere((i) => i.codes.contains(code));
-
static VideoDecodeFormatType fromString(String val) =>
values.firstWhere((i) => i.codes.any(val.startsWith));
}
diff --git a/lib/models_new/video/video_play_info/subtitle.dart b/lib/models_new/video/video_play_info/subtitle.dart
index 797adb963..f3bf28306 100644
--- a/lib/models_new/video/video_play_info/subtitle.dart
+++ b/lib/models_new/video/video_play_info/subtitle.dart
@@ -1,4 +1,4 @@
-class Subtitle {
+class Subtitle implements Comparable {
late String lan;
String? lanDoc;
String? subtitleUrl;
@@ -8,6 +8,8 @@ class Subtitle {
Subtitle({
required this.lan,
this.lanDoc,
+ this.subtitleUrl,
+ this.isAi = false,
});
Subtitle.fromJson(Map json) {
@@ -17,4 +19,13 @@ class Subtitle {
subtitleUrl = json["subtitle_url"];
subtitleUrlV2 = json["subtitle_url_v2"];
}
+
+ @override
+ int compareTo(Subtitle other) {
+ final thisHasZh = lan.contains('zh');
+ final otherHasZh = other.lan.contains('zh');
+ if (thisHasZh != otherHasZh) return thisHasZh ? -1 : 1;
+ if (isAi != other.isAi) return isAi ? 1 : -1;
+ return 0;
+ }
}
diff --git a/lib/models_new/video/video_play_info/subtitle_info.dart b/lib/models_new/video/video_play_info/subtitle_info.dart
index d559c1697..33e886186 100644
--- a/lib/models_new/video/video_play_info/subtitle_info.dart
+++ b/lib/models_new/video/video_play_info/subtitle_info.dart
@@ -14,12 +14,6 @@ class SubtitleInfo {
(json['subtitles'] as List?)
?.map((e) => Subtitle.fromJson(e as Map))
.toList()
- ?..sort((a, b) {
- final aHasZh = a.lan.contains('zh');
- final bHasZh = b.lan.contains('zh');
- if (aHasZh != bHasZh) return aHasZh ? -1 : 1;
- if (a.isAi != b.isAi) return a.isAi ? 1 : -1;
- return 0;
- }),
+ ?..sort(),
);
}
diff --git a/lib/pages/about/view.dart b/lib/pages/about/view.dart
index 3a232286d..a07c4b685 100644
--- a/lib/pages/about/view.dart
+++ b/lib/pages/about/view.dart
@@ -7,6 +7,7 @@ import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/services/logger.dart';
@@ -276,9 +277,8 @@ Commit Hash: ${BuildConfig.commitHash}''',
clipBehavior: Clip.hardEdge,
title: const Text('是否重置所有设置?'),
children: [
- ListTile(
- dense: true,
- onTap: () async {
+ DialogOption(
+ onPressed: () async {
Get.back();
await Future.wait([
GStorage.setting.clear(),
@@ -286,16 +286,15 @@ Commit Hash: ${BuildConfig.commitHash}''',
]);
SmartDialog.showToast('重置成功');
},
- title: const Text('重置可导出的设置', style: style),
+ child: const Text('重置可导出的设置', style: style),
),
- ListTile(
- dense: true,
- onTap: () async {
+ DialogOption(
+ onPressed: () async {
Get.back();
await GStorage.clear();
SmartDialog.showToast('重置成功');
},
- title: const Text('重置所有数据(含登录信息)', style: style),
+ child: const Text('重置所有数据(含登录信息)', style: style),
),
],
);
diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart
index 869882c8a..e6257447b 100644
--- a/lib/pages/article/widgets/opus_content.dart
+++ b/lib/pages/article/widgets/opus_content.dart
@@ -19,6 +19,7 @@ import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/image_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:cached_network_image_ce/cached_network_image.dart';
+import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_main.dart';
@@ -273,14 +274,14 @@ class OpusContent extends StatelessWidget {
case 5 when (element.list != null):
return SelectableText.rich(
TextSpan(
- children: element.list!.items?.indexed.map((entry) {
+ children: element.list!.items?.mapIndexed((i, entry) {
return TextSpan(
children: [
const WidgetSpan(
child: Icon(MdiIcons.circleMedium),
alignment: .middle,
),
- ...entry.$2.nodes!.map((item) {
+ ...entry.nodes!.map((item) {
if (item.word != null) {
return _getSpan(
item.word,
@@ -307,7 +308,7 @@ class OpusContent extends StatelessWidget {
}
return const TextSpan();
}),
- if (entry.$1 < element.list!.items!.length - 1)
+ if (i < element.list!.items!.length - 1)
const TextSpan(text: '\n'),
],
);
diff --git a/lib/pages/audio/controller.dart b/lib/pages/audio/controller.dart
index 8544b4082..148fb2d42 100644
--- a/lib/pages/audio/controller.dart
+++ b/lib/pages/audio/controller.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:PiliPlus/common/constants.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/grpc/audio.dart';
import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pb.dart'
show
@@ -533,62 +534,45 @@ class AudioController extends GetxController
: '${HttpString.baseUrl}/audio/au$oid';
showDialog(
context: context,
- builder: (_) => AlertDialog(
+ builder: (_) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- title: const Text(
- '复制链接',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
+ children: [
+ DialogOption(
+ child: const Text('复制链接', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ Utils.copyText(audioUrl);
+ },
+ ),
+ DialogOption(
+ child: const Text('其它app打开', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ PageUtils.launchURL(audioUrl);
+ },
+ ),
+ if (PlatformUtils.isMobile)
+ DialogOption(
+ child: const Text('分享视频', style: TextStyle(fontSize: 14)),
+ onPressed: () {
Get.back();
- Utils.copyText(audioUrl);
+ if (audioItem.value case DetailItem(
+ :final arc,
+ :final owner,
+ )) {
+ ShareUtils.shareText(
+ '${arc.title} '
+ 'UP主: ${owner.name}'
+ ' - $audioUrl',
+ );
+ }
},
),
- ListTile(
- dense: true,
- title: const Text(
- '其它app打开',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- PageUtils.launchURL(audioUrl);
- },
- ),
- if (PlatformUtils.isMobile)
- ListTile(
- dense: true,
- title: const Text(
- '分享视频',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- if (audioItem.value case DetailItem(
- :final arc,
- :final owner,
- )) {
- ShareUtils.shareText(
- '${arc.title} '
- 'UP主: ${owner.name}'
- ' - $audioUrl',
- );
- }
- },
- ),
- ListTile(
- dense: true,
- title: const Text(
- '分享至动态',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
+ if (isLogin)
+ DialogOption(
+ child: const Text('分享至动态', style: TextStyle(fontSize: 14)),
+ onPressed: () {
Get.back();
if (audioItem.value case DetailItem(
:final arc,
@@ -609,40 +593,35 @@ class AudioController extends GetxController
}
},
),
- if (isUgc)
- ListTile(
- dense: true,
- title: const Text(
- '分享至消息',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- if (audioItem.value case DetailItem(
- :final arc,
- :final owner,
- )) {
- try {
- PageUtils.pmShare(
- context,
- content: {
- "id": oid.toString(),
- "title": arc.title,
- "headline": arc.title,
- "source": 5,
- "thumb": arc.cover,
- "author": owner.name,
- "author_id": owner.mid.toString(),
- },
- );
- } catch (e) {
- SmartDialog.showToast(e.toString());
- }
+ if (isUgc && isLogin)
+ DialogOption(
+ child: const Text('分享至消息', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ if (audioItem.value case DetailItem(
+ :final arc,
+ :final owner,
+ )) {
+ try {
+ PageUtils.pmShare(
+ context,
+ content: {
+ "id": oid.toString(),
+ "title": arc.title,
+ "headline": arc.title,
+ "source": 5,
+ "thumb": arc.cover,
+ "author": owner.name,
+ "author_id": owner.mid.toString(),
+ },
+ );
+ } catch (e) {
+ SmartDialog.showToast(e.toString());
}
- },
- ),
- ],
- ),
+ }
+ },
+ ),
+ ],
),
);
}
diff --git a/lib/pages/bubble/view.dart b/lib/pages/bubble/view.dart
index 159ece5ca..d3940c341 100644
--- a/lib/pages/bubble/view.dart
+++ b/lib/pages/bubble/view.dart
@@ -91,34 +91,31 @@ class _BubblePageState extends State
tooltip: '排序',
onPressed: () => showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: .hardEdge,
contentPadding: const .symmetric(vertical: 12),
- content: Column(
- mainAxisSize: .min,
- children: sortInfo.sortItems!.map(
- (e) {
- final isSelected = item.sortType == e.sortType;
- return ListTile(
- dense: true,
- enabled: !isSelected,
- onTap: () {
- Get.back();
- if (!isSelected) {
- _controller.onSort(e.sortType);
- }
- },
- title: Text(
- e.text!,
- style: const TextStyle(fontSize: 14),
- ),
- trailing: isSelected
- ? const Icon(size: 22, Icons.check)
- : null,
- );
- },
- ).toList(),
- ),
+ children: sortInfo.sortItems!.map(
+ (e) {
+ final isSelected = item.sortType == e.sortType;
+ return ListTile(
+ dense: true,
+ enabled: !isSelected,
+ onTap: () {
+ Get.back();
+ if (!isSelected) {
+ _controller.onSort(e.sortType);
+ }
+ },
+ title: Text(
+ e.text!,
+ style: const TextStyle(fontSize: 14),
+ ),
+ trailing: isSelected
+ ? const Icon(size: 22, Icons.check)
+ : null,
+ );
+ },
+ ).toList(),
),
),
icon: const Icon(Icons.sort, size: 20),
diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart
index 6206c27f0..254d36d8c 100644
--- a/lib/pages/danmaku/controller.dart
+++ b/lib/pages/danmaku/controller.dart
@@ -6,7 +6,6 @@ import 'package:PiliPlus/grpc/dm.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/models/data_source.dart';
-import 'package:PiliPlus/plugin/pl_player/utils/danmaku_options.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/path_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
@@ -69,7 +68,6 @@ class PlDanmakuController {
final uniques = HashMap();
final filters = _plPlayerController.filters;
- final danmakuWeight = DanmakuOptions.danmakuWeight;
final shouldFilter = filters.count != 0;
for (final element in elems) {
if (_isLogin) {
@@ -87,8 +85,7 @@ class PlDanmakuController {
}
}
- if (element.weight < danmakuWeight ||
- (shouldFilter && filters.remove(element))) {
+ if (shouldFilter && filters.remove(element)) {
continue;
}
}
diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart
index 40e57639e..233080bee 100644
--- a/lib/pages/danmaku/view.dart
+++ b/lib/pages/danmaku/view.dart
@@ -114,7 +114,9 @@ class _PlDanmakuState extends State {
.getCurrentDanmaku(currentPosition);
if (currentDanmakuList != null) {
final blockColorful = DanmakuOptions.blockColorful;
+ final danmakuWeight = DanmakuOptions.danmakuWeight;
for (DanmakuElem e in currentDanmakuList) {
+ if (e.weight < danmakuWeight) return;
if (e.mode == 7) {
try {
_controller!.addDanmaku(
diff --git a/lib/pages/download/detail/widgets/item.dart b/lib/pages/download/detail/widgets/item.dart
index ac1c9e813..685a997f0 100644
--- a/lib/pages/download/detail/widgets/item.dart
+++ b/lib/pages/download/detail/widgets/item.dart
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart';
import 'package:PiliPlus/common/widgets/select_mask.dart';
@@ -61,48 +62,37 @@ class DetailItem extends StatelessWidget {
void onLongPress() => canDel && !enableMultiSelect
? showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- onTap: () {
- Get.back();
- showConfirmDialog(
- context: context,
- title: const Text('确定删除该视频?'),
- onConfirm: onDelete,
- );
- },
- dense: true,
- title: const Text(
- '删除',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ListTile(
- onTap: () async {
- Get.back();
- final res = await downloadService.downloadDanmaku(
- entry: entry,
- isUpdate: true,
- );
- if (res) {
- SmartDialog.showToast('更新成功');
- } else {
- SmartDialog.showToast('更新失败');
- }
- },
- dense: true,
- title: const Text(
- '更新弹幕',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ],
- ),
+ children: [
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ showConfirmDialog(
+ context: context,
+ title: const Text('确定删除该视频?'),
+ onConfirm: onDelete,
+ );
+ },
+ child: const Text('删除', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () async {
+ Get.back();
+ final res = await downloadService.downloadDanmaku(
+ entry: entry,
+ isUpdate: true,
+ );
+ if (res) {
+ SmartDialog.showToast('更新成功');
+ } else {
+ SmartDialog.showToast('更新失败');
+ }
+ },
+ child: const Text('更新弹幕', style: TextStyle(fontSize: 14)),
+ ),
+ ],
),
)
: null;
diff --git a/lib/pages/download/view.dart b/lib/pages/download/view.dart
index eba4574a5..150e158bb 100644
--- a/lib/pages/download/view.dart
+++ b/lib/pages/download/view.dart
@@ -4,6 +4,7 @@ import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
@@ -232,59 +233,48 @@ class _DownloadPageState extends State {
? null
: showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- onTap: () {
- Get.back();
- showConfirmDialog(
- context: context,
- title: const Text('确定删除?'),
- onConfirm: () async {
- await GStorage.watchProgress.deleteAll(
- pageInfo.entries.map((e) => e.cid.toString()),
- );
- _downloadService.deletePage(
- pageDirPath: pageInfo.dirPath,
- );
- },
- );
- },
- dense: true,
- title: const Text(
- '删除',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ListTile(
- onTap: () async {
- Get.back();
- final res = await Future.wait(
- pageInfo.entries.map(
- (e) => _downloadService.downloadDanmaku(
- entry: e,
- isUpdate: true,
- ),
+ children: [
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ showConfirmDialog(
+ context: context,
+ title: const Text('确定删除?'),
+ onConfirm: () async {
+ await GStorage.watchProgress.deleteAll(
+ pageInfo.entries.map((e) => e.cid.toString()),
+ );
+ _downloadService.deletePage(
+ pageDirPath: pageInfo.dirPath,
+ );
+ },
+ );
+ },
+ child: const Text('删除', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () async {
+ Get.back();
+ final res = await Future.wait(
+ pageInfo.entries.map(
+ (e) => _downloadService.downloadDanmaku(
+ entry: e,
+ isUpdate: true,
),
- );
- if (res.every((e) => e)) {
- SmartDialog.showToast('更新成功');
- } else {
- SmartDialog.showToast('更新失败');
- }
- },
- dense: true,
- title: const Text(
- '更新弹幕',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ],
- ),
+ ),
+ );
+ if (res.every((e) => e)) {
+ SmartDialog.showToast('更新成功');
+ } else {
+ SmartDialog.showToast('更新失败');
+ }
+ },
+ child: const Text('更新弹幕', style: TextStyle(fontSize: 14)),
+ ),
+ ],
),
);
final first = pageInfo.entries.first;
diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart
index 99ed4664d..008cb5bb8 100644
--- a/lib/pages/dynamics/widgets/author_panel.dart
+++ b/lib/pages/dynamics/widgets/author_panel.dart
@@ -431,41 +431,37 @@ class AuthorPanel extends StatelessWidget {
final reply = response.upReply;
final enableReply = reply.status == 1;
- return AlertDialog(
+ return SimpleDialog(
clipBehavior: .hardEdge,
contentPadding: const .symmetric(vertical: 12),
- content: Column(
- mainAxisSize: .min,
- crossAxisAlignment: .start,
- children: [
- ListTile(
- dense: true,
- enabled: selection.canModify,
- title: Text(
- '${enableSelection ? '停止' : '开启'}评论精选',
- style: const TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- onSetReplySubject!(
- enableSelection ? 2 : 1,
- );
- },
+ children: [
+ ListTile(
+ dense: true,
+ enabled: selection.canModify,
+ title: Text(
+ '${enableSelection ? '停止' : '开启'}评论精选',
+ style: const TextStyle(fontSize: 14),
),
- ListTile(
- dense: true,
- enabled: reply.canModify,
- title: Text(
- '${enableReply ? '关闭' : '恢复'}评论',
- style: const TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- onSetReplySubject!(enableReply ? 3 : 4);
- },
+ onTap: () {
+ Get.back();
+ onSetReplySubject!(
+ enableSelection ? 2 : 1,
+ );
+ },
+ ),
+ ListTile(
+ dense: true,
+ enabled: reply.canModify,
+ title: Text(
+ '${enableReply ? '关闭' : '恢复'}评论',
+ style: const TextStyle(fontSize: 14),
),
- ],
- ),
+ onTap: () {
+ Get.back();
+ onSetReplySubject!(enableReply ? 3 : 4);
+ },
+ ),
+ ],
);
},
);
@@ -504,32 +500,29 @@ class AuthorPanel extends StatelessWidget {
showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const .symmetric(vertical: 12),
- content: Column(
- mainAxisSize: .min,
- children: [
- ListTile(
- dense: true,
- enabled: isPrivate,
- title: const Text(
- '所有用户可见',
- style: TextStyle(fontSize: 14),
- ),
- onTap: onTap,
+ children: [
+ ListTile(
+ dense: true,
+ enabled: isPrivate,
+ title: const Text(
+ '所有用户可见',
+ style: TextStyle(fontSize: 14),
),
- ListTile(
- dense: true,
- enabled: !isPrivate,
- title: const Text(
- '仅自己可见',
- style: TextStyle(fontSize: 14),
- ),
- onTap: onTap,
+ onTap: onTap,
+ ),
+ ListTile(
+ dense: true,
+ enabled: !isPrivate,
+ title: const Text(
+ '仅自己可见',
+ style: TextStyle(fontSize: 14),
),
- ],
- ),
+ onTap: onTap,
+ ),
+ ],
),
);
},
diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart
index 8dc62f3ce..18c4157d2 100644
--- a/lib/pages/dynamics/widgets/video_panel.dart
+++ b/lib/pages/dynamics/widgets/video_panel.dart
@@ -1,11 +1,10 @@
// 视频or合集
-import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
+import 'package:PiliPlus/common/widgets/svg/play_icon.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
-import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/num_utils.dart';
import 'package:flutter/material.dart';
@@ -113,12 +112,7 @@ Widget videoSeasonWidget(
Text('${NumUtils.numFormat(stat.danmu)}弹幕'),
],
const Spacer(),
- Image.asset(
- Assets.play,
- width: 50,
- height: 50,
- cacheHeight: 50.cacheSize(context),
- ),
+ const PlayIcon(size: 50),
],
),
),
diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart
index 4ef0daced..be9574212 100644
--- a/lib/pages/dynamics/widgets/vote.dart
+++ b/lib/pages/dynamics/widgets/vote.dart
@@ -159,57 +159,53 @@ class _VotePanelState extends State {
context: context,
builder: (context) {
final colorScheme = ColorScheme.of(context);
- return AlertDialog(
+ return SimpleDialog(
clipBehavior: .hardEdge,
title: const Text('关注的人的投票'),
- contentPadding: const .only(top: 10, bottom: 12),
- content: SingleChildScrollView(
- child: Column(
- mainAxisSize: .min,
- children: list
- .map(
- (e) => ListTile(
- dense: true,
- onTap: () =>
- Get.toNamed('/member?mid=${e.mid}'),
- leading: NetworkImgLayer(
- src: e.face,
- width: 40,
- height: 40,
- type: .avatar,
- ),
- title: Text.rich(
- style: const TextStyle(fontSize: 13),
+ contentPadding: const .only(bottom: 12),
+ titlePadding: const .fromLTRB(20, 20, 20, 10),
+ children: list
+ .map(
+ (e) => ListTile(
+ dense: true,
+ onTap: () =>
+ Get.toNamed('/member?mid=${e.mid}'),
+ leading: NetworkImgLayer(
+ src: e.face,
+ width: 40,
+ height: 40,
+ type: .avatar,
+ ),
+ title: Text.rich(
+ style: const TextStyle(fontSize: 13),
+ TextSpan(
+ children: [
+ TextSpan(text: e.name),
TextSpan(
- children: [
- TextSpan(text: e.name),
- TextSpan(
- text: ' 投给了',
- style: TextStyle(
- fontSize: 12,
- color: colorScheme.outline,
- ),
- ),
- ],
+ text: ' 投给了',
+ style: TextStyle(
+ fontSize: 12,
+ color: colorScheme.outline,
+ ),
),
- ),
- subtitle: Text(
- style: const TextStyle(fontSize: 13),
- e.votes
- .map(
- (vote) => _voteInfo.options
- .firstWhereOrNull(
- (e) => e.optIdx == vote,
- )
- ?.optDesc,
- )
- .join('、'),
- ),
+ ],
),
- )
- .toList(),
- ),
- ),
+ ),
+ subtitle: Text(
+ style: const TextStyle(fontSize: 13),
+ e.votes
+ .map(
+ (vote) => _voteInfo.options
+ .firstWhereOrNull(
+ (e) => e.optIdx == vote,
+ )
+ ?.optDesc,
+ )
+ .join('、'),
+ ),
+ ),
+ )
+ .toList(),
);
},
);
diff --git a/lib/pages/fav_create/view.dart b/lib/pages/fav_create/view.dart
index 4b9faba17..3ee894682 100644
--- a/lib/pages/fav_create/view.dart
+++ b/lib/pages/fav_create/view.dart
@@ -1,5 +1,6 @@
import 'dart:io' show File;
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/http/fav.dart';
@@ -195,37 +196,32 @@ class _CreateFavPageState extends State {
if (_cover?.isNotEmpty == true) {
showDialog(
context: context,
- builder: (_) => AlertDialog(
+ builder: (_) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const .symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- onTap: () {
- Get.back();
- _pickImg(context, theme);
- },
- title: const Text(
- '替换封面',
- style: TextStyle(fontSize: 14),
- ),
+ children: [
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ _pickImg(context, theme);
+ },
+ child: const Text(
+ '替换封面',
+ style: TextStyle(fontSize: 14),
),
- ListTile(
- dense: true,
- onTap: () {
- Get.back();
- _cover = null;
- (context as Element).markNeedsBuild();
- },
- title: const Text(
- '移除封面',
- style: TextStyle(fontSize: 14),
- ),
+ ),
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ _cover = null;
+ (context as Element).markNeedsBuild();
+ },
+ child: const Text(
+ '移除封面',
+ style: TextStyle(fontSize: 14),
),
- ],
- ),
+ ),
+ ],
),
);
} else {
diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart
index 58119d060..c541a4bf6 100644
--- a/lib/pages/fav_detail/view.dart
+++ b/lib/pages/fav_detail/view.dart
@@ -226,84 +226,85 @@ class _FavDetailPageState extends State with GridMixin {
);
},
),
- PopupMenuButton(
- icon: const Icon(Icons.more_vert),
- itemBuilder: (context) {
- final isOwner = _favDetailController.isOwner;
- final folderInfo = _favDetailController.folderInfo.value;
- return [
- if (isOwner) ...[
- PopupMenuItem(
- onTap: _favDetailController.onSort,
- child: const Text('排序'),
- ),
- PopupMenuItem(
- onTap: () =>
- Get.toNamed(
- '/createFav',
- parameters: {'mediaId': mediaId},
- )?.then((res) {
- if (res is FavFolderInfo) {
- _favDetailController.folderInfo.value = res;
- }
- }),
- child: const Text('编辑信息'),
- ),
- ] else
- PopupMenuItem(
- onTap: () =>
- _favDetailController.onFav(folderInfo.favState == 1),
- child: Text('${folderInfo.favState == 1 ? '取消' : ''}收藏'),
- ),
- if (BiliUtils.isPublicFav(folderInfo.attr))
- PopupMenuItem(
- onTap: () => showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- useSafeArea: true,
- builder: (context) => RepostPanel(
- rid: _favDetailController.mediaId,
- dynType: 4300,
- pic: folderInfo.cover,
- title: folderInfo.title,
- uname: folderInfo.upper?.name,
- ),
- ),
- child: const Text('分享至动态'),
- ),
- if (isOwner) ...[
- PopupMenuItem(
- onTap: _favDetailController.cleanFav,
- child: const Text('清除失效内容'),
- ),
- if (!BiliUtils.isDefaultFav(folderInfo.attr)) ...[
- const PopupMenuDivider(height: 12),
+ if (_favDetailController.account.isLogin)
+ PopupMenuButton(
+ icon: const Icon(Icons.more_vert),
+ itemBuilder: (context) {
+ final isOwner = _favDetailController.isOwner;
+ final folderInfo = _favDetailController.folderInfo.value;
+ return [
+ if (isOwner) ...[
PopupMenuItem(
- onTap: () => showConfirmDialog(
+ onTap: _favDetailController.onSort,
+ child: const Text('排序'),
+ ),
+ PopupMenuItem(
+ onTap: () =>
+ Get.toNamed(
+ '/createFav',
+ parameters: {'mediaId': mediaId},
+ )?.then((res) {
+ if (res is FavFolderInfo) {
+ _favDetailController.folderInfo.value = res;
+ }
+ }),
+ child: const Text('编辑信息'),
+ ),
+ ] else
+ PopupMenuItem(
+ onTap: () =>
+ _favDetailController.onFav(folderInfo.favState == 1),
+ child: Text('${folderInfo.favState == 1 ? '取消' : ''}收藏'),
+ ),
+ if (BiliUtils.isPublicFav(folderInfo.attr))
+ PopupMenuItem(
+ onTap: () => showModalBottomSheet(
context: context,
- title: const Text('确定删除该收藏夹?'),
- onConfirm: () =>
- FavHttp.deleteFolder(mediaIds: mediaId).then((res) {
- if (res.isSuccess) {
- SmartDialog.showToast('删除成功');
- Get.back(result: true);
- } else {
- res.toast();
- }
- }),
- ),
- child: Text(
- '删除',
- style: TextStyle(
- color: theme.colorScheme.error,
+ isScrollControlled: true,
+ useSafeArea: true,
+ builder: (context) => RepostPanel(
+ rid: _favDetailController.mediaId,
+ dynType: 4300,
+ pic: folderInfo.cover,
+ title: folderInfo.title,
+ uname: folderInfo.upper?.name,
),
),
+ child: const Text('分享至动态'),
),
+ if (isOwner) ...[
+ PopupMenuItem(
+ onTap: _favDetailController.cleanFav,
+ child: const Text('清除失效内容'),
+ ),
+ if (!BiliUtils.isDefaultFav(folderInfo.attr)) ...[
+ const PopupMenuDivider(height: 12),
+ PopupMenuItem(
+ onTap: () => showConfirmDialog(
+ context: context,
+ title: const Text('确定删除该收藏夹?'),
+ onConfirm: () =>
+ FavHttp.deleteFolder(mediaIds: mediaId).then((res) {
+ if (res.isSuccess) {
+ SmartDialog.showToast('删除成功');
+ Get.back(result: true);
+ } else {
+ res.toast();
+ }
+ }),
+ ),
+ child: Text(
+ '删除',
+ style: TextStyle(
+ color: theme.colorScheme.error,
+ ),
+ ),
+ ),
+ ],
],
- ],
- ];
- },
- ),
+ ];
+ },
+ ),
const SizedBox(width: 10),
];
}
diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart
index 13688820e..2b0fefa8f 100644
--- a/lib/pages/follow/view.dart
+++ b/lib/pages/follow/view.dart
@@ -1,4 +1,5 @@
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
@@ -194,62 +195,48 @@ class _FollowPageState extends State {
void _onHandleTag(int index, MemberTagItemModel item) {
showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- onTap: () {
- Get.back();
- String tagName = item.name!;
- showConfirmDialog(
- context: context,
- title: const Text('编辑分组名称'),
- content: TextFormField(
- autofocus: true,
- initialValue: tagName,
- onChanged: (value) => tagName = value,
- inputFormatters: [
- LengthLimitingTextInputFormatter(16),
- ],
- decoration: const InputDecoration(
- border: OutlineInputBorder(),
- ),
+ children: [
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ String tagName = item.name!;
+ showConfirmDialog(
+ context: context,
+ title: const Text('编辑分组名称'),
+ content: TextFormField(
+ autofocus: true,
+ initialValue: tagName,
+ onChanged: (value) => tagName = value,
+ inputFormatters: [LengthLimitingTextInputFormatter(16)],
+ decoration: const InputDecoration(
+ border: OutlineInputBorder(),
),
- onConfirm: () {
- if (tagName.isNotEmpty) {
- _followController.onUpdateTag(item, tagName);
- }
- },
- );
- },
- dense: true,
- title: const Text(
- '修改名称',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ListTile(
- onTap: () {
- Get.back();
- showConfirmDialog(
- context: context,
- title: const Text('删除分组'),
- content: const Text('删除后,该分组下的用户依旧保留?'),
- onConfirm: () =>
- _followController.onDelTag(index, item.tagid!),
- );
- },
- dense: true,
- title: const Text(
- '删除分组',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ],
- ),
+ ),
+ onConfirm: () {
+ if (tagName.isNotEmpty) {
+ _followController.onUpdateTag(item, tagName);
+ }
+ },
+ );
+ },
+ child: const Text('修改名称', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ showConfirmDialog(
+ context: context,
+ title: const Text('删除分组'),
+ content: const Text('删除后,该分组下的用户依旧保留?'),
+ onConfirm: () => _followController.onDelTag(index, item.tagid!),
+ );
+ },
+ child: const Text('删除分组', style: TextStyle(fontSize: 14)),
+ ),
+ ],
),
);
}
diff --git a/lib/pages/live_dm_block/view.dart b/lib/pages/live_dm_block/view.dart
index f0dfbbc4b..d8a4d519f 100644
--- a/lib/pages/live_dm_block/view.dart
+++ b/lib/pages/live_dm_block/view.dart
@@ -9,6 +9,7 @@ import 'package:PiliPlus/pages/live_dm_block/controller.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
import 'package:PiliPlus/utils/extension/size_ext.dart';
import 'package:PiliPlus/utils/utils.dart';
+import 'package:collection/collection.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show FilteringTextInputFormatter;
@@ -168,15 +169,14 @@ class _LiveDmBlockPageState extends State {
child: Wrap(
spacing: 12,
runSpacing: 12,
- children: list.indexed.map(
- (e) {
- final item = e.$2;
+ children: list.mapIndexed(
+ (i, e) {
return SearchText(
- text: item is ShieldUserList ? item.uname! : item as String,
+ text: e is ShieldUserList ? e.uname! : e as String,
onTap: (value) => showConfirmDialog(
context: context,
title: const Text('确定删除该规则?'),
- onConfirm: () => _controller.onRemove(e.$1, item),
+ onConfirm: () => _controller.onRemove(i, e),
),
);
},
diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart
index 79dff6092..2cd59efe6 100644
--- a/lib/pages/live_room/controller.dart
+++ b/lib/pages/live_room/controller.dart
@@ -232,18 +232,19 @@ class LiveRoomController extends GetxController {
final pref = Pref.liveStream;
if (pref != null) {
try {
- final protocolName = pref[0];
- final formatName = pref[1];
- final codecName = pref[2];
- for (var i in stream.indexed) {
- if (i.$2.protocolName == protocolName) {
- streamIndex = i.$1;
- for (var j in i.$2.format.indexed) {
- if (j.$2.formatName == formatName) {
- formatIndex = j.$1;
- for (var k in j.$2.codec.indexed) {
- if (k.$2.codecName == codecName) {
- codecIndex = k.$1;
+ final String protocolName = pref[0];
+ final String formatName = pref[1];
+ final String codecName = pref[2];
+ for (var (i, s) in stream.indexed) {
+ if (s.protocolName == protocolName) {
+ streamIndex = i;
+ for (var (j, f) in s.format.indexed) {
+ if (f.formatName == formatName) {
+ formatIndex = j;
+ for (var (k, c) in f.codec.indexed) {
+ if (c.codecName == codecName) {
+ codecIndex = k;
+ return;
}
}
}
diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart
index e19817126..911758fc1 100644
--- a/lib/pages/live_room/widgets/header_control.dart
+++ b/lib/pages/live_room/widgets/header_control.dart
@@ -20,6 +20,7 @@ import 'package:PiliPlus/utils/extension/string_ext.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
+import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
@@ -357,9 +358,8 @@ class _LiveHeaderControlState extends State
padding: .only(
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
),
- children: controller.stream.indexed.map((stream) {
- final isCurrStream =
- stream.$1 == controller.streamIndex;
+ children: controller.stream.mapIndexed((si, stream) {
+ final isCurrStream = si == controller.streamIndex;
final streamColor = isCurrStream
? secondary
: onSurfaceVariant;
@@ -368,15 +368,14 @@ class _LiveHeaderControlState extends State
iconColor: streamColor,
collapsedIconColor: streamColor,
title: Text(
- stream.$2.protocolName ?? stream.$1.toString(),
+ stream.protocolName ?? si.toString(),
style: isCurrStream
? currStyle
: const TextStyle(fontSize: 14),
),
- children: stream.$2.format.indexed.map((format) {
+ children: stream.format.mapIndexed((fi, format) {
final isCurrFormat =
- isCurrStream &&
- format.$1 == controller.formatIndex;
+ isCurrStream && fi == controller.formatIndex;
final formatColor = isCurrFormat
? secondary
: onSurfaceVariant;
@@ -385,16 +384,14 @@ class _LiveHeaderControlState extends State
iconColor: formatColor,
collapsedIconColor: formatColor,
title: Text(
- format.$2.formatName ?? format.$1.toString(),
+ format.formatName ?? fi.toString(),
style: isCurrFormat
? currStyle
: const TextStyle(fontSize: 14),
),
- children: format.$2.codec.indexed.map((codec) {
- final e = codec.$2;
+ children: format.codec.mapIndexed((ci, codec) {
final isCurrCodec =
- isCurrFormat &&
- codec.$1 == controller.codecIndex;
+ isCurrFormat && ci == controller.codecIndex;
final codecColor = isCurrCodec
? secondary
: onSurfaceVariant;
@@ -403,19 +400,19 @@ class _LiveHeaderControlState extends State
iconColor: codecColor,
collapsedIconColor: codecColor,
title: Text(
- '${e.codecName ?? codec.$1.toString()} (${LiveQuality.fromCode(e.currentQn)?.desc ?? e.currentQn})',
+ '${codec.codecName ?? ci.toString()} (${LiveQuality.fromCode(codec.currentQn)?.desc ?? codec.currentQn})',
style: isCurrCodec
? currStyle
: const TextStyle(fontSize: 14),
),
- children: e.urlInfo.indexed.map((url) {
+ children: codec.urlInfo.mapIndexed((ui, url) {
final isCurrUrl =
- (isCurrCodec &&
- url.$1 == controller.liveUrlIndex);
+ isCurrCodec &&
+ ui == controller.liveUrlIndex;
return ListTile(
dense: true,
title: Text(
- '${url.$2.host}${e.baseUrl}...',
+ '${url.host}...',
style: isCurrUrl
? const TextStyle(fontSize: 14)
: TextStyle(
@@ -429,17 +426,17 @@ class _LiveHeaderControlState extends State
: () {
Get.back();
controller.initLiveUrl(
- streamIndex: stream.$1,
- formatIndex: format.$1,
- codecIndex: codec.$1,
- liveUrlIndex: url.$1,
+ streamIndex: si,
+ formatIndex: fi,
+ codecIndex: ci,
+ liveUrlIndex: ui,
);
GStorage.setting.put(
SettingBoxKey.liveStream,
[
- stream.$2.protocolName,
- format.$2.formatName,
- codec.$2.codecName,
+ stream.protocolName!,
+ format.formatName!,
+ codec.codecName!,
],
);
},
diff --git a/lib/pages/member/widget/user_info_card.dart b/lib/pages/member/widget/user_info_card.dart
index 4e064ce27..9a887b204 100644
--- a/lib/pages/member/widget/user_info_card.dart
+++ b/lib/pages/member/widget/user_info_card.dart
@@ -217,14 +217,10 @@ class UserInfoCard extends StatelessWidget {
),
),
),
- Image.asset(
- BiliUtils.levelName(
- card.levelInfo!.currentLevel!,
- isSeniorMember: card.levelInfo?.identity == 2,
- ),
+ BiliUtils.levelPicture(
+ card.levelInfo!.currentLevel!,
+ isSeniorMember: card.levelInfo?.identity == 2,
height: 11,
- cacheHeight: 11.cacheSize(context),
- semanticLabel: '等级${card.levelInfo?.currentLevel}',
),
if (card.vip?.status == 1)
Container(
diff --git a/lib/pages/member_opus/view.dart b/lib/pages/member_opus/view.dart
index 22e4152b5..b4e0b1c77 100644
--- a/lib/pages/member_opus/view.dart
+++ b/lib/pages/member_opus/view.dart
@@ -98,37 +98,32 @@ class _MemberOpusState extends State
child: FloatingActionButton.extended(
onPressed: () => showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: _controller.filter!
- .map(
- (e) => ListTile(
- onTap: () {
- if (e == _controller.type.value) {
- return;
- }
- Get.back();
- _controller
- ..type.value = e
- ..onReload();
- },
- tileColor: e == _controller.type.value
- ? Theme.of(
- context,
- ).colorScheme.onInverseSurface
- : null,
- dense: true,
- title: Text(
- e.text ?? e.tabName!,
- style: const TextStyle(fontSize: 14),
- ),
+ children: _controller.filter!
+ .map(
+ (e) => ListTile(
+ dense: true,
+ onTap: () {
+ if (e == _controller.type.value) {
+ return;
+ }
+ Get.back();
+ _controller
+ ..type.value = e
+ ..onReload();
+ },
+ tileColor: e == _controller.type.value
+ ? ColorScheme.of(context).onInverseSurface
+ : null,
+ title: Text(
+ e.text ?? e.tabName!,
+ style: const TextStyle(fontSize: 14),
),
- )
- .toList(),
- ),
+ ),
+ )
+ .toList(),
),
),
icon: const Icon(size: 20, Icons.sort),
diff --git a/lib/pages/member_profile/view.dart b/lib/pages/member_profile/view.dart
index 0fe5f38e7..debf999a1 100644
--- a/lib/pages/member_profile/view.dart
+++ b/lib/pages/member_profile/view.dart
@@ -247,17 +247,14 @@ class _EditProfilePageState extends State {
}
Widget _sexDialog(int current) {
- return AlertDialog(
+ return SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- _sexDialogItem(1, current, '男'),
- _sexDialogItem(0, current, '保密'),
- _sexDialogItem(2, current, '女'),
- ],
- ),
+ children: [
+ _sexDialogItem(1, current, '男'),
+ _sexDialogItem(0, current, '保密'),
+ _sexDialogItem(2, current, '女'),
+ ],
);
}
diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart
index 4a86bceed..8f5c92b94 100644
--- a/lib/pages/mine/view.dart
+++ b/lib/pages/mine/view.dart
@@ -22,6 +22,7 @@ import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide ListTile;
+import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
@@ -274,11 +275,10 @@ class _MediaPageState extends CommonPageState
Positioned(
right: -1,
bottom: -2,
- child: Image.asset(
+ child: SvgPicture.asset(
Assets.vipIcon,
height: 19,
- cacheHeight: 19.cacheSize(context),
- semanticLabel: "大会员",
+ semanticsLabel: "大会员",
),
),
],
@@ -315,13 +315,10 @@ class _MediaPageState extends CommonPageState
overflow: .ellipsis,
),
),
- Image.asset(
- BiliUtils.levelName(
- levelInfo?.currentLevel ?? 0,
- isSeniorMember: userInfo.isSeniorMember == 1,
- ),
+ BiliUtils.levelPicture(
+ levelInfo?.currentLevel ?? 0,
+ isSeniorMember: userInfo.isSeniorMember == 1,
height: 10,
- cacheHeight: 10.cacheSize(context),
),
],
),
diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart
index c5164002c..92ec062f4 100644
--- a/lib/pages/msg_feed_top/like_me/view.dart
+++ b/lib/pages/msg_feed_top/like_me/view.dart
@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/skeleton/msg_feed_top.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
@@ -97,13 +98,9 @@ class _LikeMePageState extends State {
if (total.isEmpty && index == latest.length - 1) {
_likeMeController.onLoadMore();
}
- return _buildItem(
- theme,
- latest[index],
- (id) {
- _likeMeController.onRemove(id, index, true);
- },
- );
+ return _buildItem(theme, latest[index], (id) {
+ _likeMeController.onRemove(id, index, true);
+ });
},
itemCount: latest.length,
separatorBuilder: (context, index) => divider,
@@ -116,13 +113,9 @@ class _LikeMePageState extends State {
if (index == total.length - 1) {
_likeMeController.onLoadMore();
}
- return _buildItem(
- theme,
- total[index],
- (id) {
- _likeMeController.onRemove(id, index, false);
- },
- );
+ return _buildItem(theme, total[index], (id) {
+ _likeMeController.onRemove(id, index, false);
+ });
},
itemCount: total.length,
separatorBuilder: (context, index) => divider,
@@ -200,51 +193,43 @@ class _LikeMePageState extends State {
context: context,
builder: (context) {
final isNotice = item.noticeState == 0;
- return AlertDialog(
+ return SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- onTap: () {
- Get.back();
+ children: [
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ showConfirmDialog(
+ context: context,
+ title: const Text('删除'),
+ content: const Text('该条通知删除后,当有新点赞时会重新出现在列表,是否继续?'),
+ onConfirm: () => onRemove(item.id),
+ );
+ },
+ child: const Text('删除', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ if (isNotice) {
showConfirmDialog(
context: context,
- title: const Text('删除'),
- content: const Text('该条通知删除后,当有新点赞时会重新出现在列表,是否继续?'),
- onConfirm: () => onRemove(item.id),
+ title: const Text('不再通知'),
+ content: const Text('这条内容的点赞将不再通知,但仍可在列表内查看,是否继续?'),
+ onConfirm: () =>
+ _likeMeController.onSetNotice(item, isNotice),
);
- },
- dense: true,
- title: const Text(
- '删除',
- style: TextStyle(fontSize: 14),
- ),
+ } else {
+ _likeMeController.onSetNotice(item, isNotice);
+ }
+ },
+ child: Text(
+ isNotice ? '不再通知' : '接收通知',
+ style: const TextStyle(fontSize: 14),
),
- ListTile(
- onTap: () {
- Get.back();
- if (isNotice) {
- showConfirmDialog(
- context: context,
- title: const Text('不再通知'),
- content: const Text('这条内容的点赞将不再通知,但仍可在列表内查看,是否继续?'),
- onConfirm: () =>
- _likeMeController.onSetNotice(item, isNotice),
- );
- } else {
- _likeMeController.onSetNotice(item, isNotice);
- }
- },
- dense: true,
- title: Text(
- isNotice ? '不再通知' : '接收通知',
- style: const TextStyle(fontSize: 14),
- ),
- ),
- ],
- ),
+ ),
+ ],
);
},
);
@@ -336,9 +321,7 @@ class _LikeMePageState extends State {
width: 45,
height: 45,
src: item.item!.image,
- borderRadius: const BorderRadius.all(
- Radius.circular(8),
- ),
+ borderRadius: const BorderRadius.all(Radius.circular(8)),
),
if (item.noticeState == 1) ...[
if (item.item?.image?.isNotEmpty == true) const SizedBox(width: 4),
diff --git a/lib/pages/pgc_review/child/view.dart b/lib/pages/pgc_review/child/view.dart
index a227d299b..70d5f7173 100644
--- a/lib/pages/pgc_review/child/view.dart
+++ b/lib/pages/pgc_review/child/view.dart
@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/skeleton/video_reply.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/custom_icon.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
@@ -14,7 +15,6 @@ import 'package:PiliPlus/pages/pgc_review/child/controller.dart';
import 'package:PiliPlus/pages/pgc_review/post/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/bili_utils.dart';
-import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/num_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
@@ -121,71 +121,56 @@ class _PgcReviewChildPageState extends State
Widget _itemWidget(ThemeData theme, int index, PgcReviewItemModel item) {
void showMore() => showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (item.author!.mid == Accounts.main.mid) ...[
- ListTile(
- dense: true,
- title: const Text(
- '编辑',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- showModalBottomSheet(
- context: context,
- useSafeArea: true,
- isScrollControlled: true,
- builder: (context) {
- return PgcReviewPostPanel(
- name: widget.name,
- mediaId: widget.mediaId,
- reviewId: item.reviewId,
- content: item.content,
- score: item.score,
- );
- },
- );
- },
- ),
- ListTile(
- dense: true,
- title: const Text(
- '删除',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- showConfirmDialog(
- context: context,
- title: const Text('删除短评,同时删除评分?'),
- onConfirm: () => _controller.onDel(index, item.reviewId!),
- );
- },
- ),
- ],
- ListTile(
- dense: true,
- title: const Text(
- '举报',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () => Get
- ..back()
- ..toNamed(
- '/webview',
- parameters: {
- 'url':
- 'https://www.bilibili.com/appeal/?reviewId=${item.reviewId}&type=shortComment&mediaId=${widget.mediaId}',
+ children: [
+ if (item.author!.mid == Accounts.main.mid) ...[
+ DialogOption(
+ child: const Text('编辑', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ showModalBottomSheet(
+ context: context,
+ useSafeArea: true,
+ isScrollControlled: true,
+ builder: (context) {
+ return PgcReviewPostPanel(
+ name: widget.name,
+ mediaId: widget.mediaId,
+ reviewId: item.reviewId,
+ content: item.content,
+ score: item.score,
+ );
},
- ),
+ );
+ },
+ ),
+ DialogOption(
+ child: const Text('删除', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ showConfirmDialog(
+ context: context,
+ title: const Text('删除短评,同时删除评分?'),
+ onConfirm: () => _controller.onDel(index, item.reviewId!),
+ );
+ },
),
],
- ),
+ DialogOption(
+ child: const Text('举报', style: TextStyle(fontSize: 14)),
+ onPressed: () => Get
+ ..back()
+ ..toNamed(
+ '/webview',
+ parameters: {
+ 'url':
+ 'https://www.bilibili.com/appeal/?reviewId=${item.reviewId}&type=shortComment&mediaId=${widget.mediaId}',
+ },
+ ),
+ ),
+ ],
),
);
@@ -244,10 +229,9 @@ class _PgcReviewChildPageState extends State
fontSize: 13,
),
),
- Image.asset(
- BiliUtils.levelName(item.author!.level!),
+ BiliUtils.levelPicture(
+ item.author!.level!,
height: 11,
- cacheHeight: 11.cacheSize(context),
),
],
),
diff --git a/lib/pages/pgc_review/view.dart b/lib/pages/pgc_review/view.dart
index 3d23873d2..62fd58ef4 100644
--- a/lib/pages/pgc_review/view.dart
+++ b/lib/pages/pgc_review/view.dart
@@ -1,3 +1,4 @@
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/models/common/pgc_review_type.dart';
import 'package:PiliPlus/pages/pgc_review/child/controller.dart';
import 'package:PiliPlus/pages/pgc_review/child/view.dart';
@@ -115,51 +116,40 @@ class _PgcReviewPageState extends State
child: FloatingActionButton(
onPressed: () => showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- title: const Text(
- '写短评',
- style: TextStyle(fontSize: 14),
+ children: [
+ DialogOption(
+ child: const Text('写短评', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ showModalBottomSheet(
+ context: context,
+ useSafeArea: true,
+ isScrollControlled: true,
+ builder: (context) {
+ return PgcReviewPostPanel(
+ name: widget.name,
+ mediaId: widget.mediaId,
+ );
+ },
+ );
+ },
+ ),
+ DialogOption(
+ child: const Text('写长评', style: TextStyle(fontSize: 14)),
+ onPressed: () => Get
+ ..back()
+ ..toNamed(
+ '/webview',
+ parameters: {
+ 'url':
+ 'https://member.bilibili.com/article-text/mobile?theme=${theme.isDark ? 1 : 0}&media_id=${widget.mediaId}',
+ },
),
- onTap: () {
- Get.back();
- showModalBottomSheet(
- context: context,
- useSafeArea: true,
- isScrollControlled: true,
- builder: (context) {
- return PgcReviewPostPanel(
- name: widget.name,
- mediaId: widget.mediaId,
- );
- },
- );
- },
- ),
- ListTile(
- dense: true,
- title: const Text(
- '写长评',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () => Get
- ..back()
- ..toNamed(
- '/webview',
- parameters: {
- 'url':
- 'https://member.bilibili.com/article-text/mobile?theme=${theme.isDark ? 1 : 0}&media_id=${widget.mediaId}',
- },
- ),
- ),
- ],
- ),
+ ),
+ ],
),
),
child: const Icon(Icons.edit),
diff --git a/lib/pages/search_panel/user/widgets/item.dart b/lib/pages/search_panel/user/widgets/item.dart
index fdb45feeb..4fc1d7ac9 100644
--- a/lib/pages/search_panel/user/widgets/item.dart
+++ b/lib/pages/search_panel/user/widgets/item.dart
@@ -1,7 +1,6 @@
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/utils/bili_utils.dart';
-import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/num_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_main.dart';
@@ -52,14 +51,10 @@ class SearchUserItem extends StatelessWidget {
),
),
const SizedBox(width: 6),
- Image.asset(
- BiliUtils.levelName(
- item.level!,
- isSeniorMember: item.isSeniorMember == 1,
- ),
+ BiliUtils.levelPicture(
+ item.level!,
+ isSeniorMember: item.isSeniorMember == 1,
height: 11,
- cacheHeight: 11.cacheSize(context),
- semanticLabel: '等级${item.level}',
),
],
),
diff --git a/lib/pages/setting/common_setting.dart b/lib/pages/setting/common_setting.dart
new file mode 100644
index 000000000..abb605284
--- /dev/null
+++ b/lib/pages/setting/common_setting.dart
@@ -0,0 +1,65 @@
+import 'package:PiliPlus/models/common/setting_type.dart';
+import 'package:PiliPlus/pages/setting/models/model.dart';
+import 'package:flutter/material.dart';
+
+class CommonSetting extends StatefulWidget {
+ const CommonSetting({
+ super.key,
+ required this.settingType,
+ this.showAppBar = true,
+ });
+
+ final bool showAppBar;
+ final SettingType settingType;
+
+ @override
+ State createState() => _CommonSettingState();
+}
+
+class _CommonSettingState extends State {
+ late EdgeInsets padding;
+ late List settings;
+
+ void _initSetting() {
+ settings = widget.settingType.settings;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ _initSetting();
+ }
+
+ @override
+ void didUpdateWidget(CommonSetting oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ if (widget.settingType != oldWidget.settingType) {
+ _initSetting();
+ }
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ padding = MediaQuery.viewPaddingOf(context);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final showAppBar = widget.showAppBar;
+ return Scaffold(
+ resizeToAvoidBottomInset: false,
+ appBar: showAppBar ? AppBar(title: Text(widget.settingType.title)) : null,
+ body: ListView.builder(
+ key: ValueKey(widget.settingType),
+ padding: EdgeInsets.only(
+ left: showAppBar ? padding.left : 0,
+ right: showAppBar ? padding.right : 0,
+ bottom: padding.bottom + 100,
+ ),
+ itemCount: settings.length,
+ itemBuilder: (context, index) => settings[index].widget,
+ ),
+ );
+ }
+}
diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart
deleted file mode 100644
index 0c6343e0c..000000000
--- a/lib/pages/setting/extra_setting.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:PiliPlus/pages/setting/models/extra_settings.dart';
-import 'package:flutter/material.dart';
-
-class ExtraSetting extends StatefulWidget {
- const ExtraSetting({super.key, this.showAppBar = true});
-
- final bool showAppBar;
-
- @override
- State createState() => _ExtraSettingState();
-}
-
-class _ExtraSettingState extends State {
- final settings = extraSettings;
-
- @override
- Widget build(BuildContext context) {
- final showAppBar = widget.showAppBar;
- final padding = MediaQuery.viewPaddingOf(context);
- return Scaffold(
- resizeToAvoidBottomInset: false,
- appBar: showAppBar ? AppBar(title: const Text('其它设置')) : null,
- body: ListView.builder(
- padding: EdgeInsets.only(
- left: showAppBar ? padding.left : 0,
- right: showAppBar ? padding.right : 0,
- bottom: padding.bottom + 100,
- ),
- itemCount: settings.length,
- itemBuilder: (context, index) => settings[index].widget,
- ),
- );
- }
-}
diff --git a/lib/pages/setting/models/extra_settings.dart b/lib/pages/setting/models/extra_settings.dart
index 7bc7d54a5..809c8bb59 100644
--- a/lib/pages/setting/models/extra_settings.dart
+++ b/lib/pages/setting/models/extra_settings.dart
@@ -2,6 +2,7 @@ import 'dart:io';
import 'dart:math' show max;
import 'package:PiliPlus/common/widgets/custom_icon.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'
show deviceTouchSlop, touchSlopH;
@@ -595,19 +596,10 @@ List get extraSettings => [
onTap: _showProxyDialog,
),
),
- const SwitchModel(
- title: '自动清除缓存',
- subtitle: '每次启动时清除缓存',
- leading: Icon(Icons.auto_delete_outlined),
- setKey: SettingBoxKey.autoClearCache,
- defaultVal: false,
- ),
NormalModel(
title: '最大缓存大小',
- getSubtitle: () {
- final num = Pref.maxCacheSize;
- return '当前最大缓存大小: 「${num == 0 ? '无限' : CacheManager.formatSize(Pref.maxCacheSize)}」';
- },
+ getSubtitle: () =>
+ '当前最大缓存大小: 「${CacheManager.formatSize(Pref.maxCacheSize)}」',
leading: const Icon(Icons.delete_outlined),
onTap: _showCacheDialog,
),
@@ -721,48 +713,42 @@ Future audioNormalization(
void _showDownPathDialog(BuildContext context, VoidCallback setState) {
showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- onTap: () {
- Get.back();
- Utils.copyText(downloadPath);
- },
- dense: true,
- title: const Text('复制', style: TextStyle(fontSize: 14)),
- ),
- ListTile(
- onTap: () {
- Get.back();
- final defPath = defDownloadPath;
- if (downloadPath == defPath) return;
- downloadPath = defPath;
- setState();
- Get.find().initDownloadList();
- GStorage.setting.delete(SettingBoxKey.downloadPath);
- },
- dense: true,
- title: const Text('重置', style: TextStyle(fontSize: 14)),
- ),
- ListTile(
- onTap: () async {
- Get.back();
- final path = await FilePicker.getDirectoryPath();
- if (path == null || path == downloadPath) return;
- downloadPath = path;
- setState();
- Get.find().initDownloadList();
- GStorage.setting.put(SettingBoxKey.downloadPath, path);
- },
- dense: true,
- title: const Text('设置新路径', style: TextStyle(fontSize: 14)),
- ),
- ],
- ),
+ children: [
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ Utils.copyText(downloadPath);
+ },
+ child: const Text('复制', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ final defPath = defDownloadPath;
+ if (downloadPath == defPath) return;
+ downloadPath = defPath;
+ setState();
+ Get.find().initDownloadList();
+ GStorage.setting.delete(SettingBoxKey.downloadPath);
+ },
+ child: const Text('重置', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () async {
+ Get.back();
+ final path = await FilePicker.getDirectoryPath();
+ if (path == null || path == downloadPath) return;
+ downloadPath = path;
+ setState();
+ Get.find().initDownloadList();
+ GStorage.setting.put(SettingBoxKey.downloadPath, path);
+ },
+ child: const Text('设置新路径', style: TextStyle(fontSize: 14)),
+ ),
+ ],
),
);
}
diff --git a/lib/pages/setting/models/privacy_settings.dart b/lib/pages/setting/models/privacy_settings.dart
index 1a241d7ff..3734b72bf 100644
--- a/lib/pages/setting/models/privacy_settings.dart
+++ b/lib/pages/setting/models/privacy_settings.dart
@@ -25,9 +25,7 @@ List get privacySettings => [
context: context,
builder: (context) => AlertDialog(
title: const Text('账号模式详情'),
- content: SingleChildScrollView(
- child: _getAccountDetail(context),
- ),
+ content: SingleChildScrollView(child: _getAccountDetail(context)),
actions: [
TextButton(
onPressed: Get.back,
diff --git a/lib/pages/setting/models/recommend_settings.dart b/lib/pages/setting/models/recommend_settings.dart
index 256d27468..84f4b584e 100644
--- a/lib/pages/setting/models/recommend_settings.dart
+++ b/lib/pages/setting/models/recommend_settings.dart
@@ -93,8 +93,8 @@ List get recommendSettings => [
onChanged: (value) => RecommendFilter.exemptFilterForFollowed = value,
),
SwitchModel(
- title: '过滤器也应用于相关视频',
- subtitle: '视频详情页的相关视频也进行过滤¹',
+ title: '过滤器也应用于详情页相关视频',
+ subtitle: '其它(如热门视频、搜索等)均不受过滤器影响,无法豁免相关视频中的已关注UP',
leading: const Icon(Icons.explore_outlined),
setKey: SettingBoxKey.applyFilterToRelatedVideos,
defaultVal: true,
diff --git a/lib/pages/setting/models/video_settings.dart b/lib/pages/setting/models/video_settings.dart
index 886e9d2c8..2136518ea 100644
--- a/lib/pages/setting/models/video_settings.dart
+++ b/lib/pages/setting/models/video_settings.dart
@@ -127,16 +127,11 @@ List get videoSettings => [
NormalModel(
title: '首选解码格式',
leading: const Icon(Icons.movie_creation_outlined),
- getSubtitle: () =>
- '首选解码格式:${VideoDecodeFormatType.fromCode(Pref.defaultDecode).description},请根据设备支持情况与需求调整',
- onTap: _showDecodeDialog,
- ),
- NormalModel(
- title: '次选解码格式',
- getSubtitle: () =>
- '非杜比视频次选:${VideoDecodeFormatType.fromCode(Pref.secondDecode).description},仍无则选择首个提供的解码格式',
- leading: const Icon(Icons.swap_horizontal_circle_outlined),
- onTap: _showSecondDecodeDialog,
+ getSubtitle: () {
+ final list = Pref.preferCodecs;
+ return '首选解码格式:${(list.isEmpty ? '第一个可用' : list.map((i) => i.name).join(","))},请根据设备支持情况与需求调整';
+ },
+ onTap: _showCodecsDialog,
),
if (kDebugMode || Platform.isAndroid)
NormalModel(
@@ -349,42 +344,25 @@ Future _showLiveCellularQaDialog(
}
}
-Future _showDecodeDialog(
+Future _showCodecsDialog(
BuildContext context,
VoidCallback setState,
) async {
- final res = await showDialog(
+ final res = await showDialog>(
context: context,
- builder: (context) => SelectDialog(
- title: '默认解码格式',
- value: Pref.defaultDecode,
- values: VideoDecodeFormatType.values
- .map((e) => (e.codes.first, e.description))
- .toList(),
+ builder: (context) => OrderedMultiSelectDialog(
+ title: '首选解码格式',
+ initValues: Pref.preferCodecs,
+ values: {for (final e in VideoDecodeFormatType.values) e: e.name},
),
);
if (res != null) {
- await GStorage.setting.put(SettingBoxKey.defaultDecode, res);
- setState();
- }
-}
-
-Future _showSecondDecodeDialog(
- BuildContext context,
- VoidCallback setState,
-) async {
- final res = await showDialog(
- context: context,
- builder: (context) => SelectDialog(
- title: '次选解码格式',
- value: Pref.secondDecode,
- values: VideoDecodeFormatType.values
- .map((e) => (e.codes.first, e.description))
- .toList(),
- ),
- );
- if (res != null) {
- await GStorage.setting.put(SettingBoxKey.secondDecode, res);
+ await (res.isEmpty
+ ? GStorage.setting.delete(SettingBoxKey.preferCodecs)
+ : GStorage.setting.put(
+ SettingBoxKey.preferCodecs,
+ res.map((i) => i.name).toList(),
+ ));
setState();
}
}
diff --git a/lib/pages/setting/pages/bar_set.dart b/lib/pages/setting/pages/bar_set.dart
index 7c2635ab2..422fdfb4f 100644
--- a/lib/pages/setting/pages/bar_set.dart
+++ b/lib/pages/setting/pages/bar_set.dart
@@ -17,6 +17,7 @@ class _BarSetPageState extends State with ReorderMixin {
late final String key;
late final String title;
late final List> list;
+ late EdgeInsets padding;
@override
void initState() {
@@ -29,7 +30,7 @@ class _BarSetPageState extends State with ReorderMixin {
.map((e) => Pair(first: e, second: cache?.contains(e.index) ?? true))
.toList();
if (cache != null && cache.isNotEmpty) {
- final cacheIndex = {for (final (k, v) in cache.indexed) v: k};
+ final cacheIndex = {for (int i = 0; i < cache.length; i++) cache[i]: i};
list.sort((a, b) {
final indexA = cacheIndex[a.first.index] ?? cacheIndex.length;
final indexB = cacheIndex[b.first.index] ?? cacheIndex.length;
@@ -38,6 +39,13 @@ class _BarSetPageState extends State with ReorderMixin {
}
}
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ final viewPad = MediaQuery.viewPaddingOf(context);
+ padding = .only(top: 10, right: viewPad.right + 34, bottom: viewPad.bottom);
+ }
+
void saveEdit() {
GStorage.setting.put(
key,
@@ -73,9 +81,7 @@ class _BarSetPageState extends State with ReorderMixin {
onReorderItem: onReorderItem,
proxyDecorator: proxyDecorator,
footer: Padding(
- padding:
- MediaQuery.viewPaddingOf(context).copyWith(top: 0, left: 0) +
- const EdgeInsets.only(right: 34, top: 10),
+ padding: padding,
child: const Align(
alignment: Alignment.centerRight,
child: Text('*长按拖动排序'),
diff --git a/lib/pages/setting/pages/color_select.dart b/lib/pages/setting/pages/color_select.dart
index 6aa14fe3b..b80766ea9 100644
--- a/lib/pages/setting/pages/color_select.dart
+++ b/lib/pages/setting/pages/color_select.dart
@@ -15,6 +15,7 @@ import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
+import 'package:collection/collection.dart';
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -148,10 +149,8 @@ class _ColorSelectPageState extends State {
alignment: WrapAlignment.center,
spacing: 22,
runSpacing: 18,
- children: colorThemeTypes.indexed.map(
- (e) {
- final index = e.$1;
- final item = e.$2;
+ children: colorThemeTypes.mapIndexed(
+ (index, item) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart
deleted file mode 100644
index 6c97855a5..000000000
--- a/lib/pages/setting/play_setting.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:PiliPlus/pages/setting/models/play_settings.dart';
-import 'package:flutter/material.dart';
-
-class PlaySetting extends StatefulWidget {
- const PlaySetting({super.key, this.showAppBar = true});
-
- final bool showAppBar;
-
- @override
- State createState() => _PlaySettingState();
-}
-
-class _PlaySettingState extends State {
- final settings = playSettings;
-
- @override
- Widget build(BuildContext context) {
- final showAppBar = widget.showAppBar;
- final padding = MediaQuery.viewPaddingOf(context);
- return Scaffold(
- resizeToAvoidBottomInset: false,
- appBar: showAppBar ? AppBar(title: const Text('播放器设置')) : null,
- body: ListView.builder(
- padding: EdgeInsets.only(
- left: showAppBar ? padding.left : 0,
- right: showAppBar ? padding.right : 0,
- bottom: padding.bottom + 100,
- ),
- itemCount: settings.length,
- itemBuilder: (context, index) => settings[index].widget,
- ),
- );
- }
-}
diff --git a/lib/pages/setting/privacy_setting.dart b/lib/pages/setting/privacy_setting.dart
deleted file mode 100644
index f2ff30bb3..000000000
--- a/lib/pages/setting/privacy_setting.dart
+++ /dev/null
@@ -1,33 +0,0 @@
-import 'package:PiliPlus/pages/setting/models/privacy_settings.dart';
-import 'package:flutter/material.dart';
-
-class PrivacySetting extends StatefulWidget {
- const PrivacySetting({super.key, this.showAppBar = true});
-
- final bool showAppBar;
-
- @override
- State createState() => _PrivacySettingState();
-}
-
-class _PrivacySettingState extends State {
- final settings = privacySettings;
-
- @override
- Widget build(BuildContext context) {
- final showAppBar = widget.showAppBar;
- final padding = MediaQuery.viewPaddingOf(context);
- return Scaffold(
- resizeToAvoidBottomInset: false,
- appBar: showAppBar ? AppBar(title: const Text('隐私设置')) : null,
- body: ListView(
- padding: EdgeInsets.only(
- left: showAppBar ? padding.left : 0,
- right: showAppBar ? padding.right : 0,
- bottom: padding.bottom + 100,
- ),
- children: settings.map((item) => item.widget).toList(),
- ),
- );
- }
-}
diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart
deleted file mode 100644
index d77315fe3..000000000
--- a/lib/pages/setting/recommend_setting.dart
+++ /dev/null
@@ -1,51 +0,0 @@
-import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
-import 'package:PiliPlus/pages/setting/models/recommend_settings.dart';
-import 'package:flutter/material.dart' hide ListTile;
-
-class RecommendSetting extends StatefulWidget {
- const RecommendSetting({super.key, this.showAppBar = true});
-
- final bool showAppBar;
-
- @override
- State createState() => _RecommendSettingState();
-}
-
-class _RecommendSettingState extends State {
- final list = recommendSettings;
-
- @override
- Widget build(BuildContext context) {
- final showAppBar = widget.showAppBar;
- final padding = MediaQuery.viewPaddingOf(context);
- final theme = Theme.of(context);
- return Scaffold(
- resizeToAvoidBottomInset: false,
- appBar: widget.showAppBar ? AppBar(title: const Text('推荐流设置')) : null,
- body: ListView(
- padding: EdgeInsets.only(
- left: showAppBar ? padding.left : 0,
- right: showAppBar ? padding.right : 0,
- bottom: padding.bottom + 100,
- ),
- children: [
- ...list.take(4).map((item) => item.widget),
- const Divider(height: 1),
- ...list.skip(4).map((item) => item.widget),
- ListTile(
- dense: true,
- subtitle: Text(
- '¹ 由于接口未提供关注信息,无法豁免相关视频中的已关注Up。\n\n'
- '* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n'
- '* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n'
- '* 后续可能会增加更多过滤条件,敬请期待。',
- style: theme.textTheme.labelSmall!.copyWith(
- color: theme.colorScheme.outline.withValues(alpha: 0.7),
- ),
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart
deleted file mode 100644
index 1b13350d7..000000000
--- a/lib/pages/setting/style_setting.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:PiliPlus/pages/setting/models/style_settings.dart';
-import 'package:flutter/material.dart';
-
-class StyleSetting extends StatefulWidget {
- const StyleSetting({super.key, this.showAppBar = true});
-
- final bool showAppBar;
-
- @override
- State createState() => _StyleSettingState();
-}
-
-class _StyleSettingState extends State {
- final settings = styleSettings;
-
- @override
- Widget build(BuildContext context) {
- final showAppBar = widget.showAppBar;
- final padding = MediaQuery.viewPaddingOf(context);
- return Scaffold(
- resizeToAvoidBottomInset: false,
- appBar: showAppBar ? AppBar(title: const Text('外观设置')) : null,
- body: ListView.builder(
- padding: EdgeInsets.only(
- left: showAppBar ? padding.left : 0,
- right: showAppBar ? padding.right : 0,
- bottom: padding.bottom + 100,
- ),
- itemCount: settings.length,
- itemBuilder: (context, index) => settings[index].widget,
- ),
- );
- }
-}
diff --git a/lib/pages/setting/video_setting.dart b/lib/pages/setting/video_setting.dart
deleted file mode 100644
index 495720aa6..000000000
--- a/lib/pages/setting/video_setting.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'package:PiliPlus/pages/setting/models/video_settings.dart';
-import 'package:flutter/material.dart';
-
-class VideoSetting extends StatefulWidget {
- const VideoSetting({super.key, this.showAppBar = true});
-
- final bool showAppBar;
-
- @override
- State createState() => _VideoSettingState();
-}
-
-class _VideoSettingState extends State {
- final settings = videoSettings;
-
- @override
- Widget build(BuildContext context) {
- final showAppBar = widget.showAppBar;
- final padding = MediaQuery.viewPaddingOf(context);
- return Scaffold(
- resizeToAvoidBottomInset: false,
- appBar: showAppBar ? AppBar(title: const Text('音视频设置')) : null,
- body: ListView.builder(
- padding: EdgeInsets.only(
- left: showAppBar ? padding.left : 0,
- right: showAppBar ? padding.right : 0,
- bottom: padding.bottom + 100,
- ),
- itemCount: settings.length,
- itemBuilder: (context, index) => settings[index].widget,
- ),
- );
- }
-}
diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart
index 197652b57..633dd7317 100644
--- a/lib/pages/setting/view.dart
+++ b/lib/pages/setting/view.dart
@@ -4,12 +4,7 @@ import 'package:PiliPlus/http/login.dart';
import 'package:PiliPlus/models/common/setting_type.dart';
import 'package:PiliPlus/pages/about/view.dart';
import 'package:PiliPlus/pages/login/controller.dart';
-import 'package:PiliPlus/pages/setting/extra_setting.dart';
-import 'package:PiliPlus/pages/setting/play_setting.dart';
-import 'package:PiliPlus/pages/setting/privacy_setting.dart';
-import 'package:PiliPlus/pages/setting/recommend_setting.dart';
-import 'package:PiliPlus/pages/setting/style_setting.dart';
-import 'package:PiliPlus/pages/setting/video_setting.dart';
+import 'package:PiliPlus/pages/setting/common_setting.dart';
import 'package:PiliPlus/pages/setting/widgets/multi_select_dialog.dart';
import 'package:PiliPlus/pages/webdav/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
@@ -43,6 +38,7 @@ class _SettingPageState extends State {
late SettingType _type = SettingType.privacySetting;
final RxBool _noAccount = Accounts.account.isEmpty.obs;
late bool _isPortrait;
+ late ThemeData theme;
static const List<_SettingsModel> _items = [
_SettingsModel(
@@ -86,9 +82,15 @@ class _SettingPageState extends State {
];
@override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+
+ theme = Theme.of(context);
_isPortrait = MediaQuery.sizeOf(context).isPortrait;
+ }
+
+ @override
+ Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
@@ -111,28 +113,19 @@ class _SettingPageState extends State {
Expanded(
flex: 6,
child: switch (_type) {
- SettingType.privacySetting => const PrivacySetting(
+ .privacySetting ||
+ .recommendSetting ||
+ .videoSetting ||
+ .playSetting ||
+ .styleSetting ||
+ .extraSetting => CommonSetting(
+ settingType: _type,
showAppBar: false,
),
- SettingType.recommendSetting => const RecommendSetting(
+ .webdavSetting => const WebDavSettingPage(
showAppBar: false,
),
- SettingType.videoSetting => const VideoSetting(
- showAppBar: false,
- ),
- SettingType.playSetting => const PlaySetting(
- showAppBar: false,
- ),
- SettingType.styleSetting => const StyleSetting(
- showAppBar: false,
- ),
- SettingType.extraSetting => const ExtraSetting(
- showAppBar: false,
- ),
- SettingType.webdavSetting => const WebDavSettingPage(
- showAppBar: false,
- ),
- SettingType.about => const AboutPage(showAppBar: false),
+ .about => const AboutPage(showAppBar: false),
},
),
],
@@ -149,7 +142,18 @@ class _SettingPageState extends State {
void _toPage(SettingType type) {
if (_isPortrait) {
- Get.toNamed('/${type.name}');
+ Get.to(
+ () => switch (type) {
+ .privacySetting ||
+ .recommendSetting ||
+ .videoSetting ||
+ .playSetting ||
+ .styleSetting ||
+ .extraSetting => CommonSetting(settingType: type),
+ .webdavSetting => const WebDavSettingPage(),
+ .about => const AboutPage(),
+ },
+ );
} else {
_type = type;
setState(() {});
diff --git a/lib/pages/sponsor_block/block_mixin.dart b/lib/pages/sponsor_block/block_mixin.dart
index 907908545..c554fb8a0 100644
--- a/lib/pages/sponsor_block/block_mixin.dart
+++ b/lib/pages/sponsor_block/block_mixin.dart
@@ -1,6 +1,7 @@
import 'dart:async' show StreamSubscription, Timer;
import 'dart:math' as math;
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/sponsor_block.dart';
@@ -285,40 +286,32 @@ mixin BlockMixin on GetxController {
void _showVoteDialog(SegmentModel segment) {
showDialog(
context: Get.context!,
- builder: (context) => AlertDialog(
- clipBehavior: Clip.hardEdge,
- contentPadding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
- content: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- title: const Text('赞成票', style: TextStyle(fontSize: 14)),
- onTap: () {
- Get.back();
- _doVote(segment.uuid, 1);
- },
- ),
- ListTile(
- dense: true,
- title: const Text('反对票', style: TextStyle(fontSize: 14)),
- onTap: () {
- Get.back();
- _doVote(segment.uuid, 0);
- },
- ),
- ListTile(
- dense: true,
- title: const Text('更改类别', style: TextStyle(fontSize: 14)),
- onTap: () {
- Get.back();
- _showCategoryDialog(segment);
- },
- ),
- ],
+ builder: (context) => SimpleDialog(
+ clipBehavior: .hardEdge,
+ contentPadding: const .symmetric(vertical: 10),
+ children: [
+ DialogOption(
+ child: const Text('赞成票', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ _doVote(segment.uuid, 1);
+ },
),
- ),
+ DialogOption(
+ child: const Text('反对票', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ _doVote(segment.uuid, 0);
+ },
+ ),
+ DialogOption(
+ child: const Text('更改类别', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ _showCategoryDialog(segment);
+ },
+ ),
+ ],
),
);
}
@@ -331,54 +324,49 @@ mixin BlockMixin on GetxController {
void _showCategoryDialog(SegmentModel segment) {
showDialog(
context: Get.context!,
- builder: (context) => AlertDialog(
- clipBehavior: Clip.hardEdge,
- contentPadding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
- content: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: SegmentType.values
- .map(
- (item) => ListTile(
- dense: true,
- onTap: () {
- Get.back();
- SponsorBlock.voteOnSponsorTime(
- uuid: segment.uuid,
- category: item,
- ).then((i) {
- SmartDialog.showToast(
- '类别更改${i.isSuccess ? '成功' : '失败: $i'}',
- );
- });
- },
- title: Text.rich(
- TextSpan(
- children: [
- WidgetSpan(
- alignment: PlaceholderAlignment.middle,
- child: Container(
- height: 10,
- width: 10,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: blockConfig._getColor(item),
- ),
- ),
- style: const TextStyle(fontSize: 14, height: 1),
+ builder: (context) => SimpleDialog(
+ clipBehavior: .hardEdge,
+ contentPadding: const .symmetric(vertical: 10),
+ children: SegmentType.values
+ .map(
+ (item) => ListTile(
+ dense: true,
+ onTap: () {
+ Get.back();
+ SponsorBlock.voteOnSponsorTime(
+ uuid: segment.uuid,
+ category: item,
+ ).then((i) {
+ SmartDialog.showToast(
+ '类别更改${i.isSuccess ? '成功' : '失败: $i'}',
+ );
+ });
+ },
+ title: Text.rich(
+ TextSpan(
+ children: [
+ WidgetSpan(
+ alignment: PlaceholderAlignment.middle,
+ child: Container(
+ height: 10,
+ width: 10,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: blockConfig._getColor(item),
),
- TextSpan(
- text: ' ${item.title}',
- style: const TextStyle(fontSize: 14, height: 1),
- ),
- ],
+ ),
+ style: const TextStyle(fontSize: 14, height: 1),
),
- ),
+ TextSpan(
+ text: ' ${item.title}',
+ style: const TextStyle(fontSize: 14, height: 1),
+ ),
+ ],
),
- )
- .toList(),
- ),
- ),
+ ),
+ ),
+ )
+ .toList(),
),
);
}
@@ -386,96 +374,91 @@ mixin BlockMixin on GetxController {
void showSBDetail() {
showDialog(
context: Get.context!,
- builder: (context) => AlertDialog(
- clipBehavior: Clip.hardEdge,
- contentPadding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
- content: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: _segmentList
- .map(
- (item) => ListTile(
- onTap: () {
- Get.back();
- if (isBlock) {
- _showVoteDialog(item);
- }
- },
- dense: true,
- title: Text.rich(
- TextSpan(
- children: [
- WidgetSpan(
- alignment: PlaceholderAlignment.middle,
- child: Container(
- height: 10,
- width: 10,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: blockConfig._getColor(item.segmentType),
- ),
- ),
- style: const TextStyle(fontSize: 14, height: 1),
+ builder: (context) => SimpleDialog(
+ clipBehavior: .hardEdge,
+ contentPadding: const .symmetric(vertical: 10),
+ children: _segmentList
+ .map(
+ (item) => ListTile(
+ onTap: () {
+ Get.back();
+ if (isBlock) {
+ _showVoteDialog(item);
+ }
+ },
+ dense: true,
+ title: Text.rich(
+ TextSpan(
+ children: [
+ WidgetSpan(
+ alignment: PlaceholderAlignment.middle,
+ child: Container(
+ height: 10,
+ width: 10,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: blockConfig._getColor(item.segmentType),
),
- TextSpan(
- text: ' ${item.segmentType.title}',
- style: const TextStyle(fontSize: 14, height: 1),
- ),
- ],
+ ),
+ style: const TextStyle(fontSize: 14, height: 1),
),
- ),
- contentPadding: const EdgeInsets.only(left: 16, right: 8),
- subtitle: Text(
- '${DurationUtils.formatDuration(item.segment.$1 / 1000)} 至 ${DurationUtils.formatDuration(item.segment.$2 / 1000)}',
+ TextSpan(
+ text: ' ${item.segmentType.title}',
+ style: const TextStyle(fontSize: 14, height: 1),
+ ),
+ ],
+ ),
+ ),
+ contentPadding: const EdgeInsets.only(left: 16, right: 8),
+ subtitle: Text(
+ '${DurationUtils.formatDuration(item.segment.$1 / 1000)} 至 ${DurationUtils.formatDuration(item.segment.$2 / 1000)}',
+ style: const TextStyle(fontSize: 13),
+ ),
+ trailing: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ item.skipType.label,
style: const TextStyle(fontSize: 13),
),
- trailing: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(
- item.skipType.label,
- style: const TextStyle(fontSize: 13),
+ if (item.segment.$2 != 0)
+ SizedBox(
+ width: 36,
+ height: 36,
+ child: IconButton(
+ tooltip: item.skipType == SkipType.showOnly
+ ? '跳至此片段'
+ : '跳过此片段',
+ onPressed: () {
+ Get.back();
+ onSkip(
+ item,
+ isSkip: item.skipType != SkipType.showOnly,
+ isSeek: false,
+ );
+ },
+ style: IconButton.styleFrom(
+ padding: EdgeInsets.zero,
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ ),
+ icon: Icon(
+ item.skipType == SkipType.showOnly
+ ? Icons.my_location
+ : MdiIcons.debugStepOver,
+ size: 18,
+ color: ColorScheme.of(
+ context,
+ ).onSurface.withValues(alpha: 0.7),
+ ),
),
- if (item.segment.$2 != 0)
- SizedBox(
- width: 36,
- height: 36,
- child: IconButton(
- tooltip: item.skipType == SkipType.showOnly
- ? '跳至此片段'
- : '跳过此片段',
- onPressed: () {
- Get.back();
- onSkip(
- item,
- isSkip: item.skipType != SkipType.showOnly,
- isSeek: false,
- );
- },
- style: IconButton.styleFrom(
- padding: EdgeInsets.zero,
- tapTargetSize: MaterialTapTargetSize.shrinkWrap,
- ),
- icon: Icon(
- item.skipType == SkipType.showOnly
- ? Icons.my_location
- : MdiIcons.debugStepOver,
- size: 18,
- color: Theme.of(
- context,
- ).colorScheme.onSurface.withValues(alpha: 0.7),
- ),
- ),
- )
- else
- const SizedBox(width: 10),
- ],
- ),
- ),
- )
- .toList(),
- ),
- ),
+ )
+ else
+ const SizedBox(width: 10),
+ ],
+ ),
+ ),
+ )
+ .toList(),
),
);
}
diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart
index bc6011a4c..84065e0bb 100644
--- a/lib/pages/video/controller.dart
+++ b/lib/pages/video/controller.dart
@@ -7,6 +7,7 @@ import 'package:PiliPlus/common/widgets/pair.dart';
import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart';
import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pbenum.dart'
show PlaylistSource;
+import 'package:PiliPlus/grpc/dm.dart';
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
@@ -19,7 +20,6 @@ import 'package:PiliPlus/models/common/sponsor_block/segment_model.dart';
import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart';
import 'package:PiliPlus/models/common/video/audio_quality.dart';
import 'package:PiliPlus/models/common/video/source_type.dart';
-import 'package:PiliPlus/models/common/video/subtitle_pref_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_type.dart';
@@ -143,8 +143,7 @@ class VideoDetailController extends GetxController
Box setting = GStorage.setting;
// 预设的解码格式
- late String cacheDecode = Pref.defaultDecode; // def avc
- late String cacheSecondDecode = Pref.secondDecode; // def av1
+ late List preferCodecs = Pref.preferCodecs;
bool get showReply => isFileSource
? false
@@ -669,31 +668,38 @@ class VideoDetailController extends GetxController
}
}
- VideoItem findVideoByQa(int qa) {
+ VideoItem findVideoByQa(int qa, {bool setCodecs = false}) {
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
final videoList = data.dash!.video!.where((i) => i.id == qa).toList();
- final currentDecodeFormats = this.currentDecodeFormats.codes;
- final defaultDecodeFormats = VideoDecodeFormatType.fromString(
- cacheDecode,
- ).codes;
- final secondDecodeFormats = VideoDecodeFormatType.fromString(
- cacheSecondDecode,
- ).codes;
-
- VideoItem? video;
- for (final i in videoList) {
- final codec = i.codecs!;
- if (currentDecodeFormats.any(codec.startsWith)) {
- video = i;
- break;
- } else if (defaultDecodeFormats.any(codec.startsWith)) {
- video = i;
- } else if (video == null && secondDecodeFormats.any(codec.startsWith)) {
- video = i;
+ final currentCodes = currentDecodeFormats.codes;
+ VideoItem? bestVideo;
+ int bestIndex = preferCodecs.length;
+ for (final video in videoList) {
+ final c = video.codecs!;
+ if (currentCodes.any(c.startsWith)) {
+ return video;
+ }
+ for (int i = 0; i < bestIndex; i++) {
+ if (preferCodecs[i].codes.any(c.startsWith)) {
+ bestIndex = i;
+ bestVideo = video;
+ break;
+ }
}
}
- return video ?? videoList.first;
+
+ if (setCodecs) {
+ if (bestIndex < preferCodecs.length) {
+ currentDecodeFormats = preferCodecs[bestIndex];
+ } else {
+ currentDecodeFormats = VideoDecodeFormatType.fromString(
+ videoList.first.codecs!,
+ );
+ }
+ }
+
+ return bestVideo ?? videoList.first;
}
/// 更新画质、音质
@@ -706,11 +712,7 @@ class VideoDetailController extends GetxController
..isBuffering.value = false
..buffered.value = Duration.zero;
- final video = findVideoByQa(currentVideoQa.code);
- if (firstVideo.codecs != video.codecs) {
- currentDecodeFormats = VideoDecodeFormatType.fromString(video.codecs!);
- }
- firstVideo = video;
+ firstVideo = findVideoByQa(currentVideoQa.code, setCodecs: true);
videoUrl = VideoUtils.getCdnUrl(firstVideo.playUrls);
/// 根据currentAudioQa 重新设置audioUrl
@@ -817,6 +819,7 @@ class VideoDetailController extends GetxController
Volume? volume;
// 视频链接
+ /// TODO: merge [DownloadHttp.getVideoUrl].
Future queryVideoUrl({
bool fromReset = false,
bool autoFullScreenFlag = false,
@@ -897,7 +900,7 @@ class VideoDetailController extends GetxController
quality: videoQuality,
);
_setVideoHeight();
- currentDecodeFormats = VideoDecodeFormatType.fromString('avc1');
+ currentDecodeFormats = VideoDecodeFormatType.AVC;
currentVideoQa.value = videoQuality;
await _initPlayerIfNeeded(autoFullScreenFlag);
isQuerying = false;
@@ -929,42 +932,25 @@ class VideoDetailController extends GetxController
}
currentVideoQa.value = VideoQuality.fromCode(targetVideoQa);
+ /// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式
+ final supportFormats = data.supportFormats!;
+
+ // 根据画质选编码格式
+ currentDecodeFormats = VideoUtils.selectCodec(
+ supportFormats
+ .firstWhere(
+ (e) => e.quality == targetVideoQa,
+ orElse: () => supportFormats.first,
+ )
+ .codecs!,
+ preferCodecs,
+ );
+
/// 取出符合当前画质的videoList
- final List videosList = videoList
+ final videosList = videoList
.where((e) => e.quality.code == targetVideoQa)
.toList();
- /// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式
- final List supportFormats = data.supportFormats!;
- // 根据画质选编码格式
- final List supportDecodeFormats = supportFormats
- .firstWhere(
- (e) => e.quality == targetVideoQa,
- orElse: () => supportFormats.first,
- )
- .codecs!;
- // 默认从设置中取AV1
- currentDecodeFormats = VideoDecodeFormatType.fromString(cacheDecode);
- VideoDecodeFormatType secondDecodeFormats =
- VideoDecodeFormatType.fromString(cacheSecondDecode);
- // 当前视频没有对应格式返回第一个
- int flag = 0;
- for (final e in supportDecodeFormats) {
- if (currentDecodeFormats.codes.any(e.startsWith)) {
- flag = 1;
- break;
- } else if (secondDecodeFormats.codes.any(e.startsWith)) {
- flag = 2;
- }
- }
- if (flag == 2) {
- currentDecodeFormats = secondDecodeFormats;
- } else if (flag == 0) {
- currentDecodeFormats = VideoDecodeFormatType.fromString(
- supportDecodeFormats.first,
- );
- }
-
/// 取出符合当前解码格式的videoItem
firstVideo = videosList.firstWhere(
(e) => currentDecodeFormats.codes.any(e.codecs!.startsWith),
@@ -1050,16 +1036,14 @@ class VideoDetailController extends GetxController
RxList subtitles = RxList();
final Map vttSubtitles = {};
- late final RxInt vttSubtitlesIndex = (-1).obs;
- late final RxBool showVP = true.obs;
- late final RxList viewPointList = [].obs;
+ late final vttSubtitlesIndex = (-1).obs;
+ late final showVP = true.obs;
+ late final viewPointList = [].obs;
// 设定字幕轨道
Future setSubtitle(int index) async {
if (index <= 0) {
- await plPlayerController.videoPlayerController?.setSubtitleTrack(
- SubtitleTrack.no(),
- );
+ await plPlayerController.videoPlayerController?.setSubtitleTrack(.no());
vttSubtitlesIndex.value = index;
return;
}
@@ -1136,9 +1120,9 @@ class VideoDetailController extends GetxController
);
if (res case Success(:final response)) {
// interactive video
+ late final introCtr = Get.find(tag: heroTag);
if (isUgc && graphVersion == null) {
try {
- final introCtr = Get.find(tag: heroTag);
if (introCtr.videoDetail.value.rights?.isSteinGate == 1) {
graphVersion = response.interaction?.graphVersion;
getSteinEdgeInfo();
@@ -1150,22 +1134,18 @@ class VideoDetailController extends GetxController
if (isUgc && continuePlayingPart) {
continuePlayingPart = false;
- try {
- UgcIntroController ugcIntroController = Get.find(
- tag: heroTag,
- );
- if ((ugcIntroController.videoDetail.value.pages?.length ?? 0) > 1 &&
- response.lastPlayCid != null &&
- response.lastPlayCid != 0) {
- if (response.lastPlayCid != cid.value) {
- int index = ugcIntroController.videoDetail.value.pages!
- .indexWhere((item) => item.cid == response.lastPlayCid);
+ final lastCid = response.lastPlayCid;
+ if (lastCid != null && lastCid != 0 && lastCid != cid.value) {
+ try {
+ final pages = introCtr.videoDetail.value.pages;
+ if (pages != null && pages.length > 1) {
+ final index = pages.indexWhere((item) => item.cid == lastCid);
if (index != -1) {
onAddItem(index);
}
}
- }
- } catch (_) {}
+ } catch (_) {}
+ }
}
if (plPlayerController.showViewPoints &&
@@ -1184,27 +1164,53 @@ class VideoDetailController extends GetxController
} catch (_) {}
}
- if (response.subtitle?.subtitles?.isNotEmpty == true) {
- subtitles.value = response.subtitle!.subtitles!;
-
- final idx = switch (Pref.subtitlePreferenceV2) {
- SubtitlePrefType.off => 0,
- SubtitlePrefType.on => 1,
- SubtitlePrefType.withoutAi =>
- subtitles.first.lan.startsWith('ai') ? 0 : 1,
- SubtitlePrefType.auto =>
- !subtitles.first.lan.startsWith('ai') ||
- (PlatformUtils.isMobile &&
- (await FlutterVolumeController.getVolume() ?? 0.0) <=
- 0.0)
- ? 1
- : 0,
- };
- await setSubtitle(idx);
+ if (response.subtitle?.subtitles case final sub? when (sub.isNotEmpty)) {
+ _setSubtitle(sub);
+ } else if (!Accounts.main.isLogin) {
+ final res = await DmGrpc.dmView(aid, cid.value);
+ if (res case Success(:final response)) {
+ if (response.hasSubtitle() &&
+ response.subtitle.subtitles.isNotEmpty) {
+ _setSubtitle(
+ response.subtitle.subtitles
+ .map(
+ (i) => Subtitle(
+ lan: i.lan,
+ lanDoc: i.lanDoc,
+ subtitleUrl: i.subtitleUrl.replaceFirst(
+ RegExp('^https?:'),
+ '',
+ ),
+ isAi: i.type == .AI,
+ ),
+ )
+ .toList()
+ ..sort(),
+ );
+ }
+ } else {
+ res.toast();
+ }
}
}
}
+ Future _setSubtitle(List sub) async {
+ subtitles.value = sub;
+ final idx = switch (Pref.subtitlePreferenceV2) {
+ .off => 0,
+ .on => 1,
+ .withoutAi => sub.first.lan.startsWith('ai') ? 0 : 1,
+ .auto =>
+ !sub.first.lan.startsWith('ai') ||
+ (PlatformUtils.isMobile &&
+ (await FlutterVolumeController.getVolume() ?? 0.0) <= 0.0)
+ ? 1
+ : 0,
+ };
+ await setSubtitle(idx);
+ }
+
void updateMediaListHistory(int aid) {
if (args['sortField'] != null) {
VideoHttp.medialistHistory(
diff --git a/lib/pages/video/introduction/pgc/controller.dart b/lib/pages/video/introduction/pgc/controller.dart
index 21cb48e02..523ccee7c 100644
--- a/lib/pages/video/introduction/pgc/controller.dart
+++ b/lib/pages/video/introduction/pgc/controller.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math' show max;
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
@@ -123,59 +124,42 @@ class PgcIntroController extends CommonIntroController {
'${HttpString.baseUrl}/bangumi/play/ep$epId${videoDetailCtr.playedTimePos}';
showDialog(
context: context,
- builder: (_) => AlertDialog(
+ builder: (_) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- title: const Text(
- '复制链接',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
+ children: [
+ DialogOption(
+ child: const Text('复制链接', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ Utils.copyText(videoUrl);
+ },
+ ),
+ DialogOption(
+ child: const Text('其它app打开', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ Get.back();
+ PageUtils.launchURL(videoUrl);
+ },
+ ),
+ if (PlatformUtils.isMobile)
+ DialogOption(
+ child: const Text('分享视频', style: TextStyle(fontSize: 14)),
+ onPressed: () {
+ final item = pgcItem.episodes?.firstWhereOrNull(
+ (item) => item.epId == epId,
+ );
Get.back();
- Utils.copyText(videoUrl);
+ ShareUtils.shareText(
+ '${pgcItem.title}${item != null ? ' ${item.showTitle}' : ''}'
+ ' - $videoUrl',
+ );
},
),
- ListTile(
- dense: true,
- title: const Text(
- '其它app打开',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- PageUtils.launchURL(videoUrl);
- },
- ),
- if (PlatformUtils.isMobile)
- ListTile(
- dense: true,
- title: const Text(
- '分享视频',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- final item = pgcItem.episodes?.firstWhereOrNull(
- (item) => item.epId == epId,
- );
- Get.back();
- ShareUtils.shareText(
- '${pgcItem.title}${item != null ? ' ${item.showTitle}' : ''}'
- ' - $videoUrl',
- );
- },
- ),
- ListTile(
- dense: true,
- title: const Text(
- '分享至动态',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
+ if (isLogin)
+ DialogOption(
+ child: const Text('分享至动态', style: TextStyle(fontSize: 14)),
+ onPressed: () {
Get.back();
final item = pgcItem.episodes?.firstWhereOrNull(
(item) => item.epId == epId,
@@ -186,15 +170,15 @@ class PgcIntroController extends CommonIntroController {
useSafeArea: true,
builder: (context) => RepostPanel(
rid: epId,
- /**
- * 1:番剧 // 4097
- 2:电影 // 4098
- 3:纪录片 // 4101
- 4:国创 // 4100
- 5:电视剧 // 4099
- 6:漫画
- 7:综艺 // 4099
- */
+ /*
+ 1:番剧 // 4097
+ 2:电影 // 4098
+ 3:纪录片 // 4101
+ 4:国创 // 4100
+ 5:电视剧 // 4099
+ 6:漫画
+ 7:综艺 // 4099
+ */
dynType: switch (pgcItem.type) {
1 => 4097,
2 => 4098,
@@ -211,16 +195,16 @@ class PgcIntroController extends CommonIntroController {
);
},
),
- ListTile(
- dense: true,
- title: const Text(
+ if (isLogin)
+ DialogOption(
+ child: const Text(
'分享至消息',
style: TextStyle(fontSize: 14),
),
- onTap: () {
+ onPressed: () {
Get.back();
try {
- EpisodeItem item = pgcItem.episodes!.firstWhere(
+ final item = pgcItem.episodes!.firstWhere(
(item) => item.epId == epId,
);
final title =
@@ -252,8 +236,7 @@ class PgcIntroController extends CommonIntroController {
}
},
),
- ],
- ),
+ ],
),
);
}
diff --git a/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart b/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart
index 2fec611e3..da1d1d610 100644
--- a/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart
+++ b/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart
@@ -13,6 +13,7 @@ import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
class PgcPanel extends StatefulWidget {
@@ -213,11 +214,10 @@ class _PgcPanelState extends State {
if (item.badge?.isNotEmpty == true) ...[
const SizedBox(width: 2),
if (item.badge == '会员')
- Image.asset(
+ SvgPicture.asset(
Assets.vipIcon,
height: 16,
- cacheHeight: 16.cacheSize(context),
- semanticLabel: "大会员",
+ semanticsLabel: "大会员",
)
else
Text(
diff --git a/lib/pages/video/introduction/ugc/controller.dart b/lib/pages/video/introduction/ugc/controller.dart
index dc1b10c92..cbfcc2eda 100644
--- a/lib/pages/video/introduction/ugc/controller.dart
+++ b/lib/pages/video/introduction/ugc/controller.dart
@@ -292,60 +292,59 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
String videoUrl = '${HttpString.baseUrl}/video/$bvid';
showDialog(
context: context,
- builder: (_) => AlertDialog(
+ builder: (_) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
+ children: [
+ ListTile(
+ dense: true,
+ title: const Text(
+ '复制链接',
+ style: TextStyle(fontSize: 14),
+ ),
+ onTap: () {
+ Get.back();
+ Utils.copyText(videoUrl);
+ },
+ trailing: playedTimePos.isNotEmpty
+ ? iconButton(
+ tooltip: '精确分享',
+ icon: const Icon(Icons.timer_outlined),
+ onPressed: () {
+ Get.back();
+ Utils.copyText('$videoUrl$playedTimePos');
+ },
+ )
+ : null,
+ ),
+ ListTile(
+ dense: true,
+ title: const Text(
+ '其它app打开',
+ style: TextStyle(fontSize: 14),
+ ),
+ onTap: () {
+ Get.back();
+ PageUtils.launchURL(videoUrl);
+ },
+ ),
+ if (PlatformUtils.isMobile)
ListTile(
dense: true,
title: const Text(
- '复制链接',
+ '分享视频',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
- Utils.copyText(videoUrl);
- },
- trailing: playedTimePos.isNotEmpty
- ? iconButton(
- tooltip: '精确分享',
- icon: const Icon(Icons.timer_outlined),
- onPressed: () {
- Get.back();
- Utils.copyText('$videoUrl$playedTimePos');
- },
- )
- : null,
- ),
- ListTile(
- dense: true,
- title: const Text(
- '其它app打开',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- PageUtils.launchURL(videoUrl);
+ ShareUtils.shareText(
+ '${videoDetail.title} '
+ 'UP主: ${videoDetail.owner!.name!}'
+ ' - $videoUrl',
+ );
},
),
- if (PlatformUtils.isMobile)
- ListTile(
- dense: true,
- title: const Text(
- '分享视频',
- style: TextStyle(fontSize: 14),
- ),
- onTap: () {
- Get.back();
- ShareUtils.shareText(
- '${videoDetail.title} '
- 'UP主: ${videoDetail.owner!.name!}'
- ' - $videoUrl',
- );
- },
- ),
+ if (isLogin)
ListTile(
dense: true,
title: const Text(
@@ -368,6 +367,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
);
},
),
+ if (isLogin)
ListTile(
dense: true,
title: const Text(
@@ -394,8 +394,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin {
}
},
),
- ],
- ),
+ ],
),
);
}
diff --git a/lib/pages/video/member/view.dart b/lib/pages/video/member/view.dart
index 0893c101e..4fe42183c 100644
--- a/lib/pages/video/member/view.dart
+++ b/lib/pages/video/member/view.dart
@@ -20,7 +20,6 @@ import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart';
import 'package:PiliPlus/pages/video/member/controller.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/bili_utils.dart';
-import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/num_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
@@ -257,13 +256,10 @@ class _HorizontalMemberPageState extends State {
),
),
const SizedBox(width: 8),
- Image.asset(
- BiliUtils.levelName(
- memberInfoModel.level!,
- isSeniorMember: memberInfoModel.isSeniorMember == 1,
- ),
+ BiliUtils.levelPicture(
+ memberInfoModel.level!,
+ isSeniorMember: memberInfoModel.isSeniorMember == 1,
height: 11,
- cacheHeight: 11.cacheSize(context),
),
],
),
diff --git a/lib/pages/video/note/view.dart b/lib/pages/video/note/view.dart
index cdd3c97ca..ca8bdab95 100644
--- a/lib/pages/video/note/view.dart
+++ b/lib/pages/video/note/view.dart
@@ -10,7 +10,6 @@ import 'package:PiliPlus/pages/video/note/controller.dart';
import 'package:PiliPlus/pages/webview/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/bili_utils.dart';
-import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart';
@@ -263,13 +262,10 @@ class _NoteListPageState extends State
),
),
const SizedBox(width: 6),
- Image.asset(
- BiliUtils.levelName(
- item.author!.level!,
- isSeniorMember: item.author!.isSeniorMember == 1,
- ),
+ BiliUtils.levelPicture(
+ item.author!.level!,
+ isSeniorMember: item.author!.isSeniorMember == 1,
height: 11,
- cacheHeight: 11.cacheSize(context),
),
],
),
diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart
index c4e1c3c89..7da217e0c 100644
--- a/lib/pages/video/reply/widgets/reply_item_grpc.dart
+++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart
@@ -184,13 +184,10 @@ class ReplyItemGrpc extends StatelessWidget {
),
),
),
- Image.asset(
- BiliUtils.levelName(
- member.level,
- isSeniorMember: member.isSeniorMember == 1,
- ),
+ BiliUtils.levelPicture(
+ member.level.toInt(),
+ isSeniorMember: member.isSeniorMember == 1,
height: 11,
- cacheHeight: 11.cacheSize(context),
),
if (replyItem.mid == upMid)
const PBadge(
diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart
index 472173c52..e431a48b3 100644
--- a/lib/pages/video/view.dart
+++ b/lib/pages/video/view.dart
@@ -12,6 +12,7 @@ import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart';
import 'package:PiliPlus/common/widgets/route_aware_mixin.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_dynamic_header.dart';
+import 'package:PiliPlus/common/widgets/svg/play_icon.dart';
import 'package:PiliPlus/models/common/episode_panel_type.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart';
import 'package:PiliPlus/models_new/video/video_detail/episode.dart' as ugc;
@@ -51,7 +52,6 @@ import 'package:PiliPlus/services/shutdown_timer_service.dart'
show shutdownTimerService;
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/android/bindings.g.dart';
-import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/image_utils.dart';
@@ -1189,12 +1189,7 @@ class _VideoDetailPageVState extends State
child: IconButton(
tooltip: '播放',
onPressed: handlePlay,
- icon: Image.asset(
- Assets.play,
- width: 60,
- height: 60,
- cacheHeight: 60.cacheSize(context),
- ),
+ icon: const PlayIcon(),
),
),
],
diff --git a/lib/pages/video/view_point/view.dart b/lib/pages/video/view_point/view.dart
index 26d54a555..294d16703 100644
--- a/lib/pages/video/view_point/view.dart
+++ b/lib/pages/video/view_point/view.dart
@@ -110,7 +110,7 @@ class _ViewPointsPageState extends State
}
}
final isCurr = currentIndex == index;
- return _buildItem(theme, segment, isCurr);
+ return _buildItem(theme.colorScheme, segment, isCurr);
},
);
if (_isNested) {
@@ -122,8 +122,11 @@ class _ViewPointsPageState extends State
return child;
}
- Widget _buildItem(ThemeData theme, ViewPointSegment segment, bool isCurr) {
- final theme = Theme.of(context);
+ Widget _buildItem(
+ ColorScheme colorScheme,
+ ViewPointSegment segment,
+ bool isCurr,
+ ) {
return Material(
type: MaterialType.transparency,
child: InkWell(
@@ -137,18 +140,11 @@ class _ViewPointsPageState extends State
}
: null,
child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: Style.safeSpace,
- vertical: 5,
- ),
+ padding: const .symmetric(horizontal: Style.safeSpace, vertical: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- NetworkImgLayer(
- src: segment.url,
- width: 140.8,
- height: 88,
- ),
+ NetworkImgLayer(src: segment.url, width: 140.8, height: 88),
const SizedBox(width: 10),
Expanded(
child: Column(
@@ -163,14 +159,14 @@ class _ViewPointsPageState extends State
style: isCurr
? TextStyle(
fontWeight: FontWeight.bold,
- color: theme.colorScheme.primary,
+ color: colorScheme.primary,
)
: null,
),
Text(
'${segment.from != null ? DurationUtils.formatDuration(segment.from) : ''} - '
'${segment.to != null ? DurationUtils.formatDuration(segment.to) : ''}',
- style: TextStyle(color: theme.colorScheme.outline),
+ style: TextStyle(color: colorScheme.outline),
),
],
),
diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart
index 5d1bcca05..b407fda3b 100644
--- a/lib/pages/video/widgets/header_control.dart
+++ b/lib/pages/video/widgets/header_control.dart
@@ -7,6 +7,7 @@ import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/custom_icon.dart';
import 'package:PiliPlus/common/widgets/dialog/report.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/marquee.dart';
import 'package:PiliPlus/http/danmaku.dart';
import 'package:PiliPlus/http/danmaku_block.dart';
@@ -789,9 +790,7 @@ class HeaderControlState extends State
content: Material(
type: MaterialType.transparency,
child: ListTileTheme(
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 24,
- ),
+ contentPadding: const .symmetric(horizontal: 24),
child: SingleChildScrollView(
child: Column(
children: [
@@ -1071,28 +1070,26 @@ class HeaderControlState extends State
// 选择解码格式
void showSetDecodeFormats() {
- final VideoItem firstVideo = videoDetailCtr.firstVideo;
+ final firstCode = videoDetailCtr.firstVideo.quality.code;
// 当前视频可用的解码格式
- final List videoFormat = videoInfo.supportFormats!;
- final List? list = videoFormat
- .firstWhere((FormatItem e) => e.quality == firstVideo.quality.code)
- .codecs;
+ final videoFormat = videoInfo.supportFormats!;
+
+ final list = videoFormat.firstWhere((e) => e.quality == firstCode).codecs;
if (list == null) {
SmartDialog.showToast('当前视频不支持选择解码格式');
return;
}
// 当前选中的解码格式
- final VideoDecodeFormatType currentDecodeFormats =
- videoDetailCtr.currentDecodeFormats;
+ final curCodecs = videoDetailCtr.currentDecodeFormats.codes;
showBottomSheet(
(context, setState) {
- final theme = Theme.of(context);
+ final colorScheme = ColorScheme.of(context);
return Padding(
padding: const EdgeInsets.all(12),
child: Material(
clipBehavior: Clip.hardEdge,
- color: theme.colorScheme.surface,
+ color: colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: Column(
children: [
@@ -1110,30 +1107,22 @@ class HeaderControlState extends State
itemBuilder: (context, index) {
final item = list[index];
final format = VideoDecodeFormatType.fromString(item);
- final isCurr = currentDecodeFormats.codes.any(
- item.startsWith,
- );
+ final isCurr = curCodecs.any(item.startsWith);
return ListTile(
dense: true,
onTap: () {
- if (isCurr) {
- return;
- }
+ if (isCurr) return;
Get.back();
videoDetailCtr
..currentDecodeFormats = format
..updatePlayer();
+ SmartDialog.showToast("解码已变为:${format.name}");
},
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 20,
- ),
+ contentPadding: const .symmetric(horizontal: 20),
title: Text(format.description),
subtitle: Text(item, style: subTitleStyle),
trailing: isCurr
- ? Icon(
- Icons.done,
- color: theme.colorScheme.primary,
- )
+ ? Icon(Icons.done, color: colorScheme.primary)
: null,
);
},
@@ -1149,69 +1138,100 @@ class HeaderControlState extends State
);
}
+ Future<_SubtitleFormat?> _showFormatDialog() {
+ return showDialog<_SubtitleFormat>(
+ context: context,
+ builder: (context) => SimpleDialog(
+ title: const Text('选择格式'),
+ children: [
+ DialogOption(
+ onPressed: () => Get.back(result: _SubtitleFormat.json),
+ child: const Text('JSON'),
+ ),
+ DialogOption(
+ onPressed: () => Get.back(result: _SubtitleFormat.vtt),
+ child: const Text('WEBVTT'),
+ ),
+ ],
+ ),
+ );
+ }
+
void onExportSubtitle() {
showDialog(
context: context,
- builder: (context) => AlertDialog(
- clipBehavior: Clip.hardEdge,
- contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
- title: const Text('保存字幕'),
- content: SingleChildScrollView(
- child: Column(
- children: videoDetailCtr.subtitles
- .map(
- (item) => ListTile(
- dense: true,
- onTap: () async {
- Get.back();
- final url = item.subtitleUrl;
- if (url == null || url.isEmpty) return;
- try {
- final res = await Request.dio.get(
- url.http2https,
- options: Options(
- responseType: ResponseType.bytes,
- headers: Constants.baseHeaders,
- extra: {'account': const NoAccount()},
- ),
+ builder: (context) {
+ final subtitles = videoDetailCtr.subtitles;
+ return SimpleDialog(
+ clipBehavior: Clip.hardEdge,
+ contentPadding: const .only(bottom: 12),
+ titlePadding: const .fromLTRB(20, 20, 20, 12),
+ title: const Text('保存字幕'),
+ children: List.generate(subtitles.length, (i) {
+ final item = subtitles[i];
+ return DialogOption(
+ onPressed: () async {
+ Get.back();
+ final url = item.subtitleUrl;
+ if (url == null || url.isEmpty) return;
+ final format = await _showFormatDialog();
+ if (format == null) return;
+ try {
+ final Uint8List bytes;
+ switch (format) {
+ case .vtt:
+ var subtitle = videoDetailCtr.vttSubtitles[i];
+ if (subtitle == null) {
+ final res = await VideoHttp.vttSubtitles(
+ item.subtitleUrl!,
);
- if (res.statusCode == 200) {
- final bytes = Uint8List.fromList(
- Request.responseBytesDecoder(
- res.data!,
- res.headers.map,
- ),
- );
- String name =
- '${introController.videoDetail.value.title}-${videoDetailCtr.bvid}-${videoDetailCtr.cid.value}-${item.lanDoc}.json';
- if (Platform.isWindows) {
- // Reserved characters may not be used in file names. See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
- name = name.replaceAll(
- RegExp(r'[<>:/\\|?*"]'),
- '',
- );
- }
- StorageUtils.saveBytes2File(
- name: name,
- bytes: bytes,
- allowedExtensions: const ['json'],
- );
- }
- } catch (e, s) {
- Utils.reportError(e, s);
- SmartDialog.showToast(e.toString());
+ if (res == null) return;
+ subtitle = (isData: true, id: res);
+ videoDetailCtr.vttSubtitles[i] = subtitle;
}
- },
- title: Text(
- item.lanDoc!,
- style: const TextStyle(fontSize: 14),
- ),
- ),
- )
- .toList(),
- ),
- ),
- ),
+ bytes = utf8.encode(subtitle.id);
+ case .json:
+ final res = await Request.dio.get(
+ url.http2https,
+ options: Options(
+ responseType: ResponseType.bytes,
+ headers: Constants.baseHeaders,
+ extra: {'account': const NoAccount()},
+ ),
+ );
+ if (res.statusCode != 200) return;
+ bytes = Uint8List.fromList(
+ Request.responseBytesDecoder(
+ res.data!,
+ res.headers.map,
+ ),
+ );
+ }
+ String name =
+ '${introController.videoDetail.value.title}-${videoDetailCtr.bvid}-${videoDetailCtr.cid.value}-${item.lanDoc}.${format.name}';
+ // Reserved characters may not be used in file names. See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
+ name = name.replaceAll(
+ Platform.isWindows ? RegExp(r'[<>:/\\|?*"]') : '/',
+ '_',
+ );
+ StorageUtils.saveBytes2File(
+ name: name,
+ bytes: bytes,
+ allowedExtensions: [format.name],
+ );
+ } catch (e, s) {
+ Utils.reportError(e, s);
+ SmartDialog.showToast(e.toString());
+ }
+ },
+ child: Text(
+ item.lanDoc ?? item.lan,
+ style: const TextStyle(fontSize: 14),
+ ),
+ );
+ }),
+ );
+ },
);
}
@@ -2065,3 +2085,5 @@ class HeaderControlState extends State
);
}
}
+
+enum _SubtitleFormat { json, vtt }
diff --git a/lib/pages/whisper/widgets/item.dart b/lib/pages/whisper/widgets/item.dart
index cb0b1c265..98f2d9b53 100644
--- a/lib/pages/whisper/widgets/item.dart
+++ b/lib/pages/whisper/widgets/item.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
@@ -56,48 +57,39 @@ class WhisperSessionItem extends StatelessWidget {
: null,
onLongPress: () => showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: DefaultTextStyle(
- style: const TextStyle(fontSize: 14),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- onTap: () {
- Get.back();
- onSetTop(item.isPinned, item.id);
- },
- title: Text(item.isPinned ? '移除置顶' : '置顶'),
- ),
- if (item.id.privateId.hasTalkerUid())
- ListTile(
- dense: true,
- onTap: () {
- Get.back();
- onSetMute(item.isMuted, item.id.privateId.talkerUid);
- },
- title: Text('${item.isMuted ? '关闭' : '开启'}免打扰'),
- ),
- if (item.id.privateId.hasTalkerUid())
- ListTile(
- dense: true,
- onTap: () {
- Get.back();
- showConfirmDialog(
- context: context,
- title: const Text('确定删除该对话?'),
- onConfirm: () =>
- onRemove(item.id.privateId.talkerUid.toInt()),
- );
- },
- title: const Text('删除'),
- ),
- ],
+ children: [
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ onSetTop(item.isPinned, item.id);
+ },
+ child: Text(item.isPinned ? '移除置顶' : '置顶'),
),
- ),
+ if (item.id.privateId.hasTalkerUid())
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ onSetMute(item.isMuted, item.id.privateId.talkerUid);
+ },
+ child: Text('${item.isMuted ? '关闭' : '开启'}免打扰'),
+ ),
+ if (item.id.privateId.hasTalkerUid())
+ DialogOption(
+ onPressed: () {
+ Get.back();
+ showConfirmDialog(
+ context: context,
+ title: const Text('确定删除该对话?'),
+ onConfirm: () =>
+ onRemove(item.id.privateId.talkerUid.toInt()),
+ );
+ },
+ child: const Text('删除'),
+ ),
+ ],
),
),
onSecondaryTapUp: PlatformUtils.isDesktop
diff --git a/lib/pages/whisper_settings/view.dart b/lib/pages/whisper_settings/view.dart
index 070a983f6..ae1a77703 100644
--- a/lib/pages/whisper_settings/view.dart
+++ b/lib/pages/whisper_settings/view.dart
@@ -1,3 +1,4 @@
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
show IMSettingType, Setting;
@@ -82,49 +83,45 @@ class _WhisperSettingsPageState extends State {
String? selected;
showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: item.redirect.windowSelect.item.map(
- (e) {
- if (e.selected) {
- selected ??= e.text;
- }
- return ListTile(
- dense: true,
- onTap: () async {
- if (!e.selected) {
- Get.back();
- for (final j in item.redirect.windowSelect.item) {
- j.selected = false;
- }
- item.redirect.selectedSummary = e.text;
- e.selected = true;
- _controller.loadingState.refresh();
- final settings = {key: item};
- final res = await _controller.onSet(settings);
- if (!res) {
- for (final j in item.redirect.windowSelect.item) {
- j.selected = j.text == selected;
- }
- item.redirect.selectedSummary = selected!;
- _controller.loadingState.refresh();
- }
+ children: item.redirect.windowSelect.item.map(
+ (e) {
+ if (e.selected) {
+ selected ??= e.text;
+ }
+ return DialogOption(
+ onPressed: () async {
+ if (!e.selected) {
+ Get.back();
+ for (final j in item.redirect.windowSelect.item) {
+ j.selected = false;
}
- },
- title: Text(
- e.text,
- style: TextStyle(
- fontSize: 14,
- color: e.selected ? theme.colorScheme.primary : null,
- ),
+ item.redirect.selectedSummary = e.text;
+ e.selected = true;
+ _controller.loadingState.refresh();
+ final settings = {key: item};
+ final res = await _controller.onSet(settings);
+ if (!res) {
+ for (final j in item.redirect.windowSelect.item) {
+ j.selected = j.text == selected;
+ }
+ item.redirect.selectedSummary = selected!;
+ _controller.loadingState.refresh();
+ }
+ }
+ },
+ child: Text(
+ e.text,
+ style: TextStyle(
+ fontSize: 14,
+ color: e.selected ? theme.colorScheme.primary : null,
),
- );
- },
- ).toList(),
- ),
+ ),
+ );
+ },
+ ).toList(),
),
);
} else if (item.redirect.otherPage.hasUrl()) {
diff --git a/lib/plugin/pl_player/view/view.dart b/lib/plugin/pl_player/view/view.dart
index 901add55e..88a17ab68 100644
--- a/lib/plugin/pl_player/view/view.dart
+++ b/lib/plugin/pl_player/view/view.dart
@@ -724,19 +724,16 @@ class _PLVideoPlayerState extends State
),
),
),
- ...videoDetailController.subtitles.indexed.map((e) {
+ ...videoDetailController.subtitles.mapIndexed((i, e) {
return PopupMenuItem(
- value: e.$1 + 1,
+ value: i + 1,
height: 35,
- onTap: () => videoDetailController.setSubtitle(e.$1 + 1),
+ onTap: () => videoDetailController.setSubtitle(i + 1),
child: Text(
- "${e.$2.lanDoc}",
+ e.lanDoc ?? e.lan,
maxLines: 1,
overflow: TextOverflow.ellipsis,
- style: const TextStyle(
- color: Colors.white,
- fontSize: 13,
- ),
+ style: const .new(color: Colors.white, fontSize: 13),
),
);
}),
diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart
index d26c3d522..1fd642e44 100644
--- a/lib/router/app_pages.dart
+++ b/lib/router/app_pages.dart
@@ -1,4 +1,3 @@
-import 'package:PiliPlus/pages/about/view.dart';
import 'package:PiliPlus/pages/article/view.dart';
import 'package:PiliPlus/pages/article_list/view.dart';
import 'package:PiliPlus/pages/audio/view.dart';
@@ -53,18 +52,12 @@ import 'package:PiliPlus/pages/popular_series/view.dart';
import 'package:PiliPlus/pages/search/view.dart';
import 'package:PiliPlus/pages/search_result/view.dart';
import 'package:PiliPlus/pages/search_trending/view.dart';
-import 'package:PiliPlus/pages/setting/extra_setting.dart';
import 'package:PiliPlus/pages/setting/pages/bar_set.dart';
import 'package:PiliPlus/pages/setting/pages/color_select.dart';
import 'package:PiliPlus/pages/setting/pages/display_mode.dart';
import 'package:PiliPlus/pages/setting/pages/font_size_select.dart';
import 'package:PiliPlus/pages/setting/pages/logs.dart';
import 'package:PiliPlus/pages/setting/pages/play_speed_set.dart';
-import 'package:PiliPlus/pages/setting/play_setting.dart';
-import 'package:PiliPlus/pages/setting/privacy_setting.dart';
-import 'package:PiliPlus/pages/setting/recommend_setting.dart';
-import 'package:PiliPlus/pages/setting/style_setting.dart';
-import 'package:PiliPlus/pages/setting/video_setting.dart';
import 'package:PiliPlus/pages/setting/view.dart';
import 'package:PiliPlus/pages/settings_search/view.dart';
import 'package:PiliPlus/pages/space_setting/view.dart';
@@ -72,7 +65,6 @@ import 'package:PiliPlus/pages/sponsor_block/view.dart';
import 'package:PiliPlus/pages/subscription/view.dart';
import 'package:PiliPlus/pages/subscription_detail/view.dart';
import 'package:PiliPlus/pages/video/view.dart';
-import 'package:PiliPlus/pages/webdav/view.dart';
import 'package:PiliPlus/pages/webview/view.dart';
import 'package:PiliPlus/pages/whisper/view.dart';
import 'package:PiliPlus/pages/whisper_detail/view.dart';
@@ -116,26 +108,12 @@ class Routes {
// 用户中心
GetPage(name: '/member', page: () => const MemberPage()),
GetPage(name: '/memberSearch', page: () => const MemberSearchPage()),
- // 推荐流设置
- GetPage(name: '/recommendSetting', page: () => const RecommendSetting()),
- // 音视频设置
- GetPage(name: '/videoSetting', page: () => const VideoSetting()),
- // 播放器设置
- GetPage(name: '/playSetting', page: () => const PlaySetting()),
- // 外观设置
- GetPage(name: '/styleSetting', page: () => const StyleSetting()),
- // 隐私设置
- GetPage(name: '/privacySetting', page: () => const PrivacySetting()),
- // 其它设置
- GetPage(name: '/extraSetting', page: () => const ExtraSetting()),
//
GetPage(name: '/blackListPage', page: () => const BlackListPage()),
GetPage(name: '/colorSetting', page: () => const ColorSelectPage()),
GetPage(name: '/fontSizeSetting', page: () => const FontSizeSelectPage()),
// 屏幕帧率
GetPage(name: '/displayModeSetting', page: () => const SetDisplayMode()),
- // 关于
- GetPage(name: '/about', page: () => const AboutPage()),
//
GetPage(name: '/articlePage', page: () => const ArticlePage()),
@@ -174,7 +152,6 @@ class Routes {
GetPage(name: '/createFav', page: () => const CreateFavPage()),
GetPage(name: '/editProfile', page: () => const EditProfilePage()),
GetPage(name: '/settingsSearch', page: () => const SettingsSearchPage()),
- GetPage(name: '/webdavSetting', page: () => const WebDavSettingPage()),
GetPage(name: '/searchTrending', page: () => const SearchTrendingPage()),
GetPage(name: '/dynTopic', page: () => const DynTopicPage()),
GetPage(name: '/articleList', page: () => const ArticleListPage()),
diff --git a/lib/utils/bili_utils.dart b/lib/utils/bili_utils.dart
index 96779e859..1555079c1 100644
--- a/lib/utils/bili_utils.dart
+++ b/lib/utils/bili_utils.dart
@@ -1,3 +1,6 @@
+import 'package:PiliPlus/common/widgets/svg/level_icon.dart';
+import 'package:flutter/material.dart';
+
abstract final class BiliUtils {
static bool isDefaultFav(int? attr) {
if (attr == null) {
@@ -21,8 +24,12 @@ abstract final class BiliUtils {
return tagid != null && tagid != 0 && tagid != -10 && tagid != -2;
}
- static String levelName(
- Object level, {
+ // https://s1.hdslb.com/bfs/svg-next/font/2025-10-27/freshspace-zpjpp3aqht.css
+ static Widget levelPicture(
+ int level, {
bool isSeniorMember = false,
- }) => 'assets/images/lv/lv${isSeniorMember ? '6_s' : level}.png';
+ double height = 11,
+ }) {
+ return UserLevel(level, height: height, flash: isSeniorMember);
+ }
}
diff --git a/lib/utils/cache_manager.dart b/lib/utils/cache_manager.dart
index 024e90c48..6e8c3027c 100644
--- a/lib/utils/cache_manager.dart
+++ b/lib/utils/cache_manager.dart
@@ -1,6 +1,5 @@
import 'dart:io' show Directory, File;
-import 'package:PiliPlus/utils/extension/file_ext.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:cached_network_image_ce/cached_network_image.dart';
@@ -10,18 +9,19 @@ import 'package:path_provider/path_provider.dart';
abstract final class CacheManager {
static late final DefaultCacheManager manager;
- static Future ensureInitialized() =>
- DefaultCacheManager.init().then((i) => manager = i);
+ static Future ensureInitialized() => DefaultCacheManager.init(
+ maxNrOfCacheLength: Pref.maxCacheSize.toInt(),
+ ).then((i) => manager = i);
// 获取缓存目录
@pragma('vm:notify-debugger-on-exception')
static Future loadApplicationCache() async {
try {
- final Directory tempDirectory = await getTemporaryDirectory();
if (PlatformUtils.isDesktop) {
return manager.getTotalLength();
}
+ final Directory tempDirectory = await getTemporaryDirectory();
if (tempDirectory.existsSync()) {
return await getTotalSizeOfFilesInDir(tempDirectory);
}
@@ -81,22 +81,4 @@ abstract final class CacheManager {
}
} catch (_) {}
}
-
- static Future autoClearCache() async {
- // TODO: remove
- Directory(
- '${(await getTemporaryDirectory()).path}/libCachedImageData',
- ).tryDel(recursive: true);
- if (Pref.autoClearCache) {
- await clearLibraryCache();
- } else {
- final maxCacheSize = Pref.maxCacheSize;
- if (maxCacheSize != 0) {
- final currCache = await loadApplicationCache();
- if (currCache >= maxCacheSize) {
- await clearLibraryCache();
- }
- }
- }
- }
}
diff --git a/lib/utils/id_utils.dart b/lib/utils/id_utils.dart
index 0ffa177f6..953882afb 100644
--- a/lib/utils/id_utils.dart
+++ b/lib/utils/id_utils.dart
@@ -3,6 +3,7 @@
import 'dart:convert' show ascii, base64;
import 'package:PiliPlus/utils/utils.dart';
+import 'package:collection/collection.dart';
import 'package:uuid/v4.dart';
abstract final class IdUtils {
@@ -24,12 +25,6 @@ abstract final class IdUtils {
static final avRegexExact = RegExp(r'^av(\d+)$', caseSensitive: false);
static final digitOnlyRegExp = RegExp(r'^\d+$');
- static void swap(List list, int idx1, int idx2) {
- final idx1Value = list[idx1];
- list[idx1] = list[idx2];
- list[idx2] = idx1Value;
- }
-
/// av转bv
static String av2bv(int aid) {
final bytes = ['B', 'V', '1', '0', '0', '0', '0', '0', '0', '0', '0', '0'];
@@ -40,18 +35,18 @@ abstract final class IdUtils {
tmp ~/= BASE;
}
- swap(bytes, 3, 9);
- swap(bytes, 4, 7);
+ bytes
+ ..swap(3, 9)
+ ..swap(4, 7);
return bytes.join();
}
/// bv转av
static int bv2av(String bvid) {
- final bvidArr = bvid.codeUnits.sublist(3);
-
- swap(bvidArr, 0, 6);
- swap(bvidArr, 1, 4);
+ final bvidArr = bvid.codeUnits.sublist(3)
+ ..swap(0, 6)
+ ..swap(1, 4);
final tmp = bvidArr.fold(0, (pre, char) => pre * BASE + invData[char]!);
return (tmp & MASK_CODE) ^ XOR_CODE;
diff --git a/lib/utils/request_utils.dart b/lib/utils/request_utils.dart
index 9deb098a4..36a3dc3cd 100644
--- a/lib/utils/request_utils.dart
+++ b/lib/utils/request_utils.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:math';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
+import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart';
import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show ReplyInfo;
@@ -169,99 +170,84 @@ abstract final class RequestUtils {
String text = isSpecialFollowed ? '移除特别关注' : '加入特别关注';
showDialog(
context: context,
- builder: (context) => AlertDialog(
+ builder: (context) => SimpleDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ListTile(
- dense: true,
- onTap: () async {
- Get.back();
- final res = await MemberHttp.specialAction(
- fid: mid,
- isAdd: !isSpecialFollowed,
- );
- if (res.isSuccess) {
- SmartDialog.showToast('$text成功');
- afterMod?.call(isSpecialFollowed ? 2 : -10);
- } else {
- res.toast();
- }
- },
- title: Text(
- text,
- style: const TextStyle(fontSize: 14),
- ),
- ),
- ListTile(
- dense: true,
- onTap: () async {
- Get.back();
- final result = await showModalBottomSheet>(
- context: context,
- useSafeArea: true,
- isScrollControlled: true,
- constraints: BoxConstraints(
- maxWidth: min(640, context.mediaQueryShortestSide),
- ),
- builder: (BuildContext context) {
- final maxChildSize =
- PlatformUtils.isMobile &&
- !context.mediaQuerySize.isPortrait
- ? 1.0
- : 0.7;
- return DraggableScrollableSheet(
- minChildSize: 0,
- maxChildSize: 1,
- snap: true,
- expand: false,
- snapSizes: [maxChildSize],
- initialChildSize: maxChildSize,
- builder: (context, scrollController) {
- return GroupPanel(
- mid: mid,
- tags: followStatus!.tag,
- scrollController: scrollController,
- );
- },
- );
- },
- );
- if (result != null) {
- followStatus!.tag = result.toList();
- afterMod?.call(result.contains(-10) ? -10 : 2);
- }
- },
- title: const Text(
- '设置分组',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ListTile(
- dense: true,
- onTap: () async {
- Get.back();
- final res = await VideoHttp.relationMod(
- mid: mid,
- act: 2,
- reSrc: 11,
- );
- if (res.isSuccess) {
- SmartDialog.showToast('取消关注成功');
- afterMod?.call(0);
- } else {
- res.toast();
- }
- },
- title: const Text(
- '取消关注',
- style: TextStyle(fontSize: 14),
- ),
- ),
- ],
- ),
+ children: [
+ DialogOption(
+ onPressed: () async {
+ Get.back();
+ final res = await MemberHttp.specialAction(
+ fid: mid,
+ isAdd: !isSpecialFollowed,
+ );
+ if (res.isSuccess) {
+ SmartDialog.showToast('$text成功');
+ afterMod?.call(isSpecialFollowed ? 2 : -10);
+ } else {
+ res.toast();
+ }
+ },
+ child: Text(text, style: const TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () async {
+ Get.back();
+ final result = await showModalBottomSheet>(
+ context: context,
+ useSafeArea: true,
+ isScrollControlled: true,
+ constraints: BoxConstraints(
+ maxWidth: min(640, context.mediaQueryShortestSide),
+ ),
+ builder: (BuildContext context) {
+ final maxChildSize =
+ PlatformUtils.isMobile &&
+ !context.mediaQuerySize.isPortrait
+ ? 1.0
+ : 0.7;
+ return DraggableScrollableSheet(
+ minChildSize: 0,
+ maxChildSize: 1,
+ snap: true,
+ expand: false,
+ snapSizes: [maxChildSize],
+ initialChildSize: maxChildSize,
+ builder: (context, scrollController) {
+ return GroupPanel(
+ mid: mid,
+ tags: followStatus!.tag,
+ scrollController: scrollController,
+ );
+ },
+ );
+ },
+ );
+ if (result != null) {
+ followStatus!.tag = result.toList();
+ afterMod?.call(result.contains(-10) ? -10 : 2);
+ }
+ },
+ child: const Text('设置分组', style: TextStyle(fontSize: 14)),
+ ),
+ DialogOption(
+ onPressed: () async {
+ Get.back();
+ final res = await VideoHttp.relationMod(
+ mid: mid,
+ act: 2,
+ reSrc: 11,
+ );
+ if (res.isSuccess) {
+ SmartDialog.showToast('取消关注成功');
+ afterMod?.call(0);
+ } else {
+ res.toast();
+ }
+ },
+ child: const Text('取消关注', style: TextStyle(fontSize: 14)),
+ ),
+ ],
),
);
}
diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart
index 7b9eb1d74..752859b51 100644
--- a/lib/utils/storage_key.dart
+++ b/lib/utils/storage_key.dart
@@ -8,8 +8,7 @@ abstract final class SettingBoxKey {
defaultAudioQaCellular = 'defaultAudioQaCellular',
autoPlayEnable = 'autoPlayEnable',
fullScreenMode = 'fullScreenMode',
- defaultDecode = 'defaultDecode',
- secondDecode = 'secondDecode',
+ preferCodecs = 'preferCodecs',
defaultToastOp = 'defaultToastOp',
defaultPicQa = 'defaultPicQa',
enableHA = 'enableHA',
@@ -56,7 +55,6 @@ abstract final class SettingBoxKey {
banWordForRecommend = 'banWordForRecommend',
applyFilterToRelatedVideos = 'applyFilterToRelatedVideos',
autoUpdate = 'autoUpdate',
- autoClearCache = 'autoClearCache',
maxCacheSize = 'maxCacheSize',
defaultShowComment = 'defaultShowComment',
replySortType = 'replySortType',
diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart
index 4fc4c1b56..14cdff96a 100644
--- a/lib/utils/storage_pref.dart
+++ b/lib/utils/storage_pref.dart
@@ -1,5 +1,4 @@
import 'dart:io';
-import 'dart:math' show pow;
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'
show deviceTouchSlop;
@@ -246,15 +245,33 @@ abstract final class Pref {
defaultValue: AudioQuality.k192.code,
);
- static String get defaultDecode => _setting.get(
- SettingBoxKey.defaultDecode,
- defaultValue: VideoDecodeFormatType.AVC.codes.first,
- );
+ static List get preferCodecs {
+ // TODO: remove next 2 version
+ if (_setting.get('defaultDecode') case String codecStr) {
+ String? codecStr2 = _setting.get('secondDecode');
+ _setting.deleteAll(const ['defaultDecode', 'secondDecode']);
+ final codecs = [
+ VideoDecodeFormatType.values.firstWhere(
+ (i) => i.codes.contains(codecStr),
+ ),
+ if (codecStr2 != null && codecStr2 != codecStr)
+ VideoDecodeFormatType.values.firstWhere(
+ (i) => i.codes.contains(codecStr2),
+ ),
+ ];
+ _setting.put(
+ SettingBoxKey.preferCodecs,
+ codecs.map((i) => i.name).toList(),
+ );
+ return codecs;
+ }
- static String get secondDecode => _setting.get(
- SettingBoxKey.secondDecode,
- defaultValue: VideoDecodeFormatType.AV1.codes.first,
- );
+ final codecs = _setting.get(SettingBoxKey.preferCodecs);
+ if (codecs is List && codecs.isNotEmpty) {
+ return codecs.map((i) => VideoDecodeFormatType.values.byName(i)).toList();
+ }
+ return const [];
+ }
static String get hardwareDecoding => _setting.get(
SettingBoxKey.hardwareDecoding,
@@ -598,7 +615,7 @@ abstract final class Pref {
_setting.get(SettingBoxKey.showPgcTimeline, defaultValue: true);
static num get maxCacheSize =>
- _setting.get(SettingBoxKey.maxCacheSize) ?? pow(1024, 3);
+ _setting.get(SettingBoxKey.maxCacheSize) ?? 1 << 30;
static bool get optTabletNav =>
_setting.get(SettingBoxKey.optTabletNav, defaultValue: true);
@@ -717,9 +734,6 @@ abstract final class Pref {
!Platform.isIOS &&
_setting.get(SettingBoxKey.dynamicColor, defaultValue: true);
- static bool get autoClearCache =>
- _setting.get(SettingBoxKey.autoClearCache, defaultValue: false);
-
static bool get enableSystemProxy =>
_setting.get(SettingBoxKey.enableSystemProxy, defaultValue: false);
diff --git a/lib/utils/video_utils.dart b/lib/utils/video_utils.dart
index fa66cb20b..5f080810f 100644
--- a/lib/utils/video_utils.dart
+++ b/lib/utils/video_utils.dart
@@ -1,4 +1,5 @@
import 'package:PiliPlus/models/common/video/cdn_type.dart';
+import 'package:PiliPlus/models/common/video/video_decode_type.dart';
import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart';
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
@@ -93,4 +94,28 @@ abstract final class VideoUtils {
final urlInfo = e.urlInfo.getOrFirst(index);
return (liveCdnUrl ?? urlInfo.host) + e.baseUrl + urlInfo.extra;
}
+
+ static VideoDecodeFormatType selectCodec(
+ Iterable codecs,
+ List preferCodecs,
+ ) {
+ if (preferCodecs.isNotEmpty) {
+ int bestIndex = preferCodecs.length;
+ for (final e in codecs) {
+ for (int i = 0; i < bestIndex; i++) {
+ if (preferCodecs[i].codes.any(e.startsWith)) {
+ bestIndex = i;
+ if (bestIndex == 0) {
+ return preferCodecs[0];
+ }
+ break;
+ }
+ }
+ }
+ if (bestIndex < preferCodecs.length) {
+ return preferCodecs[bestIndex];
+ }
+ }
+ return VideoDecodeFormatType.fromString(codecs.first);
+ }
}
diff --git a/pubspec.lock b/pubspec.lock
index f16f4c5e7..92fd9bcea 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -207,17 +207,17 @@ packages:
description:
path: cached_network_image
ref: develop
- resolved-ref: "5b79ff9f53a9a55b57d4d6fc52eb529fdfdb30d7"
- url: "https://github.com/bggRGjQaUbCoE/flutter_cached_network_image_ce.git"
+ resolved-ref: "6ad91e9bf71254803e222b7d254751576f2f6d7b"
+ url: "https://github.com/My-Responsitories/flutter_cached_network_image_ce.git"
source: git
version: "4.6.4"
cached_network_image_platform_interface_ce:
dependency: transitive
description:
path: cached_network_image_platform_interface
- ref: "5b79ff9f53a9a55b57d4d6fc52eb529fdfdb30d7"
- resolved-ref: "5b79ff9f53a9a55b57d4d6fc52eb529fdfdb30d7"
- url: "https://github.com/bggRGjQaUbCoE/flutter_cached_network_image_ce.git"
+ ref: "6ad91e9bf71254803e222b7d254751576f2f6d7b"
+ resolved-ref: "6ad91e9bf71254803e222b7d254751576f2f6d7b"
+ url: "https://github.com/My-Responsitories/flutter_cached_network_image_ce.git"
source: git
version: "5.2.0"
canvas_danmaku:
diff --git a/pubspec.yaml b/pubspec.yaml
index 72a02fc64..79f61bb12 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -47,7 +47,7 @@ dependencies:
brotli: ^0.6.0
cached_network_image_ce:
git:
- url: https://github.com/bggRGjQaUbCoE/flutter_cached_network_image_ce.git
+ url: https://github.com/My-Responsitories/flutter_cached_network_image_ce.git
path: cached_network_image
ref: develop
canvas_danmaku:
@@ -224,6 +224,11 @@ dependency_overrides:
url: https://github.com/bggRGjQaUbCoE/screen_brightness.git
path: screen_brightness_android
ref: main
+ cached_network_image_ce:
+ git:
+ url: https://github.com/My-Responsitories/flutter_cached_network_image_ce.git
+ path: cached_network_image
+ ref: develop
dev_dependencies:
flutter_test:
@@ -265,7 +270,6 @@ flutter:
assets:
- path: assets/images/
- - path: assets/images/lv/
- path: assets/images/logo/
- path: assets/images/logo/ico/
platforms: [windows]