mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 11:08:03 +08:00
@@ -4,6 +4,7 @@ import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/cropped_image.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_icon.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/immediate_tap_gesture_recognizer.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/mouse_interactive_viewer.dart';
|
||||
@@ -63,6 +64,7 @@ import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart' show RenderProxyBox;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -2352,131 +2354,129 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
return Positioned(
|
||||
right: right,
|
||||
top: top,
|
||||
child: RepaintBoundary(
|
||||
child: CustomPaint(
|
||||
painter: _DanmakuTipPainter(offset: triangleOffset),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: switch (extra) {
|
||||
null => throw UnimplementedError(),
|
||||
VideoDanmaku() => [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
_dmActionItem(
|
||||
extra.isLike
|
||||
? const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_like_solid,
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_like,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => HeaderControl.likeDanmaku(
|
||||
extra,
|
||||
plPlayerController.cid!,
|
||||
),
|
||||
),
|
||||
if (extra.like > 0)
|
||||
Positioned(
|
||||
left: _actionItemWidth - 10.5,
|
||||
top: 0,
|
||||
child: Text(
|
||||
extra.like.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 10.5,
|
||||
child: _DanmakuTip(
|
||||
offset: triangleOffset,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: switch (extra) {
|
||||
null => throw UnimplementedError(),
|
||||
VideoDanmaku() => [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
_dmActionItem(
|
||||
extra.isLike
|
||||
? const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_like_solid,
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_like,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 19,
|
||||
CustomIcons.player_dm_tip_copy,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => Utils.copyText(item.content.text),
|
||||
),
|
||||
if (item.content.selfSend)
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_recall,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => HeaderControl.deleteDanmaku(
|
||||
extra.id,
|
||||
onTap: () => HeaderControl.likeDanmaku(
|
||||
extra,
|
||||
plPlayerController.cid!,
|
||||
),
|
||||
)
|
||||
else
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => HeaderControl.reportDanmaku(
|
||||
context,
|
||||
extra: extra,
|
||||
ctr: plPlayerController,
|
||||
),
|
||||
),
|
||||
if (seekOffset != null)
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 18,
|
||||
Icons.gps_fixed_outlined,
|
||||
color: Colors.white,
|
||||
if (extra.like > 0)
|
||||
Positioned(
|
||||
left: _actionItemWidth - 10.5,
|
||||
top: 0,
|
||||
child: Text(
|
||||
extra.like.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 10.5,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () => plPlayerController.seekTo(
|
||||
Duration(seconds: seekOffset),
|
||||
isSeek: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
LiveDanmaku() => [
|
||||
],
|
||||
),
|
||||
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 19,
|
||||
CustomIcons.player_dm_tip_copy,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => Utils.copyText(item.content.text),
|
||||
),
|
||||
if (item.content.selfSend)
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 20,
|
||||
MdiIcons.accountOutline,
|
||||
CustomIcons.player_dm_tip_recall,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => Get.toNamed('/member?mid=${extra.mid}'),
|
||||
),
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 19,
|
||||
CustomIcons.player_dm_tip_copy,
|
||||
color: Colors.white,
|
||||
onTap: () => HeaderControl.deleteDanmaku(
|
||||
extra.id,
|
||||
plPlayerController.cid!,
|
||||
),
|
||||
onTap: () => Utils.copyText(item.content.text),
|
||||
),
|
||||
)
|
||||
else
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => HeaderControl.reportLiveDanmaku(
|
||||
onTap: () => HeaderControl.reportDanmaku(
|
||||
context,
|
||||
roomId: (widget.bottomControl as live_bottom.BottomControl)
|
||||
.liveRoomCtr
|
||||
.roomId,
|
||||
msg: item.content.text,
|
||||
extra: extra,
|
||||
ctr: plPlayerController,
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
if (seekOffset != null)
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 18,
|
||||
Icons.gps_fixed_outlined,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => plPlayerController.seekTo(
|
||||
Duration(seconds: seekOffset),
|
||||
isSeek: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
LiveDanmaku() => [
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 20,
|
||||
MdiIcons.accountOutline,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => Get.toNamed('/member?mid=${extra.mid}'),
|
||||
),
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 19,
|
||||
CustomIcons.player_dm_tip_copy,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => Utils.copyText(item.content.text),
|
||||
),
|
||||
_dmActionItem(
|
||||
const Icon(
|
||||
size: 20,
|
||||
CustomIcons.player_dm_tip_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
onTap: () => HeaderControl.reportLiveDanmaku(
|
||||
context,
|
||||
roomId: (widget.bottomControl as live_bottom.BottomControl)
|
||||
.liveRoomCtr
|
||||
.roomId,
|
||||
msg: item.content.text,
|
||||
extra: extra,
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -2661,6 +2661,7 @@ Future<ui.Image?> _loadImg(String path) async {
|
||||
|
||||
class _VideoShotImageState extends State<VideoShotImage> {
|
||||
late Size _size;
|
||||
late Rect _srcRect;
|
||||
late Rect _dstRect;
|
||||
late RRect _rrect;
|
||||
ui.Image? _image;
|
||||
@@ -2686,14 +2687,17 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
final height = widget.height;
|
||||
final width = height * imgXSize / imgYSize;
|
||||
_setRect(width, height);
|
||||
_setSrcRect(imgXSize, imgYSize);
|
||||
widget.onSetSize(imgXSize, imgYSize);
|
||||
} else {
|
||||
_setRect(double.nan, double.nan);
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
} else {
|
||||
final height = widget.height;
|
||||
final width = height * widget.imgXSize / widget.imgYSize;
|
||||
_setRect(width, height);
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2703,6 +2707,15 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
_rrect = RRect.fromRectAndRadius(_dstRect, const Radius.circular(10));
|
||||
}
|
||||
|
||||
void _setSrcRect(double imgXSize, double imgYSize) {
|
||||
_srcRect = Rect.fromLTWH(
|
||||
widget.x * imgXSize,
|
||||
widget.y * imgYSize,
|
||||
imgXSize,
|
||||
imgYSize,
|
||||
);
|
||||
}
|
||||
|
||||
void _loadImg() {
|
||||
final url = widget.url;
|
||||
_image = widget.imageCache[url];
|
||||
@@ -2733,6 +2746,9 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
if (oldWidget.url != widget.url) {
|
||||
_loadImg();
|
||||
}
|
||||
if (oldWidget.x != widget.x || oldWidget.y != widget.y) {
|
||||
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||
}
|
||||
}
|
||||
|
||||
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
|
||||
@@ -2744,101 +2760,56 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_image != null) {
|
||||
return RepaintBoundary(
|
||||
child: CustomPaint(
|
||||
painter: _CroppedImagePainter(
|
||||
image: _image!,
|
||||
x: widget.x,
|
||||
y: widget.y,
|
||||
imgXSize: widget.imgXSize,
|
||||
imgYSize: widget.imgYSize,
|
||||
dstRect: _dstRect,
|
||||
rrect: _rrect,
|
||||
imgPaint: _imgPaint,
|
||||
borderPaint: _borderPaint,
|
||||
),
|
||||
size: _size,
|
||||
),
|
||||
return CroppedImage(
|
||||
size: _size,
|
||||
image: _image!,
|
||||
srcRect: _srcRect,
|
||||
dstRect: _dstRect,
|
||||
rrect: _rrect,
|
||||
imgPaint: _imgPaint,
|
||||
borderPaint: _borderPaint,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
||||
class _CroppedImagePainter extends CustomPainter {
|
||||
final ui.Image image;
|
||||
final Rect srcRect;
|
||||
final Rect dstRect;
|
||||
final RRect rrect;
|
||||
final Paint imgPaint;
|
||||
final Paint borderPaint;
|
||||
|
||||
_CroppedImagePainter({
|
||||
required this.image,
|
||||
required int x,
|
||||
required int y,
|
||||
required double imgXSize,
|
||||
required double imgYSize,
|
||||
required this.dstRect,
|
||||
required this.rrect,
|
||||
required this.imgPaint,
|
||||
required this.borderPaint,
|
||||
}) : srcRect = Rect.fromLTWH(x * imgXSize, y * imgYSize, imgXSize, imgYSize);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
canvas
|
||||
..drawImageRect(image, srcRect, dstRect, imgPaint)
|
||||
..drawRRect(rrect, borderPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_CroppedImagePainter oldDelegate) {
|
||||
return oldDelegate.image != image || oldDelegate.srcRect != srcRect;
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildViewPointWidget(
|
||||
VideoDetailController videoDetailController,
|
||||
PlPlayerController plPlayerController,
|
||||
double offset,
|
||||
double maxWidth,
|
||||
) {
|
||||
return Container(
|
||||
height: 16,
|
||||
margin: EdgeInsets.only(bottom: offset),
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPointerDown: (event) {
|
||||
try {
|
||||
double seg = event.localPosition.dx / maxWidth;
|
||||
Segment item = videoDetailController.viewPointList
|
||||
.where((item) => item.start >= seg)
|
||||
.reduce((a, b) => a.start < b.start ? a : b);
|
||||
if (item.from != null) {
|
||||
plPlayerController
|
||||
..danmakuController?.clear()
|
||||
..videoPlayerController?.seek(Duration(seconds: item.from!));
|
||||
}
|
||||
// if (kDebugMode) debugPrint('${item.title},,${item.from}');
|
||||
} catch (e) {
|
||||
if (kDebugMode) rethrow;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const double _triangleHeight = 5.6;
|
||||
|
||||
class _DanmakuTipPainter extends CustomPainter {
|
||||
class _DanmakuTip extends SingleChildRenderObjectWidget {
|
||||
const _DanmakuTip({
|
||||
this.offset = 0,
|
||||
super.child,
|
||||
});
|
||||
|
||||
final double offset;
|
||||
|
||||
const _DanmakuTipPainter({this.offset = 0});
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderDanmakuTip(offset: offset);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderDanmakuTip renderObject) {
|
||||
renderObject.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderDanmakuTip extends RenderProxyBox {
|
||||
RenderDanmakuTip({
|
||||
required double offset,
|
||||
}) : _offset = offset;
|
||||
|
||||
double _offset;
|
||||
double get offset => _offset;
|
||||
set offset(double value) {
|
||||
if (_offset == value) return;
|
||||
_offset = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final paint = Paint()
|
||||
..color = const Color(0xB3000000)
|
||||
..style = PaintingStyle.fill;
|
||||
@@ -2851,7 +2822,7 @@ class _DanmakuTipPainter extends CustomPainter {
|
||||
final radius = size.height / 2;
|
||||
const triangleBase = _triangleHeight * 2 / 3;
|
||||
|
||||
final triangleCenterX = (size.width / 2 + offset).clamp(
|
||||
final triangleCenterX = (size.width / 2 + _offset).clamp(
|
||||
radius + triangleBase,
|
||||
size.width - radius - triangleBase,
|
||||
);
|
||||
@@ -2876,12 +2847,13 @@ class _DanmakuTipPainter extends CustomPainter {
|
||||
)
|
||||
..close();
|
||||
|
||||
canvas
|
||||
context.canvas
|
||||
..drawPath(path, paint)
|
||||
..drawPath(path, strokePaint);
|
||||
|
||||
super.paint(context, offset);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _DanmakuTipPainter oldDelegate) =>
|
||||
oldDelegate.offset != offset;
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user