fix:完善视频日志、操作记录页面功能
This commit is contained in:
parent
6ecf89088e
commit
50d7ea78d9
@ -248,6 +248,7 @@ class DoorLockLogLogic extends BaseGetXController {
|
||||
|
||||
/// 刷新门锁日志列表
|
||||
StreamSubscription? _getDoorLockLogListRefreshUIEvent;
|
||||
|
||||
void _getDoorLockLogListRefreshUIAction() {
|
||||
_getDoorLockLogListRefreshUIEvent = eventBus
|
||||
.on<DoorLockLogListRefreshUI>()
|
||||
|
||||
@ -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<DoorLockLogPage> with RouteAware {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// switch (value) {
|
||||
// case "读取记录".tr:
|
||||
// {
|
||||
@ -256,31 +261,24 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> 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<DoorLockLogPage> with RouteAware {
|
||||
SizedBox(
|
||||
height: 10.h,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Get.toNamed(Routers.videoLogDetailPage);
|
||||
},
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
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<DoorLockLogPage> 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: <String, Object>{
|
||||
'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: <Widget>[
|
||||
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();
|
||||
|
||||
@ -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<Uint8List?> 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();
|
||||
|
||||
@ -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<VideoLogPage> {
|
||||
final VideoLogLogic logic = Get.put(VideoLogLogic());
|
||||
final VideoLogState state = Get.find<VideoLogLogic>().state;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// TODO: implement initState
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -275,10 +284,22 @@ class _VideoLogPageState extends State<VideoLogPage> {
|
||||
Widget videoItem(RecordListData recordData) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
|
||||
'recordData': recordData,
|
||||
'videoDataList': state.videoLogList.value
|
||||
});
|
||||
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
|
||||
Get.toNamed(Routers.videoLogDetailPage, arguments: <String, Object>{
|
||||
'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<VideoLogPage> {
|
||||
}
|
||||
}
|
||||
|
||||
_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) {
|
||||
|
||||
@ -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<ControlsOverlay> createState() => _ControlsOverlayState();
|
||||
}
|
||||
|
||||
class _ControlsOverlayState extends State<ControlsOverlay> {
|
||||
@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)),
|
||||
),
|
||||
|
||||
@ -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<VideoLogDetailPage> {
|
||||
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<VideoLogDetailPage> {
|
||||
haveBack: true,
|
||||
backgroundColor: AppColors.mainColor,
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: state.videoController.value.aspectRatio,
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
body: state.videoController.value.isInitialized
|
||||
? Column(
|
||||
children: <Widget>[
|
||||
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: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
DateTool().dateToYMDString(
|
||||
state.recordData.value.operateDate.toString()),
|
||||
style: TextStyle(fontSize: 20.sp)),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 15.h),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
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: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
left: 20.w, top: 15.w, bottom: 15.w),
|
||||
child: Row(children: <Widget>[
|
||||
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: <Widget>[
|
||||
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: <Widget>[
|
||||
Column(
|
||||
children: <Widget>[
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10.w),
|
||||
child: Stack(children: <Widget>[
|
||||
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<VideoLogDetailPage> {
|
||||
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: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 20.w, top: 15.w, bottom: 15.w),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,5 +16,6 @@ class VideoLogDetailState {
|
||||
if (map['videoDataList'] != null) {
|
||||
videoLogList.value = map['videoDataList'];
|
||||
}
|
||||
print('object');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
75
lib/main/lockDetail/videoLog/widget/video_player_widget.dart
Normal file
75
lib/main/lockDetail/videoLog/widget/video_player_widget.dart
Normal file
@ -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<VideoPlayerWidget> {
|
||||
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: <Widget>[
|
||||
// 使用 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, // 允许用户拖动进度条进行快进/快退
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
73
lib/main/lockDetail/videoLog/widget/video_thumbnail.dart
Normal file
73
lib/main/lockDetail/videoLog/widget/video_thumbnail.dart
Normal file
@ -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<VideoThumbnail> {
|
||||
late VideoPlayerController _controller;
|
||||
late Future<void> _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: <Widget>[
|
||||
// 如果视频已经初始化,则显示视频玩家
|
||||
_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)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user