opt video seek preview (#1026)

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
dom
2025-08-16 10:29:46 +08:00
committed by GitHub
parent 831a3052fa
commit d805306d20
6 changed files with 365 additions and 178 deletions

View File

@@ -1,29 +1,34 @@
import 'package:PiliPlus/utils/extension.dart';
class VideoShotData { class VideoShotData {
String? pvdata; String? pvdata;
int? imgXLen; int imgXLen;
int? imgYLen; int imgYLen;
int? imgXSize; double imgXSize;
int? imgYSize; double imgYSize;
List<String>? image; late final int totalPerImage = imgXLen * imgYLen;
List<int>? index; List<String> image;
List<int> index;
VideoShotData({ VideoShotData({
this.pvdata, this.pvdata,
this.imgXLen, required this.imgXLen,
this.imgYLen, required this.imgYLen,
this.imgXSize, required this.imgXSize,
this.imgYSize, required this.imgYSize,
this.image, required this.image,
this.index, required this.index,
}); });
factory VideoShotData.fromJson(Map<String, dynamic> json) => VideoShotData( factory VideoShotData.fromJson(Map<String, dynamic> json) => VideoShotData(
pvdata: json["pvdata"], pvdata: json["pvdata"],
imgXLen: json["img_x_len"], imgXLen: json["img_x_len"],
imgYLen: json["img_y_len"], imgYLen: json["img_y_len"],
imgXSize: json["img_x_size"], imgXSize: (json["img_x_size"] as num).toDouble(),
imgYSize: json["img_y_size"], imgYSize: (json["img_y_size"] as num).toDouble(),
image: (json["image"] as List?)?.cast(), image: (json["image"] as List)
index: (json["index"] as List?)?.cast(), .map((e) => (e as String).http2https)
.toList(),
index: (json["index"] as List).cast(),
); );
} }

View File

@@ -1,10 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math' show max;
import 'dart:ui' as ui;
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/ua_type.dart';
import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/common/audio_normalization.dart'; import 'package:PiliPlus/models/common/audio_normalization.dart';
@@ -33,6 +37,7 @@ import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart';
import 'package:crclib/catalog.dart'; import 'package:crclib/catalog.dart';
import 'package:dio/dio.dart' show Options;
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -561,9 +566,7 @@ class PlPlayerController {
_pgcType = pgcType; _pgcType = pgcType;
if (showSeekPreview) { if (showSeekPreview) {
videoShot = null; _clearPreview();
showPreview.value = false;
previewDx.value = 0;
} }
if (_videoPlayerController != null && if (_videoPlayerController != null &&
@@ -1542,6 +1545,7 @@ class PlPlayerController {
} }
dmState.clear(); dmState.clear();
_playerCount = 0; _playerCount = 0;
_clearPreview();
Utils.channel.setMethodCallHandler(null); Utils.channel.setMethodCallHandler(null);
pause(); pause();
try { try {
@@ -1600,17 +1604,48 @@ class PlPlayerController {
); );
} }
late final showSeekPreview = Pref.showSeekPreview; Map<String, WeakReference<ui.Image>>? previewCache;
late bool _isQueryingVideoShot = false; LoadingState<VideoShotData>? videoShot;
Map? videoShot;
late final RxBool showPreview = false.obs; late final RxBool showPreview = false.obs;
late final RxDouble previewDx = 0.0.obs; late final showSeekPreview = Pref.showSeekPreview;
late final Rx<int?> previewIndex = Rx<int?>(null);
Future<void> getVideoShot() async { void updatePreviewIndex(int seconds) {
if (_isQueryingVideoShot) { if (videoShot == null) {
videoShot = LoadingState.loading();
getVideoShot();
return; return;
} }
_isQueryingVideoShot = true; if (videoShot case Success<VideoShotData> success) {
final data = success.response;
if (data.index.isNullOrEmpty) {
return;
}
if (!showPreview.value) {
showPreview.value = true;
}
previewIndex.value = max(
0,
(data.index.where((item) => item <= seconds).length - 2),
);
}
}
void _clearPreview() {
showPreview.value = false;
previewIndex.value = null;
videoShot = null;
previewCache
?..forEach((_, ref) {
try {
ref.target?.dispose();
} catch (_) {}
})
..clear();
previewCache = null;
}
Future<void> getVideoShot() async {
try { try {
var res = await Request().get( var res = await Request().get(
'/x/player/videoshot', '/x/player/videoshot',
@@ -1620,20 +1655,22 @@ class PlPlayerController {
'cid': _cid, 'cid': _cid,
'index': 1, 'index': 1,
}, },
options: Options(
headers: {
'user-agent': UaType.pc.ua,
'referer': 'https://www.bilibili.com/video/$bvid',
},
),
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
videoShot = { videoShot = Success(VideoShotData.fromJson(res.data['data']));
'status': true,
'data': VideoShotData.fromJson(res.data['data']),
};
} else { } else {
videoShot = {'status': false}; videoShot = const Error(null);
} }
} catch (e) { } catch (e) {
videoShot = {'status': false}; videoShot = const Error(null);
if (kDebugMode) debugPrint('getVideoShot: $e'); if (kDebugMode) debugPrint('getVideoShot: $e');
} }
_isQueryingVideoShot = false;
} }
late final RxList<double> dmTrend = <double>[].obs; late final RxList<double> dmTrend = <double>[].obs;

View File

@@ -1,10 +1,12 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:ui' as ui;
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.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';
import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/models/common/super_resolution_type.dart'; import 'package:PiliPlus/models/common/super_resolution_type.dart';
import 'package:PiliPlus/models_new/video/video_detail/episode.dart'; import 'package:PiliPlus/models_new/video/video_detail/episode.dart';
import 'package:PiliPlus/models_new/video/video_detail/section.dart'; import 'package:PiliPlus/models_new/video/video_detail/section.dart';
@@ -26,13 +28,13 @@ import 'package:PiliPlus/plugin/pl_player/widgets/common_btn.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/forward_seek.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/forward_seek.dart';
import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart'; import 'package:PiliPlus/plugin/pl_player/widgets/play_pause_btn.dart';
import 'package:PiliPlus/utils/duration_util.dart'; import 'package:PiliPlus/utils/duration_util.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
import 'package:cached_network_image/cached_network_image.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';
import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -253,7 +255,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
} }
// 动态构建底部控制条 // 动态构建底部控制条
Widget buildBottomControl(bool isLandscape, double maxWidth) { Widget buildBottomControl(bool isLandscape) {
final videoDetail = introController.videoDetail.value; final videoDetail = introController.videoDetail.value;
final isSeason = videoDetail.ugcSeason != null; final isSeason = videoDetail.ugcSeason != null;
final isPart = videoDetail.pages != null && videoDetail.pages!.length > 1; final isPart = videoDetail.pages != null && videoDetail.pages!.length > 1;
@@ -780,16 +782,17 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
final int curSliderPosition = final int curSliderPosition =
plPlayerController.sliderPosition.value.inMilliseconds; plPlayerController.sliderPosition.value.inMilliseconds;
final Duration pos = Duration( final int newPos =
milliseconds: (curSliderPosition +
curSliderPosition + (plPlayerController.sliderScale *
(plPlayerController.sliderScale * delta.dx / maxWidth) delta.dx /
.round(), maxWidth)
); .round())
final Duration result = pos.clamp( .clamp(
Duration.zero, 0,
plPlayerController.duration.value, plPlayerController.duration.value.inMilliseconds,
); );
final Duration result = Duration(milliseconds: newPos);
final height = maxHeight * 0.125; final height = maxHeight * 0.125;
if (details.localFocalPoint.dy <= height && if (details.localFocalPoint.dy <= height &&
(details.localFocalPoint.dx >= maxWidth * 0.875 || (details.localFocalPoint.dx >= maxWidth * 0.875 ||
@@ -837,18 +840,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
..onChangedSliderStart(); ..onChangedSliderStart();
if (plPlayerController.showSeekPreview && if (plPlayerController.showSeekPreview &&
plPlayerController.cancelSeek != true) { plPlayerController.cancelSeek != true) {
try { plPlayerController.updatePreviewIndex(newPos ~/ 1000);
plPlayerController.previewDx.value =
result.inMilliseconds /
plPlayerController
.durationSeconds
.value
.inMilliseconds *
maxWidth;
if (!plPlayerController.showPreview.value) {
plPlayerController.showPreview.value = true;
}
} catch (_) {}
} }
} else if (_gestureType == GestureType.left) { } else if (_gestureType == GestureType.left) {
// 左边区域 👈 // 左边区域 👈
@@ -1102,7 +1094,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
child: Align( child: Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: FractionalTranslation( child: FractionalTranslation(
translation: const Offset(0.0, 1.0), // 上下偏移量(负数向上偏移) translation: const Offset(0.0, 0.6),
child: Obx( child: Obx(
() => AnimatedOpacity( () => AnimatedOpacity(
curve: Curves.easeInOut, curve: Curves.easeInOut,
@@ -1271,11 +1263,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
widget.bottomControl ?? widget.bottomControl ??
BottomControl( BottomControl(
controller: plPlayerController, controller: plPlayerController,
buildBottomControl: (bottomMaxWidth) => buildBottomControl: () =>
buildBottomControl( buildBottomControl(maxWidth > maxHeight),
maxWidth > maxHeight,
bottomMaxWidth,
),
), ),
), ),
], ],
@@ -1448,22 +1437,19 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
if (plPlayerController.dmTrend.isNotEmpty && if (plPlayerController.dmTrend.isNotEmpty &&
plPlayerController.showDmTreandChart.value) plPlayerController.showDmTreandChart.value)
buildDmChart(theme, plPlayerController), buildDmChart(theme, plPlayerController),
if (plPlayerController.showSeekPreview)
Positioned(
left: 0,
right: 0,
bottom: 12,
child: buildSeekPreviewWidget(
plPlayerController,
maxWidth,
),
),
], ],
); );
}, },
), ),
), ),
if (plPlayerController.showSeekPreview)
buildSeekPreviewWidget(
plPlayerController,
maxWidth,
maxHeight,
),
// 锁 // 锁
SafeArea( SafeArea(
child: Obx( child: Obx(
@@ -1781,81 +1767,66 @@ Widget buildDmChart(
Widget buildSeekPreviewWidget( Widget buildSeekPreviewWidget(
PlPlayerController plPlayerController, PlPlayerController plPlayerController,
double maxWidth, double maxWidth,
double maxHeight,
) { ) {
return Obx( return Obx(
() { () {
if (!plPlayerController.showPreview.value || if (!plPlayerController.showPreview.value) {
plPlayerController.videoShot?['status'] != true) {
if (plPlayerController.videoShot == null) {
plPlayerController.getVideoShot();
}
return const SizedBox.shrink();
}
VideoShotData data = plPlayerController.videoShot!['data'];
if (data.index.isNullOrEmpty) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
try { try {
double scale = VideoShotData data = plPlayerController.videoShot!.data;
plPlayerController.isFullScreen.value &&
!plPlayerController.isVertical final isFullScreen = plPlayerController.isFullScreen.value;
final double scale = isFullScreen && !plPlayerController.isVertical
? 4 ? 4
: 2.5; : 3;
// offset double height = 27 * scale;
double left = (plPlayerController.previewDx.value - 48 * scale / 2) final compatHeight = maxHeight - 140;
.clamp(8, maxWidth - 48 * scale - 8); if (compatHeight > 50) {
height = min(height, compatHeight);
// index
// int index = plPlayerController.sliderPositionSeconds.value ~/ 5;
int index = max(
0,
(data.index!
.where(
(item) =>
item <= plPlayerController.sliderPositionSeconds.value,
)
.length -
2),
);
// pageIndex
int pageIndex = (index ~/ 100).clamp(0, data.image!.length - 1);
// alignment
double cal(m) {
return -1 + 2 / 9 * m;
} }
int align = index % 100; final int imgXLen = data.imgXLen;
int x = align % 10; final int imgYLen = data.imgYLen;
int y = align ~/ 10; final int totalPerImage = data.totalPerImage;
double dx = cal(x); double imgXSize = data.imgXSize;
double dy = cal(y); double imgYSize = data.imgYSize;
Alignment alignment = Alignment(dx, dy);
return Container( return Align(
alignment: Alignment.centerLeft, alignment: isFullScreen ? Alignment.center : Alignment.center,
padding: EdgeInsets.only(left: left), child: Obx(
child: UnconstrainedBox( () {
child: ClipRRect( final index = plPlayerController.previewIndex.value!;
borderRadius: scale == 2.5 int pageIndex = (index ~/ totalPerImage).clamp(
? const BorderRadius.all(Radius.circular(6)) 0,
: StyleString.mdRadius, data.image.length - 1,
child: Align( );
widthFactor: 0.1, int align = index % totalPerImage;
heightFactor: 0.1, int x = align % imgXLen;
alignment: alignment, int y = align ~/ imgYLen;
child: CachedNetworkImage( final url = data.image[pageIndex];
fit: BoxFit.fill,
width: 480 * scale, return ClipRRect(
height: 270 * scale, borderRadius: StyleString.mdRadius,
imageUrl: data.image![pageIndex].http2https, child: VideoShotImage(
url: url,
x: x,
y: y,
imgXSize: imgXSize,
imgYSize: imgYSize,
height: height,
image: plPlayerController.previewCache?[url]?.target,
onCacheImg: (img) =>
(plPlayerController.previewCache ??= {})[url] ??=
WeakReference(img),
onSetSize: (xSize, ySize) => data
..imgXSize = imgXSize = xSize
..imgYSize = imgYSize = ySize,
), ),
), );
), },
), ),
); );
} catch (e) { } catch (e) {
@@ -1866,6 +1837,193 @@ Widget buildSeekPreviewWidget(
); );
} }
class VideoShotImage extends StatefulWidget {
const VideoShotImage({
super.key,
this.image,
required this.url,
required this.x,
required this.y,
required this.imgXSize,
required this.imgYSize,
required this.height,
required this.onCacheImg,
required this.onSetSize,
});
final ui.Image? image;
final String url;
final int x;
final int y;
final double imgXSize;
final double imgYSize;
final double height;
final ValueChanged<ui.Image> onCacheImg;
final Function(double imgXSize, double imgYSize) onSetSize;
@override
State<VideoShotImage> createState() => _VideoShotImageState();
}
Future<ui.Image?> _getImg(String url) async {
final cacheManager = DefaultCacheManager();
final cacheKey = url.hashCode.toString();
final fileInfo = await cacheManager.getFileFromCache(cacheKey);
if (fileInfo != null) {
final bytes = await fileInfo.file.readAsBytes();
final codec = await ui.instantiateImageCodec(bytes);
final frame = await codec.getNextFrame();
codec.dispose();
return frame.image;
} else {
final res = await Request().get(
url,
options: Options(responseType: ResponseType.bytes),
);
if (res.statusCode == 200) {
final data = res.data;
cacheManager.putFile(cacheKey, data, fileExtension: 'jpg');
final codec = await ui.instantiateImageCodec(data);
final frame = await codec.getNextFrame();
return frame.image;
}
}
return null;
}
class _VideoShotImageState extends State<VideoShotImage> {
late Size _size;
late Rect _dstRect;
late RRect _rrect;
ui.Image? _image;
@override
void initState() {
super.initState();
_initSize();
_loadImg();
}
void _initSizeIfNeeded() {
if (_size.width.isNaN) {
_initSize();
}
}
void _initSize() {
if (widget.imgXSize == 0) {
if (_image != null) {
final imgXSize = _image!.width / 10;
final imgYSize = _image!.height / 10;
final height = widget.height;
final width = height * imgXSize / imgYSize;
_size = Size(width, height);
_setRect(width, height);
widget.onSetSize(imgXSize, imgYSize);
} else {
_size = const Size(double.nan, double.nan);
_setRect(double.nan, double.nan);
}
} else {
final height = widget.height;
final width = height * widget.imgXSize / widget.imgYSize;
_size = Size(width, height);
_setRect(width, height);
}
}
void _setRect(double width, double height) {
_dstRect = Rect.fromLTWH(0, 0, width, height);
_rrect = RRect.fromRectAndRadius(_dstRect, const Radius.circular(10));
}
Future<void> _loadImg() async {
_image = widget.image;
if (_image != null) {
_initSizeIfNeeded();
setState(() {});
} else {
final image = await _getImg(widget.url);
if (mounted && image != null) {
_image = image;
widget.onCacheImg(image);
_initSizeIfNeeded();
setState(() {});
}
}
}
@override
void didUpdateWidget(VideoShotImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.url != widget.url) {
_loadImg();
}
}
late final _imgPaint = Paint()..filterQuality = FilterQuality.medium;
late final _borderPaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 1.5;
@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 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( Widget buildViewPointWidget(
PlPlayerController plPlayerController, PlPlayerController plPlayerController,
double offset, double offset,

View File

@@ -17,7 +17,7 @@ class BottomControl extends StatelessWidget {
}); });
final PlPlayerController controller; final PlPlayerController controller;
final Widget Function(double maxWidth) buildBottomControl; final Widget Function() buildBottomControl;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -28,15 +28,15 @@ class BottomControl extends StatelessWidget {
double lastAnnouncedValue = -1; double lastAnnouncedValue = -1;
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 12), padding: const EdgeInsets.fromLTRB(10, 0, 10, 12),
child: LayoutBuilder( child: Column(
builder: (context, constraints) { mainAxisSize: MainAxisSize.min,
final maxWidth = constraints.maxWidth; children: [
return Column( Padding(
mainAxisSize: MainAxisSize.min, padding: const EdgeInsets.fromLTRB(10, 0, 10, 7),
children: [ child: LayoutBuilder(
Padding( builder: (context, constraints) {
padding: const EdgeInsets.fromLTRB(10, 0, 10, 7), final maxWidth = constraints.maxWidth;
child: Obx( return Obx(
() => Stack( () => Stack(
clipBehavior: Clip.none, clipBehavior: Clip.none,
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
@@ -63,20 +63,16 @@ class BottomControl extends StatelessWidget {
thumbRadius: 7, thumbRadius: 7,
onDragStart: (duration) { onDragStart: (duration) {
feedBack(); feedBack();
controller.onChangedSliderStart( controller.onChangedSliderStart(duration.timeStamp);
duration.timeStamp,
);
}, },
onDragUpdate: (duration) { onDragUpdate: (duration) {
if (controller.showSeekPreview) {
controller.updatePreviewIndex(
duration.timeStamp.inSeconds,
);
}
double newProgress = double newProgress =
duration.timeStamp.inSeconds / max; duration.timeStamp.inSeconds / max;
if (controller.showSeekPreview) {
if (!controller.showPreview.value) {
controller.showPreview.value = true;
}
controller.previewDx.value =
duration.localPosition.dx;
}
if ((newProgress - lastAnnouncedValue).abs() > if ((newProgress - lastAnnouncedValue).abs() >
0.02) { 0.02) {
accessibilityDebounce?.cancel(); accessibilityDebounce?.cancel();
@@ -151,31 +147,20 @@ class BottomControl extends StatelessWidget {
buildViewPointWidget( buildViewPointWidget(
controller, controller,
8.75, 8.75,
maxWidth - 20, maxWidth,
), ),
], ],
if (controller.dmTrend.isNotEmpty && if (controller.dmTrend.isNotEmpty &&
controller.showDmTreandChart.value) controller.showDmTreandChart.value)
buildDmChart(theme, controller, 4.5), buildDmChart(theme, controller, 4.5),
if (controller.showSeekPreview &&
controller.showControls.value)
Positioned(
left: 0,
right: 0,
bottom: 18,
child: buildSeekPreviewWidget(
controller,
maxWidth - 20,
),
),
], ],
), ),
), );
), },
buildBottomControl(maxWidth), ),
], ),
); buildBottomControl(),
}, ],
), ),
); );
} }

View File

@@ -620,7 +620,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_cache_manager: flutter_cache_manager:
dependency: transitive dependency: "direct main"
description: description:
name: flutter_cache_manager name: flutter_cache_manager
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
@@ -924,7 +924,7 @@ packages:
source: hosted source: hosted
version: "4.1.2" version: "4.1.2"
image: image:
dependency: transitive dependency: "direct main"
description: description:
name: image name: image
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"

View File

@@ -201,6 +201,7 @@ dependencies:
ref: master ref: master
crclib: ^3.0.0 crclib: ^3.0.0
web_socket_channel: ^3.0.3 web_socket_channel: ^3.0.3
image: ^4.5.4
vector_math: any vector_math: any
fixnum: any fixnum: any
@@ -212,6 +213,7 @@ dependencies:
path: any path: any
collection: any collection: any
material_color_utilities: any material_color_utilities: any
flutter_cache_manager: any
dependency_overrides: dependency_overrides:
screen_brightness: ^2.0.1 screen_brightness: ^2.0.1