From 50d7ea78d91098bdd6733b69314e4e6c7efa9c56 Mon Sep 17 00:00:00 2001 From: liyi Date: Fri, 10 Jan 2025 14:46:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E5=AE=8C=E5=96=84=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E3=80=81=E6=93=8D=E4=BD=9C=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doorLockLog/doorLockLog_logic.dart | 1 + .../doorLockLog/doorLockLog_page.dart | 156 ++++++--- .../videoLog/videoLog/videoLog_logic.dart | 25 +- .../videoLog/videoLog/videoLog_page.dart | 42 ++- .../videoLogDetail/controlsOverlay_page.dart | 44 ++- .../videoLogDetail/videoLogDetail_page.dart | 311 +++++++++--------- .../videoLogDetail/videoLogDetail_state.dart | 1 + .../widget/full_screenImage_page.dart | 20 ++ .../videoLog/widget/video_player_widget.dart | 75 +++++ .../videoLog/widget/video_thumbnail.dart | 73 ++++ 10 files changed, 491 insertions(+), 257 deletions(-) create mode 100644 lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart create mode 100644 lib/main/lockDetail/videoLog/widget/video_player_widget.dart create mode 100644 lib/main/lockDetail/videoLog/widget/video_thumbnail.dart diff --git a/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart b/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart index 23d9a38e..155c4d5c 100755 --- a/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart +++ b/lib/main/lockDetail/doorLockLog/doorLockLog_logic.dart @@ -248,6 +248,7 @@ class DoorLockLogLogic extends BaseGetXController { /// 刷新门锁日志列表 StreamSubscription? _getDoorLockLogListRefreshUIEvent; + void _getDoorLockLogListRefreshUIAction() { _getDoorLockLogListRefreshUIEvent = eventBus .on() diff --git a/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart b/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart index 561cf30b..e3ef4213 100755 --- a/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart +++ b/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart @@ -7,9 +7,13 @@ import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_entity.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_logic.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_state.dart'; import 'package:star_lock/main/lockDetail/doorLockLog/exportRecordDialog/exportRecordDialog_page.dart'; +import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; +import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart'; +import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail.dart'; import 'package:star_lock/tools/EasyRefreshTool.dart'; import 'package:star_lock/tools/advancedCalendar/src/widget.dart'; import 'package:star_lock/tools/commonDataManage.dart'; +import 'package:star_lock/tools/dateTool.dart'; import 'package:star_lock/tools/menuItem/xsDropDownWidget.dart'; import 'package:star_lock/tools/noData.dart'; import 'package:star_lock/tools/showCupertinoAlertView.dart'; @@ -142,6 +146,7 @@ class _DoorLockLogPageState extends State with RouteAware { ); } } + // switch (value) { // case "读取记录".tr: // { @@ -256,31 +261,24 @@ class _DoorLockLogPageState extends State with RouteAware { color: Colors.white, borderRadius: BorderRadius.circular(16.w), ), - child: Obx(() => EasyRefreshTool( - onRefresh: () async { - logic.mockNetworkDataRequest(isRefresh: true); - }, - onLoad: () async { - logic.mockNetworkDataRequest(isRefresh: false); - }, - child: state.lockLogItemList.isNotEmpty - ? Timeline.tileBuilder( - builder: _timelineBuilderWidget(), - theme: TimelineThemeData( - nodePosition: 0.04, //居左侧距离 - connectorTheme: const ConnectorThemeData( - thickness: 1.0, - color: AppColors.greyLineColor, - indent: 0.5, - ), - indicatorTheme: const IndicatorThemeData( - size: 8.0, - color: AppColors.greyLineColor, - position: 0.4, - ), - ), - ) - : NoData())), + child: Obx(() => state.lockLogItemList.isNotEmpty + ? Timeline.tileBuilder( + builder: _timelineBuilderWidget(), + theme: TimelineThemeData( + nodePosition: 0.04, //居左侧距离 + connectorTheme: const ConnectorThemeData( + thickness: 1.0, + color: AppColors.greyLineColor, + indent: 0.5, + ), + indicatorTheme: const IndicatorThemeData( + size: 8.0, + color: AppColors.greyLineColor, + position: 0.4, + ), + ), + ) + : NoData()), ); } @@ -319,33 +317,13 @@ class _DoorLockLogPageState extends State with RouteAware { SizedBox( height: 10.h, ), - GestureDetector( - onTap: () { - Get.toNamed(Routers.videoLogDetailPage); - }, - child: Stack( - children: [ - if (timelineData.imagesUrl!.isNotEmpty) - Image.network( - timelineData.imagesUrl!, - width: 260.w, - height: 260.h, - ) - else - Container(), - Positioned( - top: 150.h, - left: 10.w, - child: Image( - image: const AssetImage( - 'images/main/icon_lockLog_play.png'), - width: 24.w, - height: 20.w, - ), - ), - ], - ), - ), + videoItem(RecordListData( + recordId: state.lockLogItemList.value[index].recordId, + recordType: state.lockLogItemList.value[index].recordType, + operateDate: state.lockLogItemList.value[index].operateDate, + imagesUrl: state.lockLogItemList.value[index].imagesUrl, + videoUrl: state.lockLogItemList.value[index].videoUrl, + )), SizedBox( height: 20.h, ), @@ -357,6 +335,80 @@ class _DoorLockLogPageState extends State with RouteAware { ); } + Widget videoItem(RecordListData recordData) { + return GestureDetector( + onTap: () { + if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) { + final lockLogItemList = state.lockLogItemList.value; + final list = lockLogItemList + .map((e) => RecordListData( + videoUrl: e.videoUrl, + imagesUrl: e.imagesUrl, + operateDate: e.operateDate, + recordId: e.recordId, + recordType: e.recordType, + )) + .toList(); + final selectDateString = + DateTool().dateToYMDString(state.startDate.value.toString()); + final cloudStorageData = + CloudStorageData(date: selectDateString, recordList: list); + Get.toNamed(Routers.videoLogDetailPage, arguments: { + 'recordData': recordData, + 'videoDataList': [cloudStorageData] + }); + } else if (recordData.imagesUrl != null && + recordData.imagesUrl!.isNotEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FullScreenImagePage( + imageUrl: recordData.imagesUrl!, + ), + ), + ); + } + }, + child: SizedBox( + width: 260.w, + height: 260.h, + child: Column( + children: [ + Container( + width: 260.w, + height: 260.h, + margin: const EdgeInsets.all(0), + color: Colors.white, + child: ClipRRect( + borderRadius: BorderRadius.circular(10.w), + child: _buildImageOrVideoItem(recordData), + ), + ), + ], + ), + ), + ); + } + + _buildImageOrVideoItem(RecordListData recordData) { + if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) { + return _buildVideoItem(recordData); + } else { + return _buildImageItem(recordData); + } + } + + _buildVideoItem(RecordListData recordData) { + return VideoThumbnail(videoUrl: recordData.videoUrl!); + } + + _buildImageItem(RecordListData recordData) { + return Image.network( + recordData.imagesUrl!, + fit: BoxFit.cover, + ); + } + @override void didChangeDependencies() { super.didChangeDependencies(); diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart index 2409d16e..eb8854e0 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/tools/baseGetXController.dart'; -import 'package:video_thumbnail/video_thumbnail.dart'; + import 'videoLog_state.dart'; class VideoLogLogic extends BaseGetXController { @@ -14,33 +14,10 @@ class VideoLogLogic extends BaseGetXController { ); if (entity.errorCode!.codeIsSuccessful) { state.videoLogList.value = entity.data!; - state.videoLogList.refresh(); - // state.videoLogList.forEach((element) { - // final recordList = element.recordList; - // if (recordList != null && recordList.isNotEmpty) { - // recordList.forEach((item) { - // if (item.videoUrl != null && item.videoUrl!.isNotEmpty) { - // final coverImage = aw handleVideoCoverImage(item.videoUrl); - // state.videoCoverList.add(coverImage); - // } - // }); - // } - // }); } } - Future handleVideoCoverImage(videoUrl) async { - final uint8list = await VideoThumbnail.thumbnailData( - video: videoUrl, - imageFormat: ImageFormat.JPEG, - maxWidth: 128, - // specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio - quality: 25, - ); - return uint8list; - } - @override onReady() { super.onReady(); diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart index 4a587e82..5e38dca7 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart @@ -5,12 +5,15 @@ import 'package:star_lock/appRouters.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_state.dart'; +import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart'; +import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail.dart'; import 'package:star_lock/tools/dateTool.dart'; import 'package:star_lock/tools/noData.dart'; +import 'package:video_player/video_player.dart'; import '../../../../app_settings/app_colors.dart'; import '../../../../tools/titleAppBar.dart'; -import 'videoLog_logic.dart';import 'package:video_thumbnail/video_thumbnail.dart'; +import 'videoLog_logic.dart'; class VideoLogPage extends StatefulWidget { const VideoLogPage({Key? key}) : super(key: key); @@ -23,6 +26,12 @@ class _VideoLogPageState extends State { final VideoLogLogic logic = Get.put(VideoLogLogic()); final VideoLogState state = Get.find().state; + @override + void initState() { + // TODO: implement initState + super.initState(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -275,10 +284,22 @@ class _VideoLogPageState extends State { Widget videoItem(RecordListData recordData) { return GestureDetector( onTap: () { - Get.toNamed(Routers.videoLogDetailPage, arguments: { - 'recordData': recordData, - 'videoDataList': state.videoLogList.value - }); + if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) { + Get.toNamed(Routers.videoLogDetailPage, arguments: { + 'recordData': recordData, + 'videoDataList': state.videoLogList.value + }); + } else if (recordData.imagesUrl != null && + recordData.imagesUrl!.isNotEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FullScreenImagePage( + imageUrl: recordData.imagesUrl!, + ), + ), + ); + } }, child: SizedBox( width: itemW, @@ -314,15 +335,8 @@ class _VideoLogPageState extends State { } } - _buildVideoItem(RecordListData recordData) async { - final uint8list = await VideoThumbnail.thumbnailData( - video: recordData.videoUrl!, - imageFormat: ImageFormat.JPEG, - maxWidth: 128, - // specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio - quality: 25, - ); - return Image.memory(uint8list!); + _buildVideoItem(RecordListData recordData) { + return VideoThumbnail(videoUrl: recordData.videoUrl!); } _buildImageItem(RecordListData recordData) { diff --git a/lib/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart b/lib/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart index 28824226..9b25cdad 100755 --- a/lib/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart +++ b/lib/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; import 'package:star_lock/tools/dateTool.dart'; import 'package:video_player/video_player.dart'; -class ControlsOverlay extends StatelessWidget { +class ControlsOverlay extends StatefulWidget { const ControlsOverlay( {Key? key, required this.controller, required this.recordData}) : super(key: key); @@ -34,6 +35,17 @@ class ControlsOverlay extends StatelessWidget { final VideoPlayerController controller; final RecordListData recordData; + @override + State createState() => _ControlsOverlayState(); +} + +class _ControlsOverlayState extends State { + @override + void initState() { + // TODO: implement initState + super.initState(); + } + @override Widget build(BuildContext context) { return Stack( @@ -41,7 +53,7 @@ class ControlsOverlay extends StatelessWidget { AnimatedSwitcher( duration: const Duration(milliseconds: 50), reverseDuration: const Duration(milliseconds: 200), - child: controller.value.isPlaying + child: widget.controller.value.isPlaying ? const SizedBox.shrink() : Container( color: Colors.black26, @@ -59,7 +71,10 @@ class ControlsOverlay extends StatelessWidget { ), GestureDetector( onTap: () { - controller.value.isPlaying ? controller.pause() : controller.play(); + widget.controller.value.isPlaying + ? widget.controller.pause() + : widget.controller.play(); + setState(() {}); }, ), @@ -133,8 +148,8 @@ class ControlsOverlay extends StatelessWidget { // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - DateTool() - .dateToYMDHNString(recordData.operateDate.toString()), + DateTool().dateToYMDHNString( + widget.recordData.operateDate.toString()), style: TextStyle(color: Colors.white, fontSize: 20.sp)), Expanded(child: SizedBox(width: 10.w)), Container( @@ -187,7 +202,7 @@ class ControlsOverlay extends StatelessWidget { children: [ //暂停按钮 InkWell( - child: controller.value.isPlaying + child: widget.controller.value.isPlaying ? const Icon(Icons.pause, size: 30, color: Color(0xffefefef)) : const Icon(Icons.play_arrow, @@ -196,14 +211,14 @@ class ControlsOverlay extends StatelessWidget { // if(controller.value.isBuffering == false){ // return; // } - controller.value.isPlaying - ? controller.pause() - : controller.play(); + widget.controller.value.isPlaying + ? widget.controller.pause() + : widget.controller.play(); }, ), //当前播放进度 Text( - formatString(controller.value.position), + formatString(widget.controller.value.position), style: TextStyle( fontSize: 22.sp, color: const Color(0xffefefef)), ), @@ -211,19 +226,20 @@ class ControlsOverlay extends StatelessWidget { Expanded( child: Slider( activeColor: const Color(0xFFFFFFFF), - max: controller.value.duration.inMilliseconds + max: widget.controller.value.duration.inMilliseconds .truncateToDouble(), - value: controller.value.position.inMilliseconds + value: widget.controller.value.position.inMilliseconds .truncateToDouble(), onChanged: (newRating) { - controller + widget.controller .seekTo(Duration(milliseconds: newRating.truncate())); + setState(() {}); }, ), ), //总视频进度 Text( - formatString(controller.value.duration), + formatString(widget.controller.value.duration), style: TextStyle( fontSize: 22.sp, color: const Color(0xffefefef)), ), diff --git a/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_page.dart b/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_page.dart index 2791055d..16e4c12f 100755 --- a/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_page.dart +++ b/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_page.dart @@ -1,10 +1,13 @@ - +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart'; import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart'; +import 'package:star_lock/main/lockDetail/videoLog/widget/full_screenImage_page.dart'; +import 'package:star_lock/main/lockDetail/videoLog/widget/video_thumbnail.dart'; import 'package:star_lock/tools/dateTool.dart'; import 'package:video_player/video_player.dart'; @@ -39,6 +42,23 @@ class _VideoLogDetailPageState extends State { state.videoController.initialize(); } + void _initializeVideoPlayer(String videoUrl) async { + if (state.videoController != null) { + await state.videoController.dispose(); // 释放旧资源 + } + state.videoController = VideoPlayerController.networkUrl( + Uri.parse(videoUrl), + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + ); + + // 初始化完成后通知框架重新构建界面 + await state.videoController.initialize(); + state.videoController.addListener(() { + setState(() {}); + }); + setState(() {}); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -48,170 +68,111 @@ class _VideoLogDetailPageState extends State { haveBack: true, backgroundColor: AppColors.mainColor, ), - body: Column( - children: [ - AspectRatio( - aspectRatio: state.videoController.value.aspectRatio, - child: Stack( - alignment: Alignment.bottomCenter, + body: state.videoController.value.isInitialized + ? Column( children: [ - VideoPlayer(state.videoController), - ControlsOverlay( - controller: state.videoController, - recordData: state.recordData.value, - ), - if (state.videoController.value.isPlaying || - state.videoController.value.isBuffering) Container() else VideoProgressIndicator(state.videoController, - colors: VideoProgressColors( - playedColor: AppColors.mainColor), - allowScrubbing: true), - ], - ), - ), - Container( - margin: EdgeInsets.only(left: 20.w, top: 15.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - children: [ - Text( - DateTool().dateToYMDString( - state.recordData.value.operateDate.toString()), - style: TextStyle(fontSize: 20.sp)), - ], - ), - SizedBox(height: 15.h), - Row( - children: [ - videoItem(true, state.recordData.value), - ], - ), - ], - )), - Expanded( - child: ListView.builder( - itemCount: state.videoLogList.length, - itemBuilder: (BuildContext c, int index) { - CloudStorageData item = state.videoLogList[index]; - return Column( + Container( + height: 500.h, + decoration: BoxDecoration(color: Colors.black), + child: AspectRatio( + aspectRatio: 16 / 9, + child: Stack( + alignment: Alignment.bottomCenter, children: [ - Container( - margin: EdgeInsets.only( - left: 20.w, top: 15.w, bottom: 15.w), - child: Row(children: [ - Text(item.date ?? '', - style: TextStyle(fontSize: 20.sp)), - ])), - mainListView(index, item) + FittedBox( + child: SizedBox( + width: state.videoController.value.size.width, + height: state.videoController.value.size.height, + child: VideoPlayer(state.videoController), + ), + fit: BoxFit.cover, + ), + ControlsOverlay( + controller: state.videoController, + recordData: state.recordData.value, + ), + if (state.videoController.value.isPlaying || + state.videoController.value.isBuffering) + Container() + else + VideoProgressIndicator( + state.videoController, + colors: VideoProgressColors( + playedColor: AppColors.mainColor), + allowScrubbing: true, + ), ], - ); - })) - // Expanded( - // child: ListView.builder( - // itemCount: state.videoLogList.value.length, - // itemBuilder: (c, index) { - // return Column( - // children: [ - // Container( - // margin: EdgeInsets.only( - // left: 20.w, top: 15.w, bottom: 15.w), - // child: Row(children: [ - // Text("2023.10.23", - // style: TextStyle(fontSize: 20.sp)), - // ])), - // mainListView() - // ], - // ); - // }), - // ), - ], - ), + ), + ), + ), + _buildOther(), + ], + ) + : Center( + child: CircularProgressIndicator(), + ), ); } double itemW = (1.sw - 15.w * 4) / 3; double itemH = (1.sw - 15.w * 4) / 3 + 40.h; - Widget mainListView(int index, CloudStorageData itemData) { - return GridView.builder( - padding: EdgeInsets.only(left: 15.w, right: 15.w), - itemCount: itemData.recordList!.length, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - //横轴元素个数 - crossAxisCount: 3, - //纵轴间距 - mainAxisSpacing: 10.w, - // 横轴间距 - crossAxisSpacing: 15.w, - //子组件宽高长度比例 - childAspectRatio: itemW / itemH), - itemBuilder: (BuildContext context, int index) { - return videoItem(false, itemData.recordList![index]); + + Widget videoItem(RecordListData recordData) { + return GestureDetector( + onTap: () { + if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) { + state.recordData.value = recordData; + _initializeVideoPlayer(recordData.videoUrl!); + setState(() {}); + } else if (recordData.imagesUrl != null && + recordData.imagesUrl!.isNotEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => FullScreenImagePage( + imageUrl: recordData.imagesUrl!, + ), + ), + ); + } }, + child: SizedBox( + width: itemW, + height: itemH, + child: Column( + children: [ + Container( + width: itemW, + height: itemW, + margin: const EdgeInsets.all(0), + color: Colors.white, + child: ClipRRect( + borderRadius: BorderRadius.circular(10.w), + child: _buildImageOrVideoItem(recordData), + ), + ), + ], + ), + ), ); } - Widget videoItem(bool isPlay, RecordListData itemData) { - return SizedBox( - width: itemW, - height: itemH, - child: Stack( - children: [ - Column( - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(10.w), - child: Stack(children: [ - Container( - width: itemW, - height: itemW, - margin: const EdgeInsets.all(0), - color: Colors.white, - child: ClipRRect( - borderRadius: BorderRadius.circular(10.w), - child: Image( - fit: BoxFit.cover, - image: Image.network(itemData.imagesUrl ?? - 'images/icon_video_placeholder.jpg') - .image), - ), - ), - Positioned( - left: 8.w, - bottom: 5.h, - child: Text( - formatString(state.videoController.value.duration), - style: - TextStyle(color: Colors.grey, fontSize: 20.sp))), - Visibility( - visible: isPlay, - child: Positioned( - left: 0, - right: 0, - top: 0, - bottom: 0, - child: Container( - // padding: EdgeInsets.only(right: 10.w, left: 10.w), - color: const Color.fromRGBO(0, 0, 0, 0.3), - child: Center( - child: Text('播放中'.tr, - style: TextStyle( - color: Colors.white, fontSize: 22.sp))), - )), - ) - ]), - ), - SizedBox(height: 5.h), - Text( - DateTool().dateToYMDHNString(itemData.operateDate.toString()), - style: TextStyle(fontSize: 18.sp)) - ], - ), - ], - ), + _buildImageOrVideoItem(RecordListData recordData) { + if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) { + return _buildVideoItem(recordData); + } else { + return _buildImageItem(recordData); + } + } + + _buildVideoItem(RecordListData recordData) { + return VideoThumbnail(videoUrl: recordData.videoUrl!); + } + + _buildImageItem(RecordListData recordData) { + return Image.network( + recordData.imagesUrl!, + fit: BoxFit.cover, ); } @@ -225,4 +186,48 @@ class _VideoLogDetailPageState extends State { super.dispose(); state.videoController.dispose(); } + + _buildOther() { + return Expanded( + child: ListView.builder( + itemCount: state.videoLogList.length, + itemBuilder: (BuildContext c, int index) { + CloudStorageData item = state.videoLogList[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only(left: 20.w, top: 15.w, bottom: 15.w), + child: Row( + children: [ + Text(item.date ?? '', style: TextStyle(fontSize: 20.sp)), + ], + ), + ), + mainListView(index, item), + ], + ); + }, + ), + ); + } + + Widget mainListView(int index, CloudStorageData itemData) { + return GridView.builder( + itemCount: itemData.recordList!.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + //横轴元素个数 + crossAxisCount: 3, + ), + itemBuilder: (BuildContext context, int index) { + return _buildItem(itemData.recordList![index]); + }, + ); + } + + _buildItem(itemData) { + return videoItem(itemData); + } } diff --git a/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart b/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart index a936dd64..45afb135 100755 --- a/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart +++ b/lib/main/lockDetail/videoLog/videoLogDetail/videoLogDetail_state.dart @@ -16,5 +16,6 @@ class VideoLogDetailState { if (map['videoDataList'] != null) { videoLogList.value = map['videoDataList']; } + print('object'); } } diff --git a/lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart b/lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart new file mode 100644 index 00000000..9c516439 --- /dev/null +++ b/lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; + +class FullScreenImagePage extends StatelessWidget { + final String imageUrl; + + FullScreenImagePage({required this.imageUrl}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + child: PhotoView( + imageProvider: NetworkImage(imageUrl), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/main/lockDetail/videoLog/widget/video_player_widget.dart b/lib/main/lockDetail/videoLog/widget/video_player_widget.dart new file mode 100644 index 00000000..fb079abc --- /dev/null +++ b/lib/main/lockDetail/videoLog/widget/video_player_widget.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart'; +import 'package:star_lock/main/lockDetail/videoLog/videoLogDetail/controlsOverlay_page.dart'; +import 'package:video_player/video_player.dart'; + +class VideoPlayerWidget extends StatefulWidget { + final String videoUrl; + final RecordListData recordListData; + + VideoPlayerWidget({required this.videoUrl, required this.recordListData}); + + @override + _VideoPlayerWidgetState createState() => _VideoPlayerWidgetState(); +} + +class _VideoPlayerWidgetState extends State { + late VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + // 初始化 VideoPlayerController + _controller = VideoPlayerController.network(widget.videoUrl) + ..initialize().then((_) { + // 视频初始化完成后更新UI + setState(() {}); + }); + } + + @override + void dispose() { + // 清理资源 + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 200, // 固定高度为200像素 + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + // 使用 FittedBox 来适配视频尺寸 + Center( + child: _controller.value.isInitialized + ? AspectRatio( + aspectRatio: _controller.value.aspectRatio, // 保持视频的原始宽高比 + child: FittedBox( + fit: BoxFit.cover, // 确保视频填满整个区域,可能会裁剪部分内容 + child: SizedBox( + width: _controller.value.size.width, + height: _controller.value.size.height, + child: VideoPlayer(_controller), + ), + ), + ) + : Container(), // 加载中显示转圈 + ), + ControlsOverlay( + controller: _controller, + recordData: widget.recordListData, + ), + // 控制覆盖层组件 + if (!_controller.value.isPlaying) + VideoProgressIndicator( + _controller, + colors: VideoProgressColors(playedColor: Colors.red), + allowScrubbing: true, // 允许用户拖动进度条进行快进/快退 + ), + ], + ), + ); + } +} diff --git a/lib/main/lockDetail/videoLog/widget/video_thumbnail.dart b/lib/main/lockDetail/videoLog/widget/video_thumbnail.dart new file mode 100644 index 00000000..de2259a6 --- /dev/null +++ b/lib/main/lockDetail/videoLog/widget/video_thumbnail.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:video_player/video_player.dart'; + +class VideoThumbnail extends StatefulWidget { + final String videoUrl; + + VideoThumbnail({required this.videoUrl}); + + @override + _VideoThumbnailState createState() => _VideoThumbnailState(); +} + +class _VideoThumbnailState extends State { + late VideoPlayerController _controller; + late Future _initializeVideoPlayerFuture; + + @override + void initState() { + super.initState(); + // 创建并初始化 VideoPlayerController + _controller = VideoPlayerController.network(widget.videoUrl) + ..initialize().then((_) { + // 当视频控制器初始化完成后,更新UI以便显示第一帧 + setState(() {}); + }); + } + + @override + void dispose() { + // 清理资源 + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + // onTap: () { + // setState(() { + // if (_controller.value.isPlaying) { + // _controller.pause(); + // } else { + // _controller.play(); + // } + // }); + // }, + child: Stack( + alignment: Alignment.center, + children: [ + // 如果视频已经初始化,则显示视频玩家 + _controller.value.isInitialized + ? AspectRatio( + aspectRatio: 1 / 1, // 强制正方形比例 + child: FittedBox( + fit: BoxFit.cover, // 确保视频封面铺满空间 + child: SizedBox( + width: _controller.value.size.width, + height: _controller.value.size.height, + child: VideoPlayer(_controller), + ), + ), + ) + : Center( + child: CircularProgressIndicator(), // 加载中显示转圈 + ), + if (!_controller.value.isPlaying && _controller.value.isInitialized) + Icon(Icons.play_arrow_rounded, + size: 80, color: Colors.white.withOpacity(0.8)), + ], + ), + ); + } +}