Compare commits

..

4 Commits

Author SHA1 Message Date
dom
2536350ccf fix parse dyn
Signed-off-by: dom <githubaccount56556@proton.me>
2026-06-13 12:30:10 +08:00
dom
3fae4ef632 opt ui
Signed-off-by: dom <githubaccount56556@proton.me>
2026-06-13 10:43:47 +08:00
dom
b02e4253f1 fix video progress
Signed-off-by: dom <githubaccount56556@proton.me>
2026-06-13 10:43:47 +08:00
My-Responsitories
199ad274b7 tweaks (#2404)
* tweaks

* opt: semantics
2026-06-13 10:43:37 +08:00
33 changed files with 344 additions and 427 deletions

View File

@@ -9,9 +9,7 @@ abstract final class Style {
static const aspectRatio = 16 / 10;
static const aspectRatio16x9 = 16 / 9;
static const imgMaxRatio = 2.6;
static const bottomSheetRadius = BorderRadius.vertical(
top: Radius.circular(18),
);
static const bottomSheetRadius = BorderRadius.vertical(top: .circular(18));
static const dialogFixedConstraints = BoxConstraints.tightFor(width: 420);
static const topBarHeight = 52.0;
static const buttonStyle = ButtonStyle(

View File

@@ -37,7 +37,9 @@ import 'package:flutter/rendering.dart'
BoxHitTestEntry,
ContainerParentDataMixin,
InformationCollector,
DiagnosticsDebugCreator;
DiagnosticsDebugCreator,
RenderObjectVisitor,
SemanticsConfiguration;
/// ref [LayoutBuilder]
@@ -250,6 +252,23 @@ class RenderImageGrid extends RenderBox
super.dispose();
}
@override
void visitChildrenForSemantics(RenderObjectVisitor visitor) {
RenderBox? child = firstChild;
while (child != null) {
visitor(child);
child = (child.parentData as MultiChildLayoutParentData).nextSibling;
}
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config
..explicitChildNodes = true
..isSemanticBoundary = true;
}
@override
bool get isRepaintBoundary => true; // gif repaint
}

View File

@@ -22,7 +22,6 @@ 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/image_grid/image_grid_builder.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/extension/context_ext.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
@@ -209,9 +208,9 @@ class ImageGridView extends StatelessWidget {
width: width,
height: height,
decoration: BoxDecoration(
color: Theme.of(
color: ColorScheme.of(
context,
).colorScheme.onInverseSurface.withValues(alpha: 0.4),
).onInverseSurface.withValues(alpha: 0.4),
),
child: Image.asset(
Assets.loading,
@@ -221,6 +220,7 @@ class ImageGridView extends StatelessWidget {
),
);
return List.generate(picArr.length, (index) {
void onTap() => _onTap(context, index);
final item = picArr[index];
final borderRadius = _borderRadius(
info.column,
@@ -241,30 +241,21 @@ class ImageGridView extends StatelessWidget {
getPlaceHolder: () => placeHolder,
),
if (item.isLivePhoto)
const PBadge(
text: 'Live',
right: 8,
bottom: 8,
type: PBadgeType.gray,
)
const PBadge(text: 'Live', right: 8, bottom: 8, type: .gray)
else if (item.isLongPic)
const PBadge(
text: '长图',
right: 8,
bottom: 8,
),
const PBadge(text: '长图', right: 8, bottom: 8),
],
);
if (!item.isLongPic) {
child = Hero(
tag: '${item.url}$hashCode',
child: child,
);
child = Hero(tag: '${item.url}$hashCode', child: child);
}
return LayoutId(
id: index,
child = Semantics(
label: '图片,第 ${index + 1} 张,共 ${picArr.length}',
button: true,
onTap: onTap,
child: child,
);
return LayoutId(id: index, child: child);
});
},
),

View File

@@ -25,8 +25,8 @@ abstract final class AudioGrpc {
playerArgs: PlayerArgs(
qn: Int64(qn),
fnval: Int64(fnval),
forceHost: Int64(2),
voiceBalance: Int64(1),
forceHost: Int64.TWO,
voiceBalance: Int64.ONE,
),
),
PlayURLResp.fromBuffer,
@@ -60,8 +60,8 @@ abstract final class AudioGrpc {
playerArgs: PlayerArgs(
qn: Int64(qn),
fnval: Int64(fnval),
forceHost: Int64(2),
voiceBalance: Int64(1),
forceHost: Int64.TWO,
voiceBalance: Int64.ONE,
),
extraId: extraId,
sortOpt: SortOption(order: order),

View File

@@ -17,10 +17,7 @@ abstract final class SpaceGrpc {
GrpcUrl.opusSpaceFlow,
OpusSpaceFlowReq(
hostMid: Int64(hostMid),
pagination: Pagination(
pageSize: 20,
next: next,
),
pagination: Pagination(pageSize: 20, next: next),
filterType: filterType,
),
OpusSpaceFlowResp.fromBuffer,

View File

@@ -10,9 +10,7 @@ abstract final class ViewGrpc {
}) {
return GrpcReq.request(
GrpcUrl.view,
ViewReq(
bvid: bvid,
),
ViewReq(bvid: bvid),
ViewReply.fromBuffer,
);
}

View File

@@ -247,19 +247,15 @@ abstract final class VideoHttp {
case .pgc:
final result = res.data['result'];
data = PlayUrlModel.fromJson(result['video_info']);
if (result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress']
case final int progress when progress > 0) {
data.lastPlayTime = progress;
}
data = PlayUrlModel.fromJson(result['video_info'])
..lastPlayTime =
result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];
case .pugv:
final result = res.data['data'];
data = PlayUrlModel.fromJson(result);
if (result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress']
case final int progress when progress > 0) {
data.lastPlayTime = progress;
}
data = PlayUrlModel.fromJson(result)
..lastPlayTime =
result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];
}
return Success(data);
} else if (epid != null && videoType == .ugc) {

View File

@@ -422,7 +422,9 @@ class ModuleAuthorModel extends Avatar {
}
pubAction = json['pub_action'];
pubTime = json['pub_time'];
pubTs = json['pub_ts'] == 0 ? null : safeToInt(json['pub_ts']);
if (safeToInt(json['pub_ts']) case final pubTs? when pubTs > 0) {
this.pubTs = pubTs;
}
type = json['type'];
if (PendantAvatar.showDecorate) {
decorate = json['decorate'] == null
@@ -1340,7 +1342,9 @@ class DynamicStat {
bool? status;
DynamicStat.fromJson(Map<String, dynamic> json) {
count = json['count'] == 0 ? null : safeToInt(json['count']);
if (safeToInt(json['count']) case final count? when count > 0) {
this.count = count;
}
status = json['status'];
}
}

View File

@@ -22,7 +22,7 @@ class PlayUrlModel {
this.seekType,
this.dash,
this.supportFormats,
this.lastPlayTime,
this._lastPlayTime = 0,
this.lastPlayCid,
});
@@ -42,7 +42,17 @@ class PlayUrlModel {
List<Durl>? durl;
List<FormatItem>? supportFormats;
Volume? volume;
int? lastPlayTime;
late int _lastPlayTime;
int get lastPlayTime => _lastPlayTime;
set lastPlayTime(int? value) {
if (value != null && value > 0) {
_lastPlayTime = value;
} else {
_lastPlayTime = 0;
}
}
int? lastPlayCid;
String? curLanguage;
Language? language;
@@ -69,9 +79,7 @@ class PlayUrlModel {
?.map<FormatItem>((e) => FormatItem.fromJson(e))
.toList();
volume = json['volume'] == null ? null : Volume.fromJson(json['volume']);
if (json['last_play_time'] case final int progress when progress > 0) {
lastPlayTime = progress;
}
lastPlayTime = json['last_play_time'];
lastPlayCid = json['last_play_cid'];
curLanguage = json['cur_language'];
language = json['language'] == null

View File

@@ -181,22 +181,14 @@ Commit Hash: ${BuildConfig.commitHash}''',
onTap: PiliAndroidHelper.openLinkVerifySettings,
leading: const Icon(MdiIcons.linkBoxOutline),
title: const Text('打开受支持的链接'),
trailing: Icon(
Icons.arrow_forward,
size: 16,
color: outline,
),
trailing: Icon(Icons.arrow_forward, size: 16, color: outline),
),
ListTile(
onTap: () =>
PageUtils.launchURL('${Constants.sourceCodeUrl}/issues'),
leading: const Icon(Icons.feedback_outlined),
title: const Text('问题反馈'),
trailing: Icon(
Icons.arrow_forward,
size: 16,
color: outline,
),
trailing: Icon(Icons.arrow_forward, size: 16, color: outline),
),
ListTile(
onTap: () => Get.toNamed('/logs'),

View File

@@ -225,9 +225,10 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
const Duration(milliseconds: 500),
() async {
try {
List<XFile> pickedFiles = await imagePicker.pickMultiImage(
final pickedFiles = await imagePicker.pickMultiImage(
limit: limit,
imageQuality: 100,
requestFullMetadata: false,
);
if (pickedFiles.isNotEmpty) {
for (int i = 0; i < pickedFiles.length; i++) {

View File

@@ -428,9 +428,10 @@ class _CreateVotePageState extends State<CreateVotePage> {
const Duration(milliseconds: 500),
() async {
try {
XFile? pickedFile = await imagePicker.pickImage(
final pickedFile = await imagePicker.pickImage(
imageQuality: 100,
source: ImageSource.gallery,
requestFullMetadata: false,
);
if (pickedFile != null) {
final path = pickedFile.path;

View File

@@ -116,9 +116,10 @@ class _CreateFavPageState extends State<CreateFavPage> {
Future<void> _pickImg(BuildContext context, ThemeData theme) async {
try {
XFile? pickedFile = await _imagePicker.pickImage(
final pickedFile = await _imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 100,
requestFullMetadata: false,
);
if (pickedFile != null && mounted) {
String imgPath = pickedFile.path;

View File

@@ -632,19 +632,22 @@ class LiveRoomController extends GetxController {
PublishRoute(
barrierColor: Colors.transparent,
pageBuilder: (context, animation, secondaryAnimation) {
return LiveSendDmPanel(
fromEmote: fromEmote,
liveRoomController: this,
items: savedDanmaku,
autofocus: !fromEmote,
onSave: (msg) {
if (msg.isEmpty) {
savedDanmaku?.clear();
savedDanmaku = null;
} else {
savedDanmaku = msg.toList();
}
},
return Theme(
data: ThemeUtils.darkTheme,
child: LiveSendDmPanel(
fromEmote: fromEmote,
liveRoomController: this,
items: savedDanmaku,
autofocus: !fromEmote,
onSave: (msg) {
if (msg.isEmpty) {
savedDanmaku?.clear();
savedDanmaku = null;
} else {
savedDanmaku = msg.toList();
}
},
),
);
},
transitionDuration: fromEmote

View File

@@ -48,6 +48,7 @@ import 'package:PiliPlus/utils/share_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image_ce/cached_network_image.dart';
import 'package:canvas_danmaku/danmaku_screen.dart';
@@ -57,6 +58,8 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart';
const baseWhite = Color(0xFFEEEEEE);
class LiveRoomPage extends StatefulWidget {
const LiveRoomPage({super.key});
@@ -233,7 +236,10 @@ class _LiveRoomPageState extends State<LiveRoomPage>
child: child,
);
}
return child;
return Theme(
data: ThemeUtils.darkTheme,
child: child,
);
}
Widget videoPlayerPanel(
@@ -541,7 +547,6 @@ class _LiveRoomPageState extends State<LiveRoomPage>
}
PreferredSizeWidget _buildAppBar(bool isFullScreen) {
final color = Theme.of(context).colorScheme.onSurfaceVariant;
return AppBar(
primary: !plPlayerController.removeSafeArea,
toolbarHeight: isFullScreen ? 0 : null,
@@ -563,7 +568,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
Get.toNamed('/member?mid=${roomInfoH5.roomInfo?.uid}'),
child: Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
NetworkImgLayer(
width: 34,
@@ -623,47 +628,35 @@ class _LiveRoomPageState extends State<LiveRoomPage>
return <PopupMenuEntry>[
PopupMenuItem(
onTap: () => Utils.copyText(liveUrl),
child: Row(
child: const Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Icon(
Icons.copy,
size: 19,
color: color,
),
const Text('复制链接'),
Icon(Icons.copy, size: 19),
Text('复制链接'),
],
),
),
if (PlatformUtils.isMobile)
PopupMenuItem(
onTap: () => ShareUtils.shareText(liveUrl),
child: Row(
child: const Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Icon(
Icons.share,
size: 19,
color: color,
),
const Text('分享直播间'),
Icon(Icons.share, size: 19),
Text('分享直播间'),
],
),
),
PopupMenuItem(
onTap: () => PageUtils.inAppWebview(liveUrl, off: true),
child: Row(
child: const Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Icon(
Icons.open_in_browser,
size: 19,
color: color,
),
const Text('浏览器打开'),
Icon(Icons.open_in_browser, size: 19),
Text('浏览器打开'),
],
),
),
@@ -690,16 +683,12 @@ class _LiveRoomPageState extends State<LiveRoomPage>
SmartDialog.showToast(e.toString());
}
},
child: Row(
child: const Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
mainAxisSize: .min,
children: [
Icon(
Icons.forward_to_inbox,
size: 19,
color: color,
),
const Text('分享至消息'),
Icon(Icons.forward_to_inbox, size: 19),
Text('分享至消息'),
],
),
),
@@ -800,25 +789,20 @@ class _LiveRoomPageState extends State<LiveRoomPage>
Widget get _buildInputWidget {
final child = Container(
padding: EdgeInsets.only(
top: 5,
left: 10,
right: 10,
bottom: padding.bottom,
),
padding: .only(top: 5, left: 10, right: 10, bottom: padding.bottom),
height: 70 + padding.bottom,
decoration: const BoxDecoration(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
borderRadius: .vertical(top: .circular(20)),
border: Border(top: BorderSide(color: Color(0x1AFFFFFF))),
color: Color(0x1AFFFFFF),
),
child: GestureDetector(
onTap: _liveRoomController.onSendDanmaku,
behavior: HitTestBehavior.opaque,
behavior: .opaque,
child: Padding(
padding: const EdgeInsets.only(top: 5, bottom: 10),
padding: const .only(top: 5, bottom: 10),
child: Align(
alignment: Alignment.topCenter,
alignment: .topCenter,
child: Row(
spacing: 6,
children: [
@@ -830,9 +814,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
width: 34,
height: 34,
child: IconButton(
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
),
style: IconButton.styleFrom(padding: .zero),
onPressed: () {
final newVal = !enableShowLiveDanmaku;
plPlayerController.enableShowDanmaku.value = newVal;
@@ -847,12 +829,12 @@ class _LiveRoomPageState extends State<LiveRoomPage>
? const Icon(
size: 22,
CustomIcons.dm_on,
color: Color(0xFFEEEEEE),
color: baseWhite,
)
: const Icon(
size: 22,
CustomIcons.dm_off,
color: Color(0xFFEEEEEE),
color: baseWhite,
),
),
);
@@ -861,7 +843,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
const Expanded(
child: Text(
'发送弹幕',
style: TextStyle(color: Color(0xFFEEEEEE)),
style: TextStyle(color: baseWhite),
),
),
Builder(
@@ -882,7 +864,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
dimension: 34,
child: Icon(
size: 22,
color: Color(0xFFEEEEEE),
color: baseWhite,
Icons.thumb_up_off_alt,
),
),
@@ -930,7 +912,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
onPressed: () => _liveRoomController.onSendDanmaku(true),
icon: const Icon(
size: 22,
color: Color(0xFFEEEEEE),
color: baseWhite,
Icons.emoji_emotions_outlined,
),
),

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -36,6 +37,8 @@ class _BottomControlState extends State<BottomControl> with HeaderMixin {
late final LiveRoomController liveRoomCtr = widget.liveRoomCtr;
@override
late final PlPlayerController plPlayerController = widget.plPlayerController;
@override
ThemeData get theme => ThemeUtils.darkTheme;
@override
Widget build(BuildContext context) {

View File

@@ -307,7 +307,6 @@ class _LiveHeaderControlState extends State<LiveHeaderControl>
showModalBottomSheet(
context: context,
useSafeArea: true,
clipBehavior: .hardEdge,
isScrollControlled: true,
constraints: BoxConstraints(
maxWidth: math.min(640, context.mediaQueryShortestSide),
@@ -331,11 +330,7 @@ class _LiveHeaderControlState extends State<LiveHeaderControl>
final currStyle = TextStyle(fontSize: 14, color: secondary);
return Theme(
data: theme.copyWith(dividerColor: Colors.transparent),
child: ListView(
controller: scrollController,
padding: .only(
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
),
child: Column(
children: [
InkWell(
onTap: Get.back,
@@ -354,90 +349,100 @@ class _LiveHeaderControlState extends State<LiveHeaderControl>
),
),
),
...controller.stream.indexed.map((stream) {
final isCurrStream = stream.$1 == controller.streamIndex;
final streamColor = isCurrStream
? secondary
: onSurfaceVariant;
return _ExpansionTile(
initiallyExpanded: isCurrStream,
iconColor: streamColor,
collapsedIconColor: streamColor,
title: Text(
stream.$2.protocolName ?? stream.$1.toString(),
style: isCurrStream
? currStyle
: const TextStyle(fontSize: 14),
Expanded(
child: ListView(
controller: scrollController,
padding: .only(
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
),
children: stream.$2.format.indexed.map((format) {
final isCurrFormat =
isCurrStream && format.$1 == controller.formatIndex;
final formatColor = isCurrFormat
children: controller.stream.indexed.map((stream) {
final isCurrStream =
stream.$1 == controller.streamIndex;
final streamColor = isCurrStream
? secondary
: onSurfaceVariant;
return _ExpansionTile(
initiallyExpanded: isCurrFormat,
iconColor: formatColor,
collapsedIconColor: formatColor,
initiallyExpanded: isCurrStream,
iconColor: streamColor,
collapsedIconColor: streamColor,
title: Text(
format.$2.formatName ?? format.$1.toString(),
style: isCurrFormat
stream.$2.protocolName ?? stream.$1.toString(),
style: isCurrStream
? currStyle
: const TextStyle(fontSize: 14),
),
children: format.$2.codec.indexed.map((codec) {
final e = codec.$2;
final isCurrCodec =
isCurrFormat &&
codec.$1 == controller.codecIndex;
final codecColor = isCurrCodec
children: stream.$2.format.indexed.map((format) {
final isCurrFormat =
isCurrStream &&
format.$1 == controller.formatIndex;
final formatColor = isCurrFormat
? secondary
: onSurfaceVariant;
return _ExpansionTile(
initiallyExpanded: isCurrCodec,
iconColor: codecColor,
collapsedIconColor: codecColor,
initiallyExpanded: isCurrFormat,
iconColor: formatColor,
collapsedIconColor: formatColor,
title: Text(
'${e.codecName ?? codec.$1.toString()} (${LiveQuality.fromCode(e.currentQn)?.desc ?? e.currentQn})',
style: isCurrCodec
format.$2.formatName ?? format.$1.toString(),
style: isCurrFormat
? currStyle
: const TextStyle(fontSize: 14),
),
children: e.urlInfo.indexed.map((url) {
final isCurrUrl =
(isCurrCodec &&
url.$1 == controller.liveUrlIndex);
return ListTile(
dense: true,
children: format.$2.codec.indexed.map((codec) {
final e = codec.$2;
final isCurrCodec =
isCurrFormat &&
codec.$1 == controller.codecIndex;
final codecColor = isCurrCodec
? secondary
: onSurfaceVariant;
return _ExpansionTile(
initiallyExpanded: isCurrCodec,
iconColor: codecColor,
collapsedIconColor: codecColor,
title: Text(
'${url.$2.host}${e.baseUrl}...',
style: isCurrUrl
? const TextStyle(fontSize: 14)
: TextStyle(
fontSize: 14,
color: onSurfaceVariant,
),
'${e.codecName ?? codec.$1.toString()} (${LiveQuality.fromCode(e.currentQn)?.desc ?? e.currentQn})',
style: isCurrCodec
? currStyle
: const TextStyle(fontSize: 14),
),
selected: isCurrUrl,
onTap: isCurrUrl
? null
: () {
Get.back();
controller.initLiveUrl(
streamIndex: stream.$1,
formatIndex: format.$1,
codecIndex: codec.$1,
liveUrlIndex: url.$1,
);
},
children: e.urlInfo.indexed.map((url) {
final isCurrUrl =
(isCurrCodec &&
url.$1 == controller.liveUrlIndex);
return ListTile(
dense: true,
title: Text(
'${url.$2.host}${e.baseUrl}...',
style: isCurrUrl
? const TextStyle(fontSize: 14)
: TextStyle(
fontSize: 14,
color: onSurfaceVariant,
),
),
selected: isCurrUrl,
onTap: isCurrUrl
? null
: () {
Get.back();
controller.initLiveUrl(
streamIndex: stream.$1,
formatIndex: format.$1,
codecIndex: codec.$1,
liveUrlIndex: url.$1,
);
},
);
}).toList(),
);
}).toList(),
);
}).toList(),
);
}).toList(),
);
}),
),
),
],
),
);

View File

@@ -37,7 +37,7 @@ class _GeetestWebviewDialogState extends State<GeetestWebviewDialog> {
Webview? _linuxWebview;
late bool _linuxWebviewLoading = true;
String _showJs(String response) =>
static String _showJs(String response) =>
't=Geetest($response).onSuccess(()=>R("success",t.getValidate())).onError(o=>R("error",o)).onClose(o=>R("close",o));t.onReady(()=>t.verify())';
@override

View File

@@ -1,5 +1,3 @@
import 'dart:ui';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/dial_prefix.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
@@ -68,17 +66,16 @@ class _LoginPageState extends State<LoginPage> {
TextButton.icon(
onPressed: () async {
SmartDialog.showLoading(msg: '正在生成截图');
RenderRepaintBoundary boundary =
globalKey.currentContext!.findRenderObject()!
final boundary =
globalKey.currentContext!.findRenderObject()
as RenderRepaintBoundary;
final image = await boundary.toImage(pixelRatio: 3);
ByteData? byteData = await image.toByteData(
format: ImageByteFormat.png,
);
Uint8List pngBytes = byteData!.buffer.asUint8List();
final byteData = await image.toByteData(format: .png);
final pngBytes = byteData!.buffer.asUint8List();
image.dispose();
SmartDialog.dismiss();
String picName =
"${Constants.appName}_loginQRCode_${ImageUtils.time}";
final picName =
"${Constants.appName}_loginQRCode_${_loginPageCtr.codeInfo.value.data.authCode.hashCode.toUnsigned(32).toRadixString(16)}";
ImageUtils.saveByteImg(bytes: pngBytes, fileName: picName);
},
icon: const Icon(Icons.save),

View File

@@ -475,9 +475,10 @@ class _EditProfilePageState extends State<EditProfilePage> {
Future<void> _pickImg(ThemeData theme) async {
try {
XFile? pickedFile = await _imagePicker.pickImage(
final pickedFile = await _imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 100,
requestFullMetadata: false,
);
if (pickedFile != null && mounted) {
String? imagePath = pickedFile.path;

View File

@@ -88,7 +88,7 @@ class _OrderedMultiSelectDialogState<T>
),
TextButton(
onPressed: () {
assert(_tempValues.values.isSorted((a, b) => a.compareTo(b)));
assert(_tempValues.values.isSorted(Comparable.compare));
Get.back(result: _tempValues.keys.toList());
},
child: const Text('确定'),

View File

@@ -644,7 +644,7 @@ class VideoDetailController extends GetxController
await Get.key.currentState!.push(
PublishRoute(
pageBuilder: (buildContext, animation, secondaryAnimation) {
return SendDanmakuPanel(
final child = SendDanmakuPanel(
cid: cid.value,
bvid: bvid,
progress: plPlayerController.position.inMilliseconds,
@@ -654,10 +654,13 @@ class VideoDetailController extends GetxController
savedDanmaku = null;
plPlayerController.danmakuController?.addDanmaku(danmakuModel);
},
darkVideoPage: plPlayerController.darkVideoPage,
dmConfig: dmConfig,
onSaveDmConfig: (dmConfig) => this.dmConfig = dmConfig,
);
if (plPlayerController.darkVideoPage) {
return Theme(data: ThemeUtils.darkTheme, child: child);
}
return child;
},
),
);
@@ -736,15 +739,10 @@ class VideoDetailController extends GetxController
}
Future<void> playerInit({
String? video,
String? audio,
Duration? seekToTime,
Duration? duration,
bool? autoplay,
Volume? volume,
bool autoFullScreenFlag = false,
}) async {
Duration? seek = seekToTime ?? defaultST ?? playedTime;
Duration? seek = defaultST ?? playedTime;
if (seek == null || seek == Duration.zero) {
seek = getFirstSegment();
}
@@ -757,15 +755,13 @@ class VideoDetailController extends GetxController
hasDashAudio: entry.hasDashAudio,
)
: NetworkSource(
videoSource: video ?? videoUrl!,
audioSource: audio ?? audioUrl,
videoSource: videoUrl!,
audioSource: audioUrl,
),
seekTo: seek,
duration:
duration ??
(data.timeLength == null
? null
: Duration(milliseconds: data.timeLength!)),
duration: data.timeLength == null
? null
: Duration(milliseconds: data.timeLength!),
isVertical: isVertical.value,
aid: aid,
bvid: bvid,
@@ -781,7 +777,7 @@ class VideoDetailController extends GetxController
},
width: firstVideo.width,
height: firstVideo.height,
volume: volume ?? this.volume,
volume: volume,
autoFullScreenFlag: autoFullScreenFlag,
);
@@ -815,14 +811,13 @@ class VideoDetailController extends GetxController
return;
}
currLang.value = language;
queryVideoUrl(defaultST: playedTime);
queryVideoUrl(fromReset: true);
}
Volume? volume;
// 视频链接
Future<void> queryVideoUrl({
Duration? defaultST,
bool fromReset = false,
bool autoFullScreenFlag = false,
}) async {
@@ -866,11 +861,13 @@ class VideoDetailController extends GetxController
volume = data.volume;
final progress = args.remove('progress');
if (progress != null) {
this.defaultST = Duration(milliseconds: progress);
} else if (defaultST == null && data.lastPlayTime != null) {
this.defaultST = Duration(milliseconds: data.lastPlayTime!);
if (!fromReset) {
final progress = args.remove('progress');
if (progress != null) {
defaultST = Duration(milliseconds: progress);
} else {
defaultST = Duration(milliseconds: data.lastPlayTime);
}
}
if (!isUgc && !fromReset && plPlayerController.enablePgcSkip) {
@@ -1028,22 +1025,16 @@ class VideoDetailController extends GetxController
);
}
if (plPlayerController.isFullScreen.value || showVideoSheet) {
final child = PostPanel(
enableSlide: false,
videoDetailController: this,
plPlayerController: plPlayerController,
);
PageUtils.showVideoBottomSheet(
context,
child: plPlayerController.darkVideoPage
? Theme(
data: ThemeUtils.darkTheme,
child: PostPanel(
enableSlide: false,
videoDetailController: this,
plPlayerController: plPlayerController,
),
)
: PostPanel(
enableSlide: false,
videoDetailController: this,
plPlayerController: plPlayerController,
),
? Theme(data: ThemeUtils.darkTheme, child: child)
: child,
);
} else {
childKey.currentState?.showBottomSheet(
@@ -1355,26 +1346,18 @@ class VideoDetailController extends GetxController
).videoDetail.value.title;
} catch (_) {}
if (plPlayerController.isFullScreen.value || showVideoSheet) {
final child = NoteListPage(
oid: aid,
enableSlide: false,
heroTag: heroTag,
isStein: graphVersion != null,
title: title,
);
PageUtils.showVideoBottomSheet(
context,
child: plPlayerController.darkVideoPage
? Theme(
data: ThemeUtils.darkTheme,
child: NoteListPage(
oid: aid,
enableSlide: false,
heroTag: heroTag,
isStein: graphVersion != null,
title: title,
),
)
: NoteListPage(
oid: aid,
enableSlide: false,
heroTag: heroTag,
isStein: graphVersion != null,
title: title,
),
? Theme(data: ThemeUtils.darkTheme, child: child)
: child,
);
} else {
childKey.currentState?.showBottomSheet(

View File

@@ -44,6 +44,7 @@ class ActionItem extends StatelessWidget {
selectStatus ? selectIcon!.icon! : icon.icon,
size: 18,
color: selectStatus ? primary : icon.color ?? colorScheme.outline,
semanticLabel: semanticsLabel,
);
if (animation != null) {
@@ -53,11 +54,8 @@ class ActionItem extends StatelessWidget {
children: [
AnimatedBuilder(
animation: animation!,
builder: (context, child) => Arc(
size: 28,
color: primary,
progress: -animation!.value,
),
builder: (context, child) =>
Arc(size: 28, color: primary, progress: -animation!.value),
),
child,
],
@@ -69,7 +67,7 @@ class ActionItem extends StatelessWidget {
child = Material(
type: .transparency,
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(6)),
borderRadius: const .all(.circular(6)),
onTap: _isThumbsUp ? null : onTap,
onLongPress: _isThumbsUp ? null : onLongPress,
onSecondaryTap: PlatformUtils.isMobile || _isThumbsUp
@@ -104,9 +102,8 @@ class ActionItem extends StatelessWidget {
if (hasText) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
transitionBuilder: (child, animation) =>
ScaleTransition(scale: animation, child: child),
child: child,
);
}

View File

@@ -203,17 +203,16 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel>
}
return _header(theme, firstFloor);
}),
_sortWidget(theme),
_sortWidget(theme.colorScheme),
],
Obx(() => _buildBody(theme, _controller.loadingState.value)),
Obx(
() => _buildBody(theme.colorScheme, _controller.loadingState.value),
),
],
),
);
if (widget.isNested) {
return ExtendedVisibilityDetector(
uniqueKey: Key(_tag),
child: child,
);
return ExtendedVisibilityDetector(uniqueKey: Key(_tag), child: child);
}
return child;
}
@@ -243,9 +242,9 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel>
);
}
Widget _sortWidget(ThemeData theme) {
Widget _sortWidget(ColorScheme colorScheme) {
return SliverPinnedHeader(
backgroundColor: theme.colorScheme.surface,
backgroundColor: colorScheme.surface,
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 2.5, 6, 2.5),
child: Row(
@@ -265,18 +264,11 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel>
TextButton.icon(
style: Style.buttonStyle,
onPressed: _controller.queryBySort,
icon: Icon(
Icons.sort,
size: 16,
color: theme.colorScheme.secondary,
),
icon: Icon(Icons.sort, size: 16, color: colorScheme.secondary),
label: Obx(
() => Text(
_controller.sortType.value.text!,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.secondary,
),
style: TextStyle(fontSize: 13, color: colorScheme.secondary),
),
),
),
@@ -287,7 +279,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel>
}
Widget _buildBody(
ThemeData theme,
ColorScheme colorScheme,
LoadingState<List<ReplyInfo>?> loadingState,
) {
final jumpIndex = _controller.index.value;
@@ -305,15 +297,13 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel>
return Container(
height: 125,
alignment: Alignment.center,
margin: EdgeInsets.only(
bottom: MediaQuery.viewPaddingOf(context).bottom,
),
margin: .only(bottom: MediaQuery.viewPaddingOf(context).bottom),
child: Text(
_controller.isEnd ? '没有更多了' : '加载中...',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.outline,
color: colorScheme.outline,
),
),
);
@@ -323,13 +313,10 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel>
return ColoredBoxTransition(
color: _colorAnimation ??= _controller.animController.drive(
ColorTween(
begin: theme.colorScheme.onInverseSurface,
end: theme.colorScheme.surface,
).chain(
CurveTween(
curve: const Interval(0.8, 1.0), // 前0.8s不变, 后0.2s开始动画
),
),
begin: colorScheme.onInverseSurface,
end: colorScheme.surface,
// 前0.8s不变, 后0.2s开始动画
).chain(CurveTween(curve: const Interval(0.8, 1.0))),
),
child: child,
);

View File

@@ -10,7 +10,6 @@ import 'package:PiliPlus/pages/danmaku/danmaku_model.dart';
import 'package:PiliPlus/pages/setting/slide_color_picker.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:canvas_danmaku/models/danmaku_content_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter;
@@ -24,7 +23,6 @@ class SendDanmakuPanel extends CommonTextPubPage {
final dynamic progress;
final ValueChanged<DanmakuContentItem<DanmakuExtra>> onSuccess;
final bool darkVideoPage;
// config
final ({int? mode, int? fontSize, Color? color})? dmConfig;
@@ -38,7 +36,6 @@ class SendDanmakuPanel extends CommonTextPubPage {
this.bvid,
this.progress,
required this.onSuccess,
required this.darkVideoPage,
this.dmConfig,
this.onSaveDmConfig,
});
@@ -138,14 +135,14 @@ class _SendDanmakuPanelState extends CommonTextPubPageState<SendDanmakuPanel> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
themeData = widget.darkVideoPage ? ThemeUtils.darkTheme : Theme.of(context);
themeData = Theme.of(context);
}
late ThemeData themeData;
@override
Widget build(BuildContext context) {
Widget child = ViewSafeArea(
return ViewSafeArea(
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
@@ -164,7 +161,6 @@ class _SendDanmakuPanelState extends CommonTextPubPageState<SendDanmakuPanel> {
),
),
);
return widget.darkVideoPage ? Theme(data: themeData, child: child) : child;
}
@override

View File

@@ -79,11 +79,7 @@ class VideoDetailPageV extends StatefulWidget {
}
class _VideoDetailPageVState extends State<VideoDetailPageV>
with
TickerProviderStateMixin,
RouteAware,
RouteAwareMixin,
WidgetsBindingObserver {
with RouteAware, RouteAwareMixin, WidgetsBindingObserver {
final heroTag = Get.arguments['heroTag'];
late final VideoDetailController videoDetailController;
@@ -1360,7 +1356,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
if (videoDetailController.tabCtr.length != tabs.length) {
videoDetailController.tabCtr.dispose();
videoDetailController.tabCtr = TabController(
vsync: this,
vsync: videoDetailController,
length: tabs.length,
initialIndex: tabs.isEmpty
? 0
@@ -1614,9 +1610,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
return FilledButton.tonal(
style: FilledButton.styleFrom(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(6),
),
borderRadius: .all(.circular(6)),
),
backgroundColor: themeData
.colorScheme
@@ -1634,9 +1628,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
item,
isStein: true,
);
videoDetailController.getSteinEdgeInfo(
item.id,
);
videoDetailController.getSteinEdgeInfo(item.id);
},
child: Text(item.option!),
);
@@ -1991,14 +1983,12 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
},
);
if (isFullScreen || videoDetailController.showVideoSheet) {
final child = listSheetContent(enableSlide: false);
PageUtils.showVideoBottomSheet(
context,
child: videoDetailController.plPlayerController.darkVideoPage
? Theme(
data: themeData,
child: listSheetContent(enableSlide: false),
)
: listSheetContent(enableSlide: false),
? Theme(data: themeData, child: child)
: child,
);
} else {
videoDetailController.childKey.currentState?.showBottomSheet(
@@ -2070,22 +2060,16 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
void showViewPoints() {
if (isFullScreen || videoDetailController.showVideoSheet) {
final child = ViewPointsPage(
enableSlide: false,
videoDetailController: videoDetailController,
plPlayerController: plPlayerController,
);
PageUtils.showVideoBottomSheet(
context,
child: videoDetailController.plPlayerController.darkVideoPage
? Theme(
data: themeData,
child: ViewPointsPage(
enableSlide: false,
videoDetailController: videoDetailController,
plPlayerController: plPlayerController,
),
)
: ViewPointsPage(
enableSlide: false,
videoDetailController: videoDetailController,
plPlayerController: plPlayerController,
),
? Theme(data: themeData, child: child)
: child,
);
} else {
videoDetailController.childKey.currentState?.showBottomSheet(

View File

@@ -449,10 +449,7 @@ class HeaderControlState extends State<HeaderControl>
dense: true,
onTap: () {
Get.back();
videoDetailCtr.queryVideoUrl(
defaultST: videoDetailCtr.playedTime,
fromReset: true,
);
videoDetailCtr.queryVideoUrl(fromReset: true);
},
leading: const Icon(Icons.refresh_outlined, size: 20),
title: const Text('重载视频', style: titleStyle),
@@ -518,10 +515,7 @@ class HeaderControlState extends State<HeaderControl>
VideoUtils.cdnService = result;
setting.put(SettingBoxKey.CDNService, result.name);
SmartDialog.showToast('已设置为 ${result.desc},正在重载视频');
videoDetailCtr.queryVideoUrl(
defaultST: videoDetailCtr.playedTime,
fromReset: true,
);
videoDetailCtr.queryVideoUrl(fromReset: true);
}
},
),
@@ -1974,9 +1968,7 @@ class HeaderControlState extends State<HeaderControl>
FontAwesomeIcons.thumbsUp,
color: Colors.white,
),
selectIcon: const Icon(
FontAwesomeIcons.solidThumbsUp,
),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
selectStatus: introController.hasLike.value,
semanticsLabel: '点赞',
animation: introController.tripleAnimation,

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/plugin/pl_player/controller.dart';
import 'package:PiliPlus/plugin/pl_player/utils/danmaku_options.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -12,6 +13,13 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
bool get isFullScreen => plPlayerController.isFullScreen.value;
ThemeData? get theme {
if (plPlayerController.darkVideoPage) {
return ThemeUtils.darkTheme;
}
return null;
}
Future<void>? showBottomSheet(
StatefulWidgetBuilder builder, {
ValueGetter<EdgeInsets>? padding,
@@ -21,12 +29,16 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
maxWidth: 512,
padding: padding,
child: StatefulBuilder(
builder: (context, setState) => plPlayerController.darkVideoPage
? Theme(
data: Theme.of(this.context),
child: builder(this.context, setState),
)
: builder(context, setState),
builder: (context, setState) {
final theme = this.theme;
if (theme != null) {
return Theme(
data: theme,
child: builder(this.context, setState),
);
}
return builder(context, setState);
},
),
);
}

View File

@@ -344,9 +344,10 @@ class _WhisperDetailPageState
);
} else {
try {
final XFile? pickedFile = await imagePicker.pickImage(
final pickedFile = await imagePicker.pickImage(
source: ImageSource.gallery,
imageQuality: 100,
requestFullMetadata: false,
);
if (pickedFile != null) {
final path = pickedFile.path;

View File

@@ -36,6 +36,7 @@ import 'package:PiliPlus/utils/android/android_helper.dart';
import 'package:PiliPlus/utils/android/bindings.g.dart';
import 'package:PiliPlus/utils/asset_utils.dart';
import 'package:PiliPlus/utils/device_utils.dart';
import 'package:PiliPlus/utils/duration_utils.dart';
import 'package:PiliPlus/utils/extension/box_ext.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
@@ -46,7 +47,6 @@ import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:archive/archive.dart' show getCrc32;
import 'package:canvas_danmaku/canvas_danmaku.dart';
@@ -772,16 +772,12 @@ class PlPlayerController with BlockConfigMixin {
assert(_videoPlayerController == null);
final opt = {
'video-sync': Pref.videoSync,
if (Platform.isAndroid) 'ao': Pref.audioOutput,
'volume':
(PlatformUtils.isMobile ? Pref.playerVolume : volume.value * 100)
.toString(),
'volume-max': kMaxVolume.toString(),
};
if (Platform.isAndroid) {
opt['ao'] = Pref.audioOutput;
}
if (PlatformUtils.isMobile) {
opt['volume'] = Pref.playerVolume.toString();
} else {
opt['volume'] = (volume.value * 100).toString();
}
opt['volume-max'] = kMaxVolume.toString();
final autosync = Pref.autosync;
if (autosync != '0') {
opt['autosync'] = autosync;
@@ -1737,6 +1733,9 @@ class PlPlayerController with BlockConfigMixin {
Future<void> takeScreenshot() async {
SmartDialog.showToast('截图中');
final time = DurationUtils.formatDuration(
position.inMilliseconds / 1000,
).replaceAll(':', '-');
final image = await videoPlayerController?.screenshot();
if (image != null) {
SmartDialog.showToast('点击弹窗保存截图');
@@ -1748,7 +1747,7 @@ class PlPlayerController with BlockConfigMixin {
if (bytes != null) {
ImageUtils.saveByteImg(
bytes: bytes.buffer.asUint8List(),
fileName: 'screenshot_${ImageUtils.time}',
fileName: 'screenshot_${cid}_$time',
);
}
Get.back();
@@ -1765,7 +1764,7 @@ class PlPlayerController with BlockConfigMixin {
decoration: BoxDecoration(
border: Border.all(
width: 5,
color: ThemeUtils.theme.colorScheme.surface,
color: ColorScheme.of(context).surface,
),
),
child: Padding(

View File

@@ -25,8 +25,6 @@ import 'package:PiliPlus/models/common/super_resolution_type.dart';
import 'package:PiliPlus/models/common/video/video_quality.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/models_new/video/video_detail/episode.dart' as ugc;
import 'package:PiliPlus/models_new/video/video_detail/episode.dart';
import 'package:PiliPlus/models_new/video/video_detail/section.dart';
import 'package:PiliPlus/models_new/video/video_detail/ugc_season.dart';
import 'package:PiliPlus/pages/common/common_intro_controller.dart';
import 'package:PiliPlus/pages/danmaku/danmaku_model.dart';
@@ -579,9 +577,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
String bvid = plPlayerController.bvid;
List<ugc.BaseEpisodeItem> episodes = [];
if (isSeason) {
final List<SectionItem> sections = videoDetail.ugcSeason!.sections!;
final sections = videoDetail.ugcSeason!.sections!;
for (int i = 0; i < sections.length; i++) {
final List<EpisodeItem> episodesList = sections[i].episodes!;
final episodesList = sections[i].episodes!;
for (final item in episodesList) {
if (item.cid == currentCid) {
index = i;
@@ -811,18 +809,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (videoInfo.dash == null) {
return const SizedBox.shrink();
}
final List<FormatItem> videoFormat = videoInfo.supportFormats!;
final int totalQaSam = videoFormat.length;
int usefulQaSam = 0;
final List<VideoItem> video = videoInfo.dash!.video!;
final Set<int> idSet = {};
for (final VideoItem item in video) {
final int id = item.id!;
if (!idSet.contains(id)) {
idSet.add(id);
usefulQaSam++;
}
}
final videoFormat = videoInfo.supportFormats!;
final totalQaSam = videoFormat.length;
final usefulQaSam = videoInfo.dash!.video!
.map((i) => i.id)
.toSet()
.length;
return PopupMenuButton<int>(
tooltip: '画质',
requestFocus: false,
@@ -892,16 +884,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
height: 30,
tooltip: isFullScreen ? '退出全屏' : '全屏',
icon: isFullScreen
? const Icon(
Icons.fullscreen_exit,
size: 24,
color: Colors.white,
)
: const Icon(
Icons.fullscreen,
size: 24,
color: Colors.white,
),
? const Icon(Icons.fullscreen_exit, size: 24, color: Colors.white)
: const Icon(Icons.fullscreen, size: 24, color: Colors.white),
onTap: () =>
plPlayerController.triggerFullScreen(status: !isFullScreen),
onSecondaryTap: () => plPlayerController.triggerFullScreen(
@@ -914,29 +898,24 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
final isNotFileSource = !plPlayerController.isFileSource;
List<BottomControlType> userSpecifyItemLeft = [
BottomControlType.playOrPause,
BottomControlType.time,
if (!isNotFileSource || anySeason) ...[
BottomControlType.pre,
BottomControlType.next,
],
.playOrPause,
.time,
if (!isNotFileSource || anySeason) ...[.pre, .next],
];
final flag =
isFullScreen || plPlayerController.isDesktopPip || maxWidth >= 500;
List<BottomControlType> userSpecifyItemRight = [
if (isNotFileSource && plPlayerController.showDmChart)
BottomControlType.dmChart,
if (plPlayerController.isAnim) BottomControlType.superResolution,
if (isNotFileSource && plPlayerController.showViewPoints)
BottomControlType.viewPoints,
if (isNotFileSource && anySeason) BottomControlType.episode,
if (flag) BottomControlType.fit,
if (isNotFileSource) BottomControlType.aiTranslate,
BottomControlType.subtitle,
BottomControlType.speed,
if (isNotFileSource && flag) BottomControlType.qa,
if (!plPlayerController.isDesktopPip) BottomControlType.fullscreen,
final List<BottomControlType> userSpecifyItemRight = [
if (isNotFileSource && plPlayerController.showDmChart) .dmChart,
if (plPlayerController.isAnim) .superResolution,
if (isNotFileSource && plPlayerController.showViewPoints) .viewPoints,
if (isNotFileSource && anySeason) .episode,
if (flag) .fit,
if (isNotFileSource) .aiTranslate,
.subtitle,
.speed,
if (isNotFileSource && flag) .qa,
if (!plPlayerController.isDesktopPip) .fullscreen,
];
return PlayerBar(
children: [
@@ -1058,21 +1037,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
displayTime: const Duration(milliseconds: 1500),
maskColor: Colors.transparent,
builder: (context) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
padding: const .symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(6),
),
borderRadius: const .all(.circular(6)),
color: colorScheme.secondaryContainer,
),
child: Text(
'松开手指,取消进退',
style: TextStyle(
color: colorScheme.onSecondaryContainer,
),
style: TextStyle(color: colorScheme.onSecondaryContainer),
),
),
);

View File

@@ -165,7 +165,7 @@ class ShutdownTimerService {
const Center(child: Text('定时关闭', style: titleStyle)),
const SizedBox(height: 10),
...{...scheduleTimeMinutes, _durationInMinutes}
.sorted((a, b) => a.compareTo(b))
.sorted(Comparable.compare)
.map(
(minutes) => ListTile(
dense: true,

View File

@@ -20,14 +20,11 @@ import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:intl/intl.dart' show DateFormat;
import 'package:live_photo_maker/live_photo_maker.dart';
import 'package:saver_gallery/saver_gallery.dart';
import 'package:share_plus/share_plus.dart';
abstract final class ImageUtils {
static String get time =>
DateFormat('yyyy-MM-dd_HH-mm-ss').format(DateTime.now());
static bool silentDownImg = Pref.silentDownImg;
static final _albumPath = Platform.isAndroid
? 'Pictures/${Constants.appName}'