mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-07-07 01:39:18 +08:00
163
lib/common/widgets/cropped_image.dart
Normal file
163
lib/common/widgets/cropped_image.dart
Normal file
@@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
117
lib/common/widgets/custom_arc.dart
Normal file
117
lib/common/widgets/custom_arc.dart
Normal file
@@ -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;
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import 'dart:ui' show clampDouble;
|
|||||||
|
|
||||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
enum TooltipType { top, right }
|
enum TooltipType { top, right }
|
||||||
|
|
||||||
@@ -261,19 +261,83 @@ class _CustomMultiTooltipPositionDelegate extends MultiChildLayoutDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrianglePainter extends CustomPainter {
|
class Triangle extends LeafRenderObjectWidget {
|
||||||
TrianglePainter(this.color, {this.type = TooltipType.top});
|
const Triangle({
|
||||||
final TooltipType type;
|
super.key,
|
||||||
|
required this.color,
|
||||||
|
required this.size,
|
||||||
|
this.type = .top,
|
||||||
|
});
|
||||||
|
|
||||||
final Color color;
|
final Color color;
|
||||||
|
final Size size;
|
||||||
|
final TooltipType type;
|
||||||
|
|
||||||
@override
|
@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()
|
final paint = Paint()
|
||||||
..color = color
|
..color = color
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
Path path;
|
Path path;
|
||||||
switch (type) {
|
switch (_type) {
|
||||||
case TooltipType.top:
|
case TooltipType.top:
|
||||||
path = Path()
|
path = Path()
|
||||||
..moveTo(0, 0)
|
..moveTo(0, 0)
|
||||||
@@ -288,11 +352,11 @@ class TrianglePainter extends CustomPainter {
|
|||||||
..close();
|
..close();
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.drawPath(path, paint);
|
context.canvas.drawPath(path, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(TrianglePainter oldDelegate) => color != oldDelegate.color;
|
bool get isRepaintBoundary => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Offset positionDependentBox({
|
Offset positionDependentBox({
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
class DisabledIcon<T extends Widget> extends SingleChildRenderObjectWidget {
|
class DisabledIcon<T extends Widget> extends SingleChildRenderObjectWidget {
|
||||||
final Color? color;
|
|
||||||
final double lineLengthScale;
|
|
||||||
final StrokeCap strokeCap;
|
|
||||||
|
|
||||||
const DisabledIcon({
|
const DisabledIcon({
|
||||||
super.key,
|
super.key,
|
||||||
required T child,
|
required T child,
|
||||||
@@ -18,27 +14,70 @@ class DisabledIcon<T extends Widget> extends SingleChildRenderObjectWidget {
|
|||||||
strokeCap = strokeCap ?? StrokeCap.butt,
|
strokeCap = strokeCap ?? StrokeCap.butt,
|
||||||
super(child: child);
|
super(child: child);
|
||||||
|
|
||||||
|
final Color? color;
|
||||||
|
final StrokeCap strokeCap;
|
||||||
|
final double lineLengthScale;
|
||||||
|
|
||||||
|
T enable() => child as T;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderObject createRenderObject(BuildContext context) {
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
return RenderMaskedIcon(
|
return RenderMaskedIcon(
|
||||||
|
color:
|
||||||
color ??
|
color ??
|
||||||
(child is Icon
|
(child is Icon
|
||||||
? (child as Icon).color ?? IconTheme.of(context).color!
|
? (child as Icon).color ?? IconTheme.of(context).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 {
|
class RenderMaskedIcon extends RenderProxyBox {
|
||||||
final Color color;
|
RenderMaskedIcon({
|
||||||
final double lineLengthScale;
|
required Color color,
|
||||||
final StrokeCap strokeCap;
|
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
|
@override
|
||||||
void paint(PaintingContext context, Offset offset) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
@@ -77,7 +116,7 @@ class RenderMaskedIcon extends RenderProxyBox {
|
|||||||
..clipPath(path, doAntiAlias: false);
|
..clipPath(path, doAntiAlias: false);
|
||||||
super.paint(context, offset);
|
super.paint(context, offset);
|
||||||
|
|
||||||
context.canvas.restore();
|
canvas.restore();
|
||||||
|
|
||||||
final linePaint = Paint()
|
final linePaint = Paint()
|
||||||
..color = color
|
..color = color
|
||||||
@@ -94,6 +133,9 @@ class RenderMaskedIcon extends RenderProxyBox {
|
|||||||
linePaint,
|
linePaint,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isRepaintBoundary => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DisabledIconExt on Icon {
|
extension DisabledIconExt on Icon {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:math';
|
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:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
@@ -30,19 +30,14 @@ class LoadingWidget extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
//loading animation
|
//loading animation
|
||||||
RepaintBoundary.wrap(
|
|
||||||
Obx(
|
Obx(
|
||||||
() => CustomPaint(
|
() => Arc(
|
||||||
size: const Size.square(40),
|
size: 40,
|
||||||
painter: ArcPainter(
|
|
||||||
color: onSurfaceVariant,
|
color: onSurfaceVariant,
|
||||||
strokeWidth: 3,
|
strokeWidth: 3,
|
||||||
sweepAngle: progress.value * 2 * pi,
|
sweepAngle: progress.value * 2 * pi,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
//msg
|
//msg
|
||||||
Text(msg, style: TextStyle(color: onSurfaceVariant)),
|
Text(msg, style: TextStyle(color: onSurfaceVariant)),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -104,9 +104,9 @@ class _EmotePanelState extends State<EmotePanel>
|
|||||||
);
|
);
|
||||||
if (!isTextEmote) {
|
if (!isTextEmote) {
|
||||||
child = CustomTooltip(
|
child = CustomTooltip(
|
||||||
indicator: () => CustomPaint(
|
indicator: () => Triangle(
|
||||||
|
color: color,
|
||||||
size: const Size(14, 8),
|
size: const Size(14, 8),
|
||||||
painter: TrianglePainter(color),
|
|
||||||
),
|
),
|
||||||
overlayWidget: () => Container(
|
overlayWidget: () => Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
|
|||||||
@@ -105,9 +105,9 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: CustomTooltip(
|
child: CustomTooltip(
|
||||||
indicator: () => CustomPaint(
|
indicator: () => Triangle(
|
||||||
|
color: color,
|
||||||
size: const Size(14, 8),
|
size: const Size(14, 8),
|
||||||
painter: TrianglePainter(color),
|
|
||||||
),
|
),
|
||||||
overlayWidget: () => Container(
|
overlayWidget: () => Container(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
|
|||||||
@@ -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/extension/theme_ext.dart';
|
||||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -52,18 +51,14 @@ class ActionItem extends StatelessWidget {
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
RepaintBoundary(
|
AnimatedBuilder(
|
||||||
child: AnimatedBuilder(
|
|
||||||
animation: animation!,
|
animation: animation!,
|
||||||
builder: (context, child) => CustomPaint(
|
builder: (context, child) => Arc(
|
||||||
size: const Size.square(28),
|
size: 28,
|
||||||
painter: ArcPainter(
|
|
||||||
color: primary,
|
color: primary,
|
||||||
sweepAngle: animation!.value,
|
sweepAngle: animation!.value,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
child,
|
child,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -115,40 +110,3 @@ class ActionItem extends StatelessWidget {
|
|||||||
return child;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'dart:math' as math;
|
|||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:PiliPlus/common/constants.dart';
|
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/custom_icon.dart';
|
||||||
import 'package:PiliPlus/common/widgets/gesture/immediate_tap_gesture_recognizer.dart';
|
import 'package:PiliPlus/common/widgets/gesture/immediate_tap_gesture_recognizer.dart';
|
||||||
import 'package:PiliPlus/common/widgets/gesture/mouse_interactive_viewer.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/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart' show RenderProxyBox;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@@ -2352,9 +2354,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
return Positioned(
|
return Positioned(
|
||||||
right: right,
|
right: right,
|
||||||
top: top,
|
top: top,
|
||||||
child: RepaintBoundary(
|
child: _DanmakuTip(
|
||||||
child: CustomPaint(
|
offset: triangleOffset,
|
||||||
painter: _DanmakuTipPainter(offset: triangleOffset),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
@@ -2478,7 +2479,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2661,6 +2661,7 @@ Future<ui.Image?> _loadImg(String path) async {
|
|||||||
|
|
||||||
class _VideoShotImageState extends State<VideoShotImage> {
|
class _VideoShotImageState extends State<VideoShotImage> {
|
||||||
late Size _size;
|
late Size _size;
|
||||||
|
late Rect _srcRect;
|
||||||
late Rect _dstRect;
|
late Rect _dstRect;
|
||||||
late RRect _rrect;
|
late RRect _rrect;
|
||||||
ui.Image? _image;
|
ui.Image? _image;
|
||||||
@@ -2686,14 +2687,17 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
|||||||
final height = widget.height;
|
final height = widget.height;
|
||||||
final width = height * imgXSize / imgYSize;
|
final width = height * imgXSize / imgYSize;
|
||||||
_setRect(width, height);
|
_setRect(width, height);
|
||||||
|
_setSrcRect(imgXSize, imgYSize);
|
||||||
widget.onSetSize(imgXSize, imgYSize);
|
widget.onSetSize(imgXSize, imgYSize);
|
||||||
} else {
|
} else {
|
||||||
_setRect(double.nan, double.nan);
|
_setRect(double.nan, double.nan);
|
||||||
|
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final height = widget.height;
|
final height = widget.height;
|
||||||
final width = height * widget.imgXSize / widget.imgYSize;
|
final width = height * widget.imgXSize / widget.imgYSize;
|
||||||
_setRect(width, height);
|
_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));
|
_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() {
|
void _loadImg() {
|
||||||
final url = widget.url;
|
final url = widget.url;
|
||||||
_image = widget.imageCache[url];
|
_image = widget.imageCache[url];
|
||||||
@@ -2733,6 +2746,9 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
|||||||
if (oldWidget.url != widget.url) {
|
if (oldWidget.url != widget.url) {
|
||||||
_loadImg();
|
_loadImg();
|
||||||
}
|
}
|
||||||
|
if (oldWidget.x != widget.x || oldWidget.y != widget.y) {
|
||||||
|
_setSrcRect(widget.imgXSize, widget.imgYSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
|
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
|
||||||
@@ -2744,101 +2760,56 @@ class _VideoShotImageState extends State<VideoShotImage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_image != null) {
|
if (_image != null) {
|
||||||
return RepaintBoundary(
|
return CroppedImage(
|
||||||
child: CustomPaint(
|
size: _size,
|
||||||
painter: _CroppedImagePainter(
|
|
||||||
image: _image!,
|
image: _image!,
|
||||||
x: widget.x,
|
srcRect: _srcRect,
|
||||||
y: widget.y,
|
|
||||||
imgXSize: widget.imgXSize,
|
|
||||||
imgYSize: widget.imgYSize,
|
|
||||||
dstRect: _dstRect,
|
dstRect: _dstRect,
|
||||||
rrect: _rrect,
|
rrect: _rrect,
|
||||||
imgPaint: _imgPaint,
|
imgPaint: _imgPaint,
|
||||||
borderPaint: _borderPaint,
|
borderPaint: _borderPaint,
|
||||||
),
|
|
||||||
size: _size,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
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;
|
const double _triangleHeight = 5.6;
|
||||||
|
|
||||||
class _DanmakuTipPainter extends CustomPainter {
|
class _DanmakuTip extends SingleChildRenderObjectWidget {
|
||||||
|
const _DanmakuTip({
|
||||||
|
this.offset = 0,
|
||||||
|
super.child,
|
||||||
|
});
|
||||||
|
|
||||||
final double offset;
|
final double offset;
|
||||||
|
|
||||||
const _DanmakuTipPainter({this.offset = 0});
|
@override
|
||||||
|
RenderObject createRenderObject(BuildContext context) {
|
||||||
|
return RenderDanmakuTip(offset: offset);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
void paint(Canvas canvas, Size size) {
|
void paint(PaintingContext context, Offset offset) {
|
||||||
final paint = Paint()
|
final paint = Paint()
|
||||||
..color = const Color(0xB3000000)
|
..color = const Color(0xB3000000)
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
@@ -2851,7 +2822,7 @@ class _DanmakuTipPainter extends CustomPainter {
|
|||||||
final radius = size.height / 2;
|
final radius = size.height / 2;
|
||||||
const triangleBase = _triangleHeight * 2 / 3;
|
const triangleBase = _triangleHeight * 2 / 3;
|
||||||
|
|
||||||
final triangleCenterX = (size.width / 2 + offset).clamp(
|
final triangleCenterX = (size.width / 2 + _offset).clamp(
|
||||||
radius + triangleBase,
|
radius + triangleBase,
|
||||||
size.width - radius - triangleBase,
|
size.width - radius - triangleBase,
|
||||||
);
|
);
|
||||||
@@ -2876,12 +2847,13 @@ class _DanmakuTipPainter extends CustomPainter {
|
|||||||
)
|
)
|
||||||
..close();
|
..close();
|
||||||
|
|
||||||
canvas
|
context.canvas
|
||||||
..drawPath(path, paint)
|
..drawPath(path, paint)
|
||||||
..drawPath(path, strokePaint);
|
..drawPath(path, strokePaint);
|
||||||
|
|
||||||
|
super.paint(context, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool shouldRepaint(covariant _DanmakuTipPainter oldDelegate) =>
|
bool get isRepaintBoundary => true;
|
||||||
oldDelegate.offset != offset;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user