mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-02 17:18:13 +08:00
feat: tap danmaku (#1534)
This commit is contained in:
committed by
GitHub
parent
35b34cb2d4
commit
f6ca007815
@@ -65,6 +65,76 @@ class HeaderControl extends StatefulWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
State<HeaderControl> createState() => HeaderControlState();
|
State<HeaderControl> createState() => HeaderControlState();
|
||||||
|
|
||||||
|
static Future<bool> likeDanmaku(VideoDanmaku extra, int cid) async {
|
||||||
|
if (!Accounts.main.isLogin) {
|
||||||
|
SmartDialog.showToast('请先登录');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final res = await DanmakuHttp.danmakuLike(
|
||||||
|
isLike: extra.isLike,
|
||||||
|
cid: cid,
|
||||||
|
id: extra.id,
|
||||||
|
);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
extra.isLike = !extra.isLike;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
res.toast();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<bool> deleteDanmaku(int id, int cid) async {
|
||||||
|
final res = await DanmakuHttp.danmakuRecall(
|
||||||
|
cid: cid,
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
if (res.isSuccess) {
|
||||||
|
SmartDialog.showToast('删除成功');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
res.toast();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> reportDanmaku(
|
||||||
|
VideoDanmaku extra,
|
||||||
|
BuildContext context,
|
||||||
|
PlPlayerController ctr,
|
||||||
|
) {
|
||||||
|
if (Accounts.main.isLogin) {
|
||||||
|
return autoWrapReportDialog(
|
||||||
|
context,
|
||||||
|
ReportOptions.danmakuReport,
|
||||||
|
(reasonType, reasonDesc, banUid) {
|
||||||
|
if (banUid) {
|
||||||
|
final filter = ctr.filters;
|
||||||
|
if (filter.dmUid.add(extra.mid)) {
|
||||||
|
filter.count++;
|
||||||
|
GStorage.localCache.put(
|
||||||
|
LocalCacheKey.danmakuFilterRules,
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
DanmakuFilterHttp.danmakuFilterAdd(
|
||||||
|
filter: extra.mid,
|
||||||
|
type: 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return DanmakuHttp.danmakuReport(
|
||||||
|
reason: reasonType == 0 ? 11 : reasonType,
|
||||||
|
cid: ctr.cid!,
|
||||||
|
id: extra.id,
|
||||||
|
content: reasonDesc,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return SmartDialog.showToast('请先登录');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HeaderControlState extends State<HeaderControl> {
|
class HeaderControlState extends State<HeaderControl> {
|
||||||
@@ -1868,15 +1938,16 @@ class HeaderControlState extends State<HeaderControl> {
|
|||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
color: theme.colorScheme.surface,
|
color: theme.colorScheme.surface,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverPersistentHeader(
|
SliverPersistentHeader(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
delegate: CustomSliverPersistentHeaderDelegate(
|
delegate: CustomSliverPersistentHeaderDelegate(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 14,
|
||||||
|
vertical: 7,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -1897,7 +1968,6 @@ class HeaderControlState extends State<HeaderControl> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1913,83 +1983,43 @@ class HeaderControlState extends State<HeaderControl> {
|
|||||||
final extra = item.content.extra! as VideoDanmaku;
|
final extra = item.content.extra! as VideoDanmaku;
|
||||||
return ListTile(
|
return ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
contentPadding: const EdgeInsets.only(left: 6),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 14),
|
||||||
onLongPress: () => Utils.copyText(item.content.text),
|
onLongPress: () => Utils.copyText(item.content.text),
|
||||||
title: Text(item.content.text * 10),
|
title: Text(item.content.text),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) => IconButton(
|
builder: (context) => IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (!Accounts.main.isLogin) {
|
if (await HeaderControl.likeDanmaku(
|
||||||
SmartDialog.showToast('请先登录');
|
extra,
|
||||||
return;
|
plPlayerController.cid!,
|
||||||
}
|
) &&
|
||||||
final res = await DanmakuHttp.danmakuLike(
|
context.mounted) {
|
||||||
isLike: extra.isLike,
|
|
||||||
cid: plPlayerController.cid!,
|
|
||||||
id: extra.id,
|
|
||||||
);
|
|
||||||
if (res.isSuccess) {
|
|
||||||
extra.isLike = !extra.isLike;
|
|
||||||
if (context.mounted) {
|
|
||||||
(context as Element).markNeedsBuild();
|
(context as Element).markNeedsBuild();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
res.toast();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
icon: extra.isLike
|
icon: extra.isLike
|
||||||
? const Icon(Icons.thumb_up)
|
? const Icon(Icons.thumb_up_off_alt_sharp)
|
||||||
: const Icon(Icons.thumb_up_outlined),
|
: const Icon(Icons.thumb_up_off_alt_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (item.content.selfSend)
|
if (item.content.selfSend)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () => HeaderControl.deleteDanmaku(
|
||||||
final res = await DanmakuHttp.danmakuRecall(
|
extra.id,
|
||||||
cid: plPlayerController.cid!,
|
plPlayerController.cid!,
|
||||||
id: extra.id,
|
),
|
||||||
);
|
|
||||||
if (res.isSuccess) {
|
|
||||||
SmartDialog.showToast('删除成功');
|
|
||||||
} else {
|
|
||||||
res.toast();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.delete_outline),
|
icon: const Icon(Icons.delete_outline),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () => HeaderControl.reportDanmaku(
|
||||||
autoWrapReportDialog(
|
extra,
|
||||||
context,
|
context,
|
||||||
ReportOptions.danmakuReport,
|
plPlayerController,
|
||||||
(reasonType, reasonDesc, banUid) {
|
),
|
||||||
if (banUid) {
|
|
||||||
final filter = plPlayerController.filters;
|
|
||||||
if (filter.dmUid.add(extra.mid)) {
|
|
||||||
filter.count++;
|
|
||||||
GStorage.localCache.put(
|
|
||||||
LocalCacheKey.danmakuFilterRules,
|
|
||||||
filter,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DanmakuFilterHttp.danmakuFilterAdd(
|
|
||||||
filter: extra.mid,
|
|
||||||
type: 2,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return DanmakuHttp.danmakuReport(
|
|
||||||
reason: reasonType == 0 ? 11 : reasonType,
|
|
||||||
cid: plPlayerController.cid!,
|
|
||||||
id: extra.id,
|
|
||||||
content: reasonDesc,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.report_problem_outlined),
|
icon: const Icon(Icons.report_problem_outlined),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ import 'package:PiliPlus/models_new/video/video_detail/section.dart';
|
|||||||
import 'package:PiliPlus/models_new/video/video_detail/ugc_season.dart';
|
import 'package:PiliPlus/models_new/video/video_detail/ugc_season.dart';
|
||||||
import 'package:PiliPlus/models_new/video/video_shot/data.dart';
|
import 'package:PiliPlus/models_new/video/video_shot/data.dart';
|
||||||
import 'package:PiliPlus/pages/common/common_intro_controller.dart';
|
import 'package:PiliPlus/pages/common/common_intro_controller.dart';
|
||||||
|
import 'package:PiliPlus/pages/danmaku/dnamaku_model.dart';
|
||||||
import 'package:PiliPlus/pages/video/controller.dart';
|
import 'package:PiliPlus/pages/video/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart';
|
import 'package:PiliPlus/pages/video/introduction/pgc/controller.dart';
|
||||||
import 'package:PiliPlus/pages/video/post_panel/popup_menu_text.dart';
|
import 'package:PiliPlus/pages/video/post_panel/popup_menu_text.dart';
|
||||||
import 'package:PiliPlus/pages/video/post_panel/view.dart';
|
import 'package:PiliPlus/pages/video/post_panel/view.dart';
|
||||||
|
import 'package:PiliPlus/pages/video/widgets/header_control.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/models/bottom_control_type.dart';
|
import 'package:PiliPlus/plugin/pl_player/models/bottom_control_type.dart';
|
||||||
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
|
import 'package:PiliPlus/plugin/pl_player/models/bottom_progress_behavior.dart';
|
||||||
@@ -46,6 +48,7 @@ import 'package:PiliPlus/utils/image_utils.dart';
|
|||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/storage_key.dart';
|
import 'package:PiliPlus/utils/storage_key.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
|
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
@@ -1051,12 +1054,27 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
plPlayerController.triggerFullScreen(status: !isFullScreen);
|
plPlayerController.triggerFullScreen(status: !isFullScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTap(PointerDeviceKind? kind) {
|
void onTapUp(TapDownDetails? event) {
|
||||||
switch (kind) {
|
switch (event?.kind) {
|
||||||
case ui.PointerDeviceKind.mouse when Utils.isDesktop:
|
case ui.PointerDeviceKind.mouse when (!kDebugMode && Utils.isDesktop):
|
||||||
onTapDesktop();
|
onTapDesktop();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
if (kDebugMode || Utils.isMobile) {
|
||||||
|
final ctr = plPlayerController.danmakuController;
|
||||||
|
if (ctr != null) {
|
||||||
|
final item = ctr.findSingleDanmaku(event!.globalPosition);
|
||||||
|
if (item == null) {
|
||||||
|
if (_suspendedDM != null) {
|
||||||
|
_removeOverlay();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (item != _suspendedDM) {
|
||||||
|
_showOverlay(item, event, ctr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
plPlayerController.controls = !plPlayerController.showControls.value;
|
plPlayerController.controls = !plPlayerController.showControls.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1175,8 +1193,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final isMobile = Utils.isMobile;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
maxWidth = widget.maxWidth;
|
maxWidth = widget.maxWidth;
|
||||||
@@ -1223,12 +1239,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
!Utils.isDesktop && !plPlayerController.controlsLock.value,
|
!Utils.isDesktop && !plPlayerController.controlsLock.value,
|
||||||
enableShrinkVideoSize:
|
enableShrinkVideoSize:
|
||||||
!Utils.isDesktop && plPlayerController.enableShrinkVideoSize,
|
!Utils.isDesktop && plPlayerController.enableShrinkVideoSize,
|
||||||
onInteractionStart: _onInteractionStart,
|
onInteractionStart: _onInteractionStart, // TODO: refa gesture
|
||||||
onInteractionUpdate: _onInteractionUpdate,
|
onInteractionUpdate: _onInteractionUpdate,
|
||||||
onInteractionEnd: _onInteractionEnd,
|
onInteractionEnd: _onInteractionEnd,
|
||||||
flipX: plPlayerController.flipX.value,
|
flipX: plPlayerController.flipX.value,
|
||||||
flipY: plPlayerController.flipY.value,
|
flipY: plPlayerController.flipY.value,
|
||||||
onTap: onTap,
|
onTap: onTapUp,
|
||||||
onDoubleTapDown: onDoubleTapDown,
|
onDoubleTapDown: onDoubleTapDown,
|
||||||
onLongPressStart: isLive
|
onLongPressStart: isLive
|
||||||
? null
|
? null
|
||||||
@@ -1656,7 +1672,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isMobile)
|
if (Utils.isMobile)
|
||||||
buildViewPointWidget(
|
buildViewPointWidget(
|
||||||
videoDetailController,
|
videoDetailController,
|
||||||
plPlayerController,
|
plPlayerController,
|
||||||
@@ -1878,7 +1894,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (!isMobile) {
|
if (!kDebugMode && !Utils.isMobile) {
|
||||||
return Listener(
|
return Listener(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onPointerDown: onPointerDown,
|
onPointerDown: onPointerDown,
|
||||||
@@ -2045,6 +2061,154 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const overlaySpacing = 10.0;
|
||||||
|
static const overlayWidth = 130.0;
|
||||||
|
static const overlayHeight = 35.0;
|
||||||
|
|
||||||
|
DanmakuItem? _suspendedDM;
|
||||||
|
OverlayEntry? _overlayEntry;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void deactivate() {
|
||||||
|
_removeOverlay();
|
||||||
|
super.deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeOverlay() {
|
||||||
|
_suspendedDM?.suspend = false;
|
||||||
|
_suspendedDM = null;
|
||||||
|
_overlayEntry?.remove();
|
||||||
|
_overlayEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _overlayItem(Widget child, {required VoidCallback onTap}) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: onTap,
|
||||||
|
child: SizedBox(
|
||||||
|
height: overlayHeight,
|
||||||
|
width: overlayWidth / 3,
|
||||||
|
child: Center(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showOverlay(
|
||||||
|
DanmakuItem<DanmakuExtra> item,
|
||||||
|
PositionedGestureDetails event,
|
||||||
|
DanmakuController<DanmakuExtra> ctr,
|
||||||
|
) {
|
||||||
|
_removeOverlay();
|
||||||
|
item.suspend = true;
|
||||||
|
_suspendedDM = item;
|
||||||
|
|
||||||
|
final dy = item.content.type == DanmakuItemType.bottom
|
||||||
|
? ctr.viewHeight - item.yPosition - item.height
|
||||||
|
: item.yPosition;
|
||||||
|
final extra = item.content.extra as VideoDanmaku;
|
||||||
|
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
Overlay.of(context).insert(
|
||||||
|
_overlayEntry = OverlayEntry(
|
||||||
|
builder: (context) {
|
||||||
|
return Positioned(
|
||||||
|
top: dy + item.height + 4,
|
||||||
|
left: clampDouble(
|
||||||
|
event.globalPosition.dx - overlayWidth / 2,
|
||||||
|
overlaySpacing,
|
||||||
|
ctr.viewWidth - overlayWidth - overlaySpacing,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
CustomPaint(
|
||||||
|
painter: _TrianglePainter(
|
||||||
|
theme.colorScheme.onSurface.withValues(alpha: 0.8),
|
||||||
|
),
|
||||||
|
size: const Size(12, 6),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: overlayWidth,
|
||||||
|
height: overlayHeight,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.onSurface.withValues(alpha: 0.8),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(18)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_overlayItem(
|
||||||
|
Icon(
|
||||||
|
size: 20,
|
||||||
|
extra.isLike
|
||||||
|
? Icons.thumb_up_off_alt_sharp
|
||||||
|
: Icons.thumb_up_off_alt_outlined,
|
||||||
|
color: theme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_removeOverlay();
|
||||||
|
HeaderControl.likeDanmaku(
|
||||||
|
extra,
|
||||||
|
plPlayerController.cid!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_overlayItem(
|
||||||
|
Icon(
|
||||||
|
size: 20,
|
||||||
|
Icons.copy,
|
||||||
|
color: theme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_removeOverlay();
|
||||||
|
Utils.copyText(item.content.text);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (item.content.selfSend)
|
||||||
|
_overlayItem(
|
||||||
|
Icon(
|
||||||
|
size: 20,
|
||||||
|
Icons.delete,
|
||||||
|
color: theme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_removeOverlay();
|
||||||
|
HeaderControl.deleteDanmaku(
|
||||||
|
extra.id,
|
||||||
|
plPlayerController.cid!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_overlayItem(
|
||||||
|
Icon(
|
||||||
|
size: 20,
|
||||||
|
Icons.report_problem_outlined,
|
||||||
|
color: theme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_removeOverlay();
|
||||||
|
HeaderControl.reportDanmaku(
|
||||||
|
extra,
|
||||||
|
context,
|
||||||
|
plPlayerController,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildDmChart(
|
Widget buildDmChart(
|
||||||
@@ -2389,3 +2553,27 @@ Widget buildViewPointWidget(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TrianglePainter extends CustomPainter {
|
||||||
|
const _TrianglePainter(this.color);
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = color
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
final path = Path()
|
||||||
|
..moveTo(0, size.height)
|
||||||
|
..lineTo(size.width, size.height)
|
||||||
|
..lineTo(size.width / 2, 0)
|
||||||
|
..close();
|
||||||
|
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _TrianglePainter oldDelegate) =>
|
||||||
|
color != oldDelegate.color;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user