mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-13 22:28:08 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2536350ccf | ||
|
|
3fae4ef632 | ||
|
|
b02e4253f1 | ||
|
|
199ad274b7 |
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -10,9 +10,7 @@ abstract final class ViewGrpc {
|
||||
}) {
|
||||
return GrpcReq.request(
|
||||
GrpcUrl.view,
|
||||
ViewReq(
|
||||
bvid: bvid,
|
||||
),
|
||||
ViewReq(bvid: bvid),
|
||||
ViewReply.fromBuffer,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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('确定'),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}'
|
||||
|
||||
Reference in New Issue
Block a user