mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
opt dm action
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
Binary file not shown.
1
assets/images/dm_tip/player_dm_tip_center.svg
Normal file
1
assets/images/dm_tip/player_dm_tip_center.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" data-pointer="none" viewBox="0 0 162 48"><path fill="#000" fill-opacity=".703" fill-rule="evenodd" d="M1 27.075C1 16.07 9.92 7.149 20.925 7.149h55.91L81.522 1l4.741 6.15h54.812C152.079 7.15 161 16.07 161 27.074 161 38.079 152.079 47 141.075 47H20.925C9.921 47 1 38.08 1 27.075Z" clip-rule="evenodd"></path><path stroke="#fff" stroke-linejoin="round" stroke-opacity=".496" d="M81.918.695a.5.5 0 0 0-.794.002l-4.536 5.952H20.925C9.645 6.65.5 15.794.5 27.075.5 38.355 9.645 47.5 20.925 47.5h120.15c11.28 0 20.425-9.145 20.425-20.425 0-11.281-9.145-20.426-20.425-20.426H86.509L81.918.695Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 651 B |
1
assets/images/dm_tip/player_dm_tip_left.svg
Normal file
1
assets/images/dm_tip/player_dm_tip_left.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" data-pointer="none" viewBox="0 0 145 42"><path fill="#000" fill-opacity=".703" fill-rule="evenodd" d="M1 23.562c0-9.63 7.807-17.438 17.438-17.438h4.372L26.65 1l3.887 5.124h96.025c9.631 0 17.438 7.808 17.438 17.438C144 33.192 136.193 41 126.562 41H18.438C8.808 41 1 33.193 1 23.562Z" clip-rule="evenodd"></path><path stroke="#fff" stroke-linejoin="round" stroke-opacity=".496" d="M27.05.698a.5.5 0 0 0-.8.002l-3.69 4.924h-4.122C8.53 5.624.5 13.655.5 23.562.5 33.47 8.531 41.5 18.438 41.5h108.124c9.907 0 17.938-8.031 17.938-17.938 0-9.907-8.031-17.938-17.938-17.938H30.785L27.05.698Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 649 B |
1
assets/images/dm_tip/player_dm_tip_right.svg
Normal file
1
assets/images/dm_tip/player_dm_tip_right.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" data-pointer="none" version="1.1" viewBox="0 0 145 42" xmlns="http://www.w3.org/2000/svg"><path d="m144 23.562c0-9.63-7.807-17.438-17.438-17.438h-4.372l-3.84-5.124-3.887 5.124h-96.025c-9.631 0-17.438 7.808-17.438 17.438s7.807 17.438 17.438 17.438h108.12c9.63 0 17.438-7.807 17.438-17.438z" clip-rule="evenodd" fill="#000" fill-opacity=".703" fill-rule="evenodd"/><path d="m117.95 0.698a0.5 0.5 0 0 1 0.8 2e-3l3.69 4.924h4.122c9.908 0 17.938 8.031 17.938 17.938 0 9.908-8.031 17.938-17.938 17.938h-108.12c-9.907 0-17.938-8.031-17.938-17.938s8.031-17.938 17.938-17.938h95.777z" stroke="#fff" stroke-linejoin="round" stroke-opacity=".496"/></svg>
|
||||||
|
After Width: | Height: | Size: 660 B |
@@ -10,19 +10,24 @@ class CustomIcons {
|
|||||||
static const IconData dyn = _CustomIconData(0xe804);
|
static const IconData dyn = _CustomIconData(0xe804);
|
||||||
static const IconData fav = _CustomIconData(0xe805);
|
static const IconData fav = _CustomIconData(0xe805);
|
||||||
static const IconData live_reserve = _CustomIconData(0xe806);
|
static const IconData live_reserve = _CustomIconData(0xe806);
|
||||||
static const IconData share = _CustomIconData(0xe807);
|
static const IconData player_dm_tip_back = _CustomIconData(0xe807);
|
||||||
static const IconData share_line = _CustomIconData(0xe808);
|
static const IconData player_dm_tip_copy = _CustomIconData(0xe808);
|
||||||
static const IconData share_node = _CustomIconData(0xe809);
|
static const IconData player_dm_tip_like = _CustomIconData(0xe809);
|
||||||
static const IconData star_favorite_line = _CustomIconData(0xe80a);
|
static const IconData player_dm_tip_like_solid = _CustomIconData(0xe80a);
|
||||||
static const IconData star_favorite_solid = _CustomIconData(0xe80b);
|
static const IconData player_dm_tip_recall = _CustomIconData(0xe80b);
|
||||||
static const IconData thumbs_down = _CustomIconData(0xe80c);
|
static const IconData share = _CustomIconData(0xe80c);
|
||||||
static const IconData thumbs_down_outline = _CustomIconData(0xe80d);
|
static const IconData share_line = _CustomIconData(0xe80d);
|
||||||
static const IconData thumbs_up = _CustomIconData(0xe80e);
|
static const IconData share_node = _CustomIconData(0xe80e);
|
||||||
static const IconData thumbs_up_fill = _CustomIconData(0xe80f);
|
static const IconData star_favorite_line = _CustomIconData(0xe80f);
|
||||||
static const IconData thumbs_up_line = _CustomIconData(0xe810);
|
static const IconData star_favorite_solid = _CustomIconData(0xe810);
|
||||||
static const IconData thumbs_up_outline = _CustomIconData(0xe811);
|
static const IconData thumbs_down = _CustomIconData(0xe811);
|
||||||
static const IconData topic_tag = _CustomIconData(0xe812);
|
static const IconData thumbs_down_outline = _CustomIconData(0xe812);
|
||||||
static const IconData watch_later = _CustomIconData(0xe813);
|
static const IconData thumbs_up = _CustomIconData(0xe813);
|
||||||
|
static const IconData thumbs_up_fill = _CustomIconData(0xe814);
|
||||||
|
static const IconData thumbs_up_line = _CustomIconData(0xe815);
|
||||||
|
static const IconData thumbs_up_outline = _CustomIconData(0xe816);
|
||||||
|
static const IconData topic_tag = _CustomIconData(0xe817);
|
||||||
|
static const IconData watch_later = _CustomIconData(0xe818);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CustomIconData extends IconData {
|
class _CustomIconData extends IconData {
|
||||||
|
|||||||
159
lib/common/widgets/image/flutter_svg_provider.dart
Normal file
159
lib/common/widgets/image/flutter_svg_provider.dart
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' show rootBundle;
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
/// https://github.com/yang-f/flutter_svg_provider
|
||||||
|
|
||||||
|
/// Rasterizes given svg picture for displaying in [Image] widget:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// Image(
|
||||||
|
/// width: 32,
|
||||||
|
/// height: 32,
|
||||||
|
/// image: Svg('assets/my_icon.svg'),
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
class SvgImageProvider extends ImageProvider<SvgImageKey> {
|
||||||
|
/// Path to svg file or asset
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
/// Size in logical pixels to render.
|
||||||
|
/// Useful for [DecorationImage].
|
||||||
|
/// If not specified, will use size from [Image].
|
||||||
|
/// If [Image] not specifies size too, will use default size 100x100.
|
||||||
|
final Size? size;
|
||||||
|
|
||||||
|
/// Color to tint the SVG
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
/// Image scale.
|
||||||
|
final double? scale;
|
||||||
|
|
||||||
|
/// Width and height can also be specified from [Image] constructor.
|
||||||
|
/// Default size is 100x100 logical pixels.
|
||||||
|
/// Different size can be specified in [Image] parameters
|
||||||
|
const SvgImageProvider(
|
||||||
|
this.path, {
|
||||||
|
this.size,
|
||||||
|
this.scale,
|
||||||
|
this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SvgImageKey> obtainKey(ImageConfiguration configuration) {
|
||||||
|
final Color color = this.color ?? Colors.transparent;
|
||||||
|
final double scale = this.scale ?? configuration.devicePixelRatio ?? 1.0;
|
||||||
|
final double logicWidth = size?.width ?? configuration.size?.width ?? 100;
|
||||||
|
final double logicHeight =
|
||||||
|
size?.height ?? configuration.size?.height ?? 100;
|
||||||
|
|
||||||
|
return SynchronousFuture<SvgImageKey>(
|
||||||
|
SvgImageKey(
|
||||||
|
path: path,
|
||||||
|
scale: scale,
|
||||||
|
color: color,
|
||||||
|
pixelWidth: (logicWidth * scale).round(),
|
||||||
|
pixelHeight: (logicHeight * scale).round(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ImageStreamCompleter loadImage(SvgImageKey key, ImageDecoderCallback decode) {
|
||||||
|
return OneFrameImageStreamCompleter(
|
||||||
|
_loadAsync(key, getFilterColor(color)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<ImageInfo> _loadAsync(SvgImageKey key, Color color) async {
|
||||||
|
final rawSvg = await rootBundle.loadString(key.path);
|
||||||
|
final pictureInfo = await vg.loadPicture(
|
||||||
|
SvgStringLoader(rawSvg, theme: SvgTheme(currentColor: color)),
|
||||||
|
null,
|
||||||
|
clipViewbox: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final image = pictureInfo.picture.toImageSync(
|
||||||
|
pictureInfo.size.width.round(),
|
||||||
|
pictureInfo.size.height.round(),
|
||||||
|
);
|
||||||
|
return ImageInfo(image: image);
|
||||||
|
} finally {
|
||||||
|
// Dispose of the Picture to release resources
|
||||||
|
pictureInfo.picture.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: == and hashCode not overrided as changes in properties
|
||||||
|
// (width, height and scale) are not observable from the here.
|
||||||
|
// [SvgImageKey] instances will be compared instead.
|
||||||
|
@override
|
||||||
|
String toString() => '$runtimeType(${describeIdentity(path)})';
|
||||||
|
|
||||||
|
// Running on web with Colors.transparent may throws the exception `Expected a value of type 'SkDeletable', but got one of type 'Null'`.
|
||||||
|
static Color getFilterColor(Color? color) {
|
||||||
|
if (kIsWeb && color == Colors.transparent) {
|
||||||
|
return const Color(0x01ffffff);
|
||||||
|
} else {
|
||||||
|
return color ?? Colors.transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@immutable
|
||||||
|
class SvgImageKey {
|
||||||
|
const SvgImageKey({
|
||||||
|
required this.path,
|
||||||
|
required this.pixelWidth,
|
||||||
|
required this.pixelHeight,
|
||||||
|
required this.scale,
|
||||||
|
this.color,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Path to svg asset.
|
||||||
|
final String path;
|
||||||
|
|
||||||
|
/// Width in physical pixels.
|
||||||
|
/// Used when raterizing.
|
||||||
|
final int pixelWidth;
|
||||||
|
|
||||||
|
/// Height in physical pixels.
|
||||||
|
/// Used when raterizing.
|
||||||
|
final int pixelHeight;
|
||||||
|
|
||||||
|
/// Color to tint the SVG
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
/// Used to calculate logical size from physical, i.e.
|
||||||
|
/// logicalWidth = [pixelWidth] / [scale],
|
||||||
|
/// logicalHeight = [pixelHeight] / [scale].
|
||||||
|
/// Should be equal to [MediaQueryData.devicePixelRatio].
|
||||||
|
final double scale;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other.runtimeType != runtimeType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return other is SvgImageKey &&
|
||||||
|
other.path == path &&
|
||||||
|
other.pixelWidth == pixelWidth &&
|
||||||
|
other.pixelHeight == pixelHeight &&
|
||||||
|
other.scale == scale &&
|
||||||
|
other.color == color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
path,
|
||||||
|
pixelWidth,
|
||||||
|
pixelHeight,
|
||||||
|
scale,
|
||||||
|
color,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ List<SettingsModel> get playSettings => [
|
|||||||
const SettingsModel(
|
const SettingsModel(
|
||||||
settingsType: SettingsType.sw1tch,
|
settingsType: SettingsType.sw1tch,
|
||||||
title: '启用点击弹幕',
|
title: '启用点击弹幕',
|
||||||
|
subtitle: '点击弹幕悬停,支持点赞、复制、举报操作',
|
||||||
leading: Icon(Icons.touch_app_outlined),
|
leading: Icon(Icons.touch_app_outlined),
|
||||||
setKey: SettingBoxKey.enableTapDm,
|
setKey: SettingBoxKey.enableTapDm,
|
||||||
defaultVal: true,
|
defaultVal: true,
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ 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/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';
|
||||||
|
import 'package:PiliPlus/common/widgets/image/flutter_svg_provider.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||||
import 'package:PiliPlus/common/widgets/pair.dart';
|
import 'package:PiliPlus/common/widgets/pair.dart';
|
||||||
import 'package:PiliPlus/common/widgets/progress_bar/audio_video_progress_bar.dart';
|
import 'package:PiliPlus/common/widgets/progress_bar/audio_video_progress_bar.dart';
|
||||||
@@ -867,14 +869,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
void _onInteractionStart(ScaleStartDetails details) {
|
void _onInteractionStart(ScaleStartDetails details) {
|
||||||
if (plPlayerController.controlsLock.value) return;
|
if (plPlayerController.controlsLock.value) return;
|
||||||
// 如果起点太靠上则屏蔽
|
// 如果起点太靠上则屏蔽
|
||||||
if (details.localFocalPoint.dy < 40) return;
|
final localFocalPoint = details.localFocalPoint;
|
||||||
if (details.localFocalPoint.dx < 40) return;
|
final dx = localFocalPoint.dx;
|
||||||
if (details.localFocalPoint.dx > maxWidth - 40) return;
|
final dy = localFocalPoint.dy;
|
||||||
if (details.localFocalPoint.dy > maxHeight - 40) return;
|
if (dx < 40 || dy < 40) return;
|
||||||
|
if (dx > maxWidth - 40 || dy > maxHeight - 40) return;
|
||||||
if (details.pointerCount > 1) {
|
if (details.pointerCount > 1) {
|
||||||
interacting = true;
|
interacting = true;
|
||||||
}
|
}
|
||||||
plPlayerController.initialFocalPoint = details.localFocalPoint;
|
plPlayerController.initialFocalPoint = localFocalPoint;
|
||||||
// if (kDebugMode) {
|
// if (kDebugMode) {
|
||||||
// debugPrint("_initialFocalPoint$_initialFocalPoint");
|
// debugPrint("_initialFocalPoint$_initialFocalPoint");
|
||||||
// }
|
// }
|
||||||
@@ -899,10 +902,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
|
|
||||||
if (_gestureType == null) {
|
if (_gestureType == null) {
|
||||||
if (cumulativeDelta.distance < 1) return;
|
if (cumulativeDelta.distance < 1) return;
|
||||||
if (cumulativeDelta.dx.abs() > 3 * cumulativeDelta.dy.abs()) {
|
final dx = cumulativeDelta.dx.abs();
|
||||||
|
final dy = cumulativeDelta.dy.abs();
|
||||||
|
if (dx > 3 * dy) {
|
||||||
_gestureType = GestureType.horizontal;
|
_gestureType = GestureType.horizontal;
|
||||||
_showControlsIfNeeded();
|
_showControlsIfNeeded();
|
||||||
} else if (cumulativeDelta.dy.abs() > 3 * cumulativeDelta.dx.abs()) {
|
} else if (dy > 3 * dx) {
|
||||||
if (!plPlayerController.enableSlideVolumeBrightness &&
|
if (!plPlayerController.enableSlideVolumeBrightness &&
|
||||||
!plPlayerController.enableSlideFS) {
|
!plPlayerController.enableSlideFS) {
|
||||||
return;
|
return;
|
||||||
@@ -1208,10 +1213,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
if (_gestureType == null) {
|
if (_gestureType == null) {
|
||||||
final pan = event.pan;
|
final pan = event.pan;
|
||||||
if (pan.distance < 1) return;
|
if (pan.distance < 1) return;
|
||||||
if (pan.dx.abs() > 3 * pan.dy.abs()) {
|
final dx = pan.dx.abs();
|
||||||
|
final dy = pan.dy.abs();
|
||||||
|
if (dx > 3 * dy) {
|
||||||
_gestureType = GestureType.horizontal;
|
_gestureType = GestureType.horizontal;
|
||||||
_showControlsIfNeeded();
|
_showControlsIfNeeded();
|
||||||
} else if (pan.dy.abs() > 3 * pan.dx.abs()) {
|
} else if (dy > 3 * dx) {
|
||||||
_gestureType = GestureType.right;
|
_gestureType = GestureType.right;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -2179,7 +2186,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const _overlaySpacing = 10.0;
|
static const _overlaySpacing = 10.0;
|
||||||
static const _overlayWidth = 130.0;
|
static const _overlayWidth = 118.0;
|
||||||
static const _overlayHeight = 35.0;
|
static const _overlayHeight = 35.0;
|
||||||
|
|
||||||
DanmakuItem<DanmakuExtra>? _suspendedDm;
|
DanmakuItem<DanmakuExtra>? _suspendedDm;
|
||||||
@@ -2196,7 +2203,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
Widget _dmActionItem(Widget child, {required VoidCallback onTap}) {
|
Widget _dmActionItem(Widget child, {required VoidCallback onTap}) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: onTap,
|
onTap: () {
|
||||||
|
_removeDmAction();
|
||||||
|
onTap();
|
||||||
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: _overlayHeight,
|
height: _overlayHeight,
|
||||||
width: _overlayWidth / 3,
|
width: _overlayWidth / 3,
|
||||||
@@ -2207,12 +2217,49 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BoxDecoration _getDmTipBg(DanmakuItem item, double dx) {
|
||||||
|
const offset = 65;
|
||||||
|
const size = Size(_overlayWidth, _overlayHeight);
|
||||||
|
if (item.xPosition >= maxWidth - offset) {
|
||||||
|
return const BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
filterQuality: FilterQuality.low,
|
||||||
|
image: SvgImageProvider(
|
||||||
|
'assets/images/dm_tip/player_dm_tip_right.svg',
|
||||||
|
size: size,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (item.xPosition + item.width <= offset) {
|
||||||
|
return const BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
filterQuality: FilterQuality.low,
|
||||||
|
image: SvgImageProvider(
|
||||||
|
'assets/images/dm_tip/player_dm_tip_left.svg',
|
||||||
|
size: size,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
filterQuality: FilterQuality.low,
|
||||||
|
image: SvgImageProvider(
|
||||||
|
'assets/images/dm_tip/player_dm_tip_center.svg',
|
||||||
|
size: size,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildDmAction(
|
Widget _buildDmAction(
|
||||||
DanmakuItem<DanmakuExtra> item,
|
DanmakuItem<DanmakuExtra> item,
|
||||||
Offset offset,
|
Offset offset,
|
||||||
) {
|
) {
|
||||||
|
final dx = offset.dx;
|
||||||
// fullscreen
|
// fullscreen
|
||||||
if (offset.dx > maxWidth) {
|
if (dx > maxWidth) {
|
||||||
_removeDmAction();
|
_removeDmAction();
|
||||||
return const Positioned(left: 0, top: 0, child: SizedBox.shrink());
|
return const Positioned(left: 0, top: 0, child: SizedBox.shrink());
|
||||||
}
|
}
|
||||||
@@ -2224,28 +2271,24 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
final right =
|
final right =
|
||||||
maxWidth -
|
maxWidth -
|
||||||
clampDouble(
|
clampDouble(
|
||||||
offset.dx + _overlayWidth / 2,
|
dx + _overlayWidth / 2,
|
||||||
_overlaySpacing + _overlayWidth,
|
_overlaySpacing + _overlayWidth,
|
||||||
maxWidth - _overlaySpacing,
|
maxWidth - _overlaySpacing,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO LiveDanmaku
|
||||||
final extra = item.content.extra as VideoDanmaku;
|
final extra = item.content.extra as VideoDanmaku;
|
||||||
|
|
||||||
return Positioned(
|
return Positioned(
|
||||||
right: right,
|
right: right,
|
||||||
top: top,
|
top: top,
|
||||||
child: Column(
|
child: SizedBox(
|
||||||
children: [
|
width: _overlayWidth,
|
||||||
const CustomPaint(
|
height: _overlayHeight,
|
||||||
painter: _TrianglePainter(Colors.black54),
|
child: DecoratedBox(
|
||||||
size: Size(12, 6),
|
decoration: _getDmTipBg(item, dx),
|
||||||
),
|
child: Padding(
|
||||||
Container(
|
padding: const EdgeInsets.only(top: 4),
|
||||||
width: _overlayWidth,
|
|
||||||
height: _overlayHeight,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.black54,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(18)),
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
@@ -2254,64 +2297,52 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
Icon(
|
Icon(
|
||||||
size: 20,
|
size: 20,
|
||||||
extra.isLike
|
extra.isLike
|
||||||
? Icons.thumb_up_off_alt_sharp
|
? CustomIcons.player_dm_tip_like_solid
|
||||||
: Icons.thumb_up_off_alt_outlined,
|
: CustomIcons.player_dm_tip_like,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => HeaderControl.likeDanmaku(
|
||||||
_removeDmAction();
|
extra,
|
||||||
HeaderControl.likeDanmaku(
|
plPlayerController.cid!,
|
||||||
extra,
|
),
|
||||||
plPlayerController.cid!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
_dmActionItem(
|
_dmActionItem(
|
||||||
const Icon(
|
const Icon(
|
||||||
size: 20,
|
size: 19,
|
||||||
Icons.copy,
|
CustomIcons.player_dm_tip_copy,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => Utils.copyText(item.content.text),
|
||||||
_removeDmAction();
|
|
||||||
Utils.copyText(item.content.text);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
if (item.content.selfSend)
|
if (item.content.selfSend)
|
||||||
_dmActionItem(
|
_dmActionItem(
|
||||||
const Icon(
|
const Icon(
|
||||||
size: 20,
|
size: 20,
|
||||||
Icons.delete,
|
CustomIcons.player_dm_tip_recall,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => HeaderControl.deleteDanmaku(
|
||||||
_removeDmAction();
|
extra.id,
|
||||||
HeaderControl.deleteDanmaku(
|
plPlayerController.cid!,
|
||||||
extra.id,
|
),
|
||||||
plPlayerController.cid!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
_dmActionItem(
|
_dmActionItem(
|
||||||
const Icon(
|
const Icon(
|
||||||
size: 20,
|
size: 20,
|
||||||
Icons.report_problem_outlined,
|
CustomIcons.player_dm_tip_back,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () => HeaderControl.reportDanmaku(
|
||||||
_removeDmAction();
|
extra,
|
||||||
HeaderControl.reportDanmaku(
|
context,
|
||||||
extra,
|
plPlayerController,
|
||||||
context,
|
),
|
||||||
plPlayerController,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2659,27 +2690,3 @@ 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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -326,6 +326,7 @@ flutter:
|
|||||||
- assets/images/live/
|
- assets/images/live/
|
||||||
- assets/images/video/
|
- assets/images/video/
|
||||||
- assets/images/paycoins/
|
- assets/images/paycoins/
|
||||||
|
- assets/images/dm_tip/
|
||||||
- assets/shaders/
|
- assets/shaders/
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|||||||
Reference in New Issue
Block a user