diff --git a/lib/common/widgets/cropped_image.dart b/lib/common/widgets/cropped_image.dart
new file mode 100644
index 000000000..6cb61ad75
--- /dev/null
+++ b/lib/common/widgets/cropped_image.dart
@@ -0,0 +1,163 @@
+/*
+ * This file is part of PiliPlus
+ *
+ * PiliPlus is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PiliPlus is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PiliPlus. If not, see .
+ */
+
+import 'dart:ui' as ui;
+
+import 'package:flutter/widgets.dart';
+
+class CroppedImage extends LeafRenderObjectWidget {
+ const CroppedImage({
+ super.key,
+ required this.size,
+ required this.image,
+ required this.srcRect,
+ required this.dstRect,
+ required this.rrect,
+ required this.imgPaint,
+ required this.borderPaint,
+ });
+
+ final Size size;
+ final ui.Image image;
+ final Rect srcRect;
+ final Rect dstRect;
+ final RRect rrect;
+ final Paint imgPaint;
+ final Paint borderPaint;
+
+ @override
+ RenderObject createRenderObject(BuildContext context) {
+ return RenderCroppedImage(
+ preferredSize: size,
+ image: image,
+ srcRect: srcRect,
+ dstRect: dstRect,
+ rrect: rrect,
+ imgPaint: imgPaint,
+ borderPaint: borderPaint,
+ );
+ }
+
+ @override
+ void updateRenderObject(
+ BuildContext context,
+ RenderCroppedImage renderObject,
+ ) {
+ renderObject
+ ..preferredSize = size
+ ..image = image
+ ..srcRect = srcRect
+ ..dstRect = dstRect
+ ..rrect = rrect
+ ..imgPaint = imgPaint
+ ..borderPaint = borderPaint;
+ }
+}
+
+class RenderCroppedImage extends RenderBox {
+ RenderCroppedImage({
+ required Size preferredSize,
+ required ui.Image image,
+ required Rect srcRect,
+ required Rect dstRect,
+ required RRect rrect,
+ required Paint imgPaint,
+ required Paint borderPaint,
+ }) : _preferredSize = preferredSize,
+ _image = image,
+ _srcRect = srcRect,
+ _dstRect = dstRect,
+ _rrect = rrect,
+ _imgPaint = imgPaint,
+ _borderPaint = borderPaint;
+
+ Size _preferredSize;
+ Size get preferredSize => _preferredSize;
+ set preferredSize(Size value) {
+ if (_preferredSize == value) return;
+ _preferredSize = value;
+ markNeedsLayout();
+ }
+
+ ui.Image _image;
+ ui.Image get image => _image;
+ set image(ui.Image value) {
+ if (_image == value) return;
+ _image = value;
+ markNeedsPaint();
+ }
+
+ Rect _srcRect;
+ Rect get srcRect => _srcRect;
+ set srcRect(Rect value) {
+ if (_srcRect == value) return;
+ _srcRect = value;
+ markNeedsPaint();
+ }
+
+ Rect _dstRect;
+ Rect get dstRect => _dstRect;
+ set dstRect(Rect value) {
+ if (_dstRect == value) return;
+ _dstRect = value;
+ markNeedsPaint();
+ }
+
+ RRect _rrect;
+ RRect get rrect => _rrect;
+ set rrect(RRect value) {
+ if (_rrect == value) return;
+ _rrect = value;
+ markNeedsPaint();
+ }
+
+ Paint _imgPaint;
+ Paint get imgPaint => _imgPaint;
+ set imgPaint(Paint value) {
+ if (_imgPaint == value) return;
+ _imgPaint = value;
+ markNeedsPaint();
+ }
+
+ Paint _borderPaint;
+ Paint get borderPaint => _borderPaint;
+ set borderPaint(Paint value) {
+ if (_borderPaint == value) return;
+ _borderPaint = value;
+ markNeedsPaint();
+ }
+
+ @override
+ void performLayout() {
+ size = computeDryLayout(constraints);
+ }
+
+ @override
+ Size computeDryLayout(BoxConstraints constraints) {
+ return constraints.constrain(_preferredSize);
+ }
+
+ @override
+ void paint(PaintingContext context, Offset offset) {
+ context.canvas
+ ..drawImageRect(image, srcRect, dstRect, _imgPaint)
+ ..drawRRect(rrect, _borderPaint);
+ }
+
+ @override
+ bool get isRepaintBoundary => true;
+}
diff --git a/lib/common/widgets/custom_arc.dart b/lib/common/widgets/custom_arc.dart
new file mode 100644
index 000000000..4244851b7
--- /dev/null
+++ b/lib/common/widgets/custom_arc.dart
@@ -0,0 +1,117 @@
+import 'dart:math' show pi;
+
+import 'package:flutter/widgets.dart';
+
+class Arc extends LeafRenderObjectWidget {
+ const Arc({
+ super.key,
+ required this.size,
+ required this.color,
+ required this.sweepAngle,
+ this.strokeWidth = 2,
+ });
+
+ final double size;
+ final Color color;
+ final double sweepAngle;
+ final double strokeWidth;
+
+ @override
+ RenderObject createRenderObject(BuildContext context) {
+ return RenderArc(
+ size: size,
+ color: color,
+ sweepAngle: sweepAngle,
+ strokeWidth: strokeWidth,
+ );
+ }
+
+ @override
+ void updateRenderObject(
+ BuildContext context,
+ RenderArc renderObject,
+ ) {
+ renderObject
+ ..color = color
+ ..sweepAngle = sweepAngle
+ ..strokeWidth = strokeWidth;
+ }
+}
+
+class RenderArc extends RenderBox {
+ RenderArc({
+ required double size,
+ required Color color,
+ required double sweepAngle,
+ required double strokeWidth,
+ }) : _preferredSize = Size.square(size),
+ _color = color,
+ _sweepAngle = sweepAngle,
+ _strokeWidth = strokeWidth;
+
+ Color _color;
+ Color get color => _color;
+ set color(Color value) {
+ if (_color == value) return;
+ _color = value;
+ markNeedsPaint();
+ }
+
+ double _sweepAngle;
+ double get sweepAngle => _sweepAngle;
+ set sweepAngle(double value) {
+ if (_sweepAngle == value) return;
+ _sweepAngle = value;
+ markNeedsPaint();
+ }
+
+ double _strokeWidth;
+ double get strokeWidth => _strokeWidth;
+ set strokeWidth(double value) {
+ if (_strokeWidth == value) return;
+ _strokeWidth = value;
+ markNeedsPaint();
+ }
+
+ Size _preferredSize;
+ set preferredSize(Size value) {
+ if (_preferredSize == value) return;
+ _preferredSize = value;
+ markNeedsLayout();
+ }
+
+ @override
+ void performLayout() {
+ size = computeDryLayout(constraints);
+ }
+
+ @override
+ Size computeDryLayout(BoxConstraints constraints) {
+ return constraints.constrain(_preferredSize);
+ }
+
+ @override
+ void paint(PaintingContext context, Offset offset) {
+ if (sweepAngle == 0) {
+ return;
+ }
+
+ final paint = Paint()
+ ..color = color
+ ..strokeWidth = strokeWidth
+ ..style = PaintingStyle.stroke;
+
+ final size = this.size;
+ final rect = Rect.fromCircle(
+ center: Offset(size.width / 2, size.height / 2),
+ radius: size.width / 2,
+ );
+
+ const startAngle = -pi / 2;
+
+ context.canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
+ }
+
+ @override
+ bool get isRepaintBoundary => true;
+}
diff --git a/lib/common/widgets/custom_tooltip.dart b/lib/common/widgets/custom_tooltip.dart
index cbf7cf678..31a227051 100644
--- a/lib/common/widgets/custom_tooltip.dart
+++ b/lib/common/widgets/custom_tooltip.dart
@@ -3,7 +3,7 @@ import 'dart:ui' show clampDouble;
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
enum TooltipType { top, right }
@@ -261,19 +261,83 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
}
}
-class TrianglePainter extends CustomPainter {
- TrianglePainter(this.color, {this.type = TooltipType.top});
- final TooltipType type;
+class Triangle extends LeafRenderObjectWidget {
+ const Triangle({
+ super.key,
+ required this.color,
+ required this.size,
+ this.type = .top,
+ });
+
final Color color;
+ final Size size;
+ final TooltipType type;
@override
- void paint(Canvas canvas, Size size) {
+ RenderObject createRenderObject(BuildContext context) {
+ return RenderTriangle(
+ color: color,
+ size: size,
+ type: type,
+ );
+ }
+
+ @override
+ void updateRenderObject(
+ BuildContext context,
+ RenderTriangle renderObject,
+ ) {
+ renderObject
+ ..color = color
+ ..preferredSize = size;
+ }
+}
+
+class RenderTriangle extends RenderBox {
+ RenderTriangle({
+ required Color color,
+ required Size size,
+ required TooltipType type,
+ }) : _color = color,
+ _preferredSize = size,
+ _type = type;
+
+ Color _color;
+ Color get color => _color;
+ set color(Color value) {
+ if (_color == value) return;
+ _color = value;
+ markNeedsPaint();
+ }
+
+ Size _preferredSize;
+ set preferredSize(Size value) {
+ if (_preferredSize == value) return;
+ _preferredSize = value;
+ markNeedsLayout();
+ }
+
+ final TooltipType _type;
+
+ @override
+ void performLayout() {
+ size = computeDryLayout(constraints);
+ }
+
+ @override
+ Size computeDryLayout(BoxConstraints constraints) {
+ return constraints.constrain(_preferredSize);
+ }
+
+ @override
+ void paint(PaintingContext context, Offset offset) {
+ final size = this.size;
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
Path path;
- switch (type) {
+ switch (_type) {
case TooltipType.top:
path = Path()
..moveTo(0, 0)
@@ -288,11 +352,11 @@ class TrianglePainter extends CustomPainter {
..close();
}
- canvas.drawPath(path, paint);
+ context.canvas.drawPath(path, paint);
}
@override
- bool shouldRepaint(TrianglePainter oldDelegate) => color != oldDelegate.color;
+ bool get isRepaintBoundary => true;
}
Offset positionDependentBox({
diff --git a/lib/common/widgets/disabled_icon.dart b/lib/common/widgets/disabled_icon.dart
index f51e95ffb..97d32f9d5 100644
--- a/lib/common/widgets/disabled_icon.dart
+++ b/lib/common/widgets/disabled_icon.dart
@@ -4,10 +4,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class DisabledIcon extends SingleChildRenderObjectWidget {
- final Color? color;
- final double lineLengthScale;
- final StrokeCap strokeCap;
-
const DisabledIcon({
super.key,
required T child,
@@ -18,27 +14,70 @@ class DisabledIcon extends SingleChildRenderObjectWidget {
strokeCap = strokeCap ?? StrokeCap.butt,
super(child: child);
+ final Color? color;
+ final StrokeCap strokeCap;
+ final double lineLengthScale;
+
+ T enable() => child as T;
+
@override
RenderObject createRenderObject(BuildContext context) {
return RenderMaskedIcon(
- color ??
+ color:
+ color ??
(child is Icon
? (child as Icon).color ?? IconTheme.of(context).color!
: IconTheme.of(context).color!),
- lineLengthScale,
- strokeCap,
+ strokeCap: strokeCap,
+ lineLengthScale: lineLengthScale,
);
}
- T enable() => child as T;
+ @override
+ void updateRenderObject(BuildContext context, RenderMaskedIcon renderObject) {
+ renderObject
+ ..color =
+ color ??
+ (child is Icon
+ ? (child as Icon).color ?? IconTheme.of(context).color!
+ : IconTheme.of(context).color!)
+ ..strokeCap = strokeCap
+ ..lineLengthScale = lineLengthScale;
+ }
}
class RenderMaskedIcon extends RenderProxyBox {
- final Color color;
- final double lineLengthScale;
- final StrokeCap strokeCap;
+ RenderMaskedIcon({
+ required Color color,
+ required StrokeCap strokeCap,
+ required double lineLengthScale,
+ }) : _color = color,
+ _strokeCap = strokeCap,
+ _lineLengthScale = lineLengthScale;
- RenderMaskedIcon(this.color, this.lineLengthScale, this.strokeCap);
+ Color _color;
+ Color get color => _color;
+ set color(Color value) {
+ if (_color == value) return;
+ _color = value;
+ markNeedsPaint();
+ }
+
+ StrokeCap _strokeCap;
+ StrokeCap get strokeCap => _strokeCap;
+ set strokeCap(StrokeCap value) {
+ if (_strokeCap == value) return;
+ _strokeCap = value;
+ markNeedsPaint();
+ }
+
+ double _lineLengthScale;
+ double get lineLengthScale => _lineLengthScale;
+ set lineLengthScale(double value) {
+ if (_lineLengthScale == value) return;
+ _lineLengthScale = value;
+ markNeedsPaint();
+ }
@override
void paint(PaintingContext context, Offset offset) {
@@ -77,7 +116,7 @@ class RenderMaskedIcon extends RenderProxyBox {
..clipPath(path, doAntiAlias: false);
super.paint(context, offset);
- context.canvas.restore();
+ canvas.restore();
final linePaint = Paint()
..color = color
@@ -94,6 +133,9 @@ class RenderMaskedIcon extends RenderProxyBox {
linePaint,
);
}
+
+ @override
+ bool get isRepaintBoundary => true;
}
extension DisabledIconExt on Icon {
diff --git a/lib/common/widgets/loading_widget.dart b/lib/common/widgets/loading_widget.dart
index 4605eec53..3897ffae2 100644
--- a/lib/common/widgets/loading_widget.dart
+++ b/lib/common/widgets/loading_widget.dart
@@ -1,6 +1,6 @@
import 'dart:math';
-import 'package:PiliPlus/pages/video/introduction/ugc/widgets/action_item.dart';
+import 'package:PiliPlus/common/widgets/custom_arc.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -30,18 +30,13 @@ class LoadingWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
//loading animation
- RepaintBoundary.wrap(
- Obx(
- () => CustomPaint(
- size: const Size.square(40),
- painter: ArcPainter(
- color: onSurfaceVariant,
- strokeWidth: 3,
- sweepAngle: progress.value * 2 * pi,
- ),
- ),
+ Obx(
+ () => Arc(
+ size: 40,
+ color: onSurfaceVariant,
+ strokeWidth: 3,
+ sweepAngle: progress.value * 2 * pi,
),
- 0,
),
//msg
Text(msg, style: TextStyle(color: onSurfaceVariant)),
diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart
index 1ea9d1813..ce1c5ad31 100644
--- a/lib/pages/emote/view.dart
+++ b/lib/pages/emote/view.dart
@@ -104,9 +104,9 @@ class _EmotePanelState extends State
);
if (!isTextEmote) {
child = CustomTooltip(
- indicator: () => CustomPaint(
+ indicator: () => Triangle(
+ color: color,
size: const Size(14, 8),
- painter: TrianglePainter(color),
),
overlayWidget: () => Container(
padding: const EdgeInsets.all(8),
diff --git a/lib/pages/live_emote/view.dart b/lib/pages/live_emote/view.dart
index a4dbe119a..3991ba6ee 100644
--- a/lib/pages/live_emote/view.dart
+++ b/lib/pages/live_emote/view.dart
@@ -105,9 +105,9 @@ class _LiveEmotePanelState extends State
}
},
child: CustomTooltip(
- indicator: () => CustomPaint(
+ indicator: () => Triangle(
+ color: color,
size: const Size(14, 8),
- painter: TrianglePainter(color),
),
overlayWidget: () => Container(
padding: const EdgeInsets.all(8),
diff --git a/lib/pages/video/introduction/ugc/widgets/action_item.dart b/lib/pages/video/introduction/ugc/widgets/action_item.dart
index 09ba3c304..1e8788ff5 100644
--- a/lib/pages/video/introduction/ugc/widgets/action_item.dart
+++ b/lib/pages/video/introduction/ugc/widgets/action_item.dart
@@ -1,5 +1,4 @@
-import 'dart:math' show pi;
-
+import 'package:PiliPlus/common/widgets/custom_arc.dart';
import 'package:PiliPlus/utils/extension/theme_ext.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:flutter/material.dart';
@@ -52,16 +51,12 @@ class ActionItem extends StatelessWidget {
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
- RepaintBoundary(
- child: AnimatedBuilder(
- animation: animation!,
- builder: (context, child) => CustomPaint(
- size: const Size.square(28),
- painter: ArcPainter(
- color: primary,
- sweepAngle: animation!.value,
- ),
- ),
+ AnimatedBuilder(
+ animation: animation!,
+ builder: (context, child) => Arc(
+ size: 28,
+ color: primary,
+ sweepAngle: animation!.value,
),
),
child,
@@ -115,40 +110,3 @@ class ActionItem extends StatelessWidget {
return child;
}
}
-
-class ArcPainter extends CustomPainter {
- const ArcPainter({
- required this.color,
- required this.sweepAngle,
- this.strokeWidth = 2,
- });
- final Color color;
- final double sweepAngle;
- final double strokeWidth;
-
- @override
- void paint(Canvas canvas, Size size) {
- if (sweepAngle == 0) {
- return;
- }
-
- final paint = Paint()
- ..color = color
- ..strokeWidth = strokeWidth
- ..style = PaintingStyle.stroke;
-
- final rect = Rect.fromCircle(
- center: Offset(size.width / 2, size.height / 2),
- radius: size.width / 2,
- );
-
- const startAngle = -pi / 2;
-
- canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
- }
-
- @override
- bool shouldRepaint(covariant ArcPainter oldDelegate) {
- return sweepAngle != oldDelegate.sweepAngle || color != oldDelegate.color;
- }
-}
diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart
index c3b8214f5..a5b1900ad 100644
--- a/lib/plugin/pl_player/view.dart
+++ b/lib/plugin/pl_player/view.dart
@@ -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
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 _loadImg(String path) async {
class _VideoShotImageState extends State {
late Size _size;
+ late Rect _srcRect;
late Rect _dstRect;
late RRect _rrect;
ui.Image? _image;
@@ -2686,14 +2687,17 @@ class _VideoShotImageState extends State {
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 {
_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 {
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 {
@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;
}