fix:调整视频缩略图、调整操作记录查询逻辑

This commit is contained in:
liyi 2025-08-19 10:16:20 +08:00
parent 335f66a0b5
commit 9fdf8377ee
3 changed files with 130 additions and 63 deletions

View File

@ -236,7 +236,7 @@ class DoorLockLogLogic extends BaseGetXController {
lockId: state.keyInfos.value.lockId!,
lockEventType: state.dropdownValue.value,
pageNo: pageNo,
pageSize: 100,
pageSize: 1000,
startDate: state.startDate.value,
endDate: state.endDate.value);
if (entity.errorCode!.codeIsSuccessful) {
@ -361,6 +361,7 @@ class DoorLockLogLogic extends BaseGetXController {
@override
Future<void> onInit() async {
_setWeekRange();
super.onInit();
//
@ -373,6 +374,35 @@ class DoorLockLogLogic extends BaseGetXController {
}
}
void _setWeekRange() {
final now = DateTime.now();
// 1=7=
int weekday = now.weekday; // 1-7
//
// : 0, : 1, ..., : 6
int daysToSubtract = weekday - 1; // 1
// 00:00:00.000
DateTime startOfWeek = DateTime(now.year, now.month, now.day)
.subtract(Duration(days: daysToSubtract));
// 23:59:59.999
DateTime endOfWeek = startOfWeek
.add(Duration(days: 6)) // 6
.add(Duration(hours: 23, minutes: 59, seconds: 59, milliseconds: 999));
//
state.startDate.value = startOfWeek.millisecondsSinceEpoch;
state.endDate.value = endOfWeek.millisecondsSinceEpoch;
}
//
void refreshWeek() {
_setWeekRange();
}
@override
Future<void> onClose() async {
super.onClose();

View File

@ -37,8 +37,39 @@ class DoorLockLogPage extends StatefulWidget {
}
class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
final ScrollController _scrollController = ScrollController();
final DoorLockLogLogic logic = Get.put(DoorLockLogLogic());
final DoorLockLogState state = Get.find<DoorLockLogLogic>().state;
bool _isAtBottom = false;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
final max = _scrollController.position.maxScrollExtent;
final current = _scrollController.position.pixels;
AppLog.log('current:${current}');
// 5
if (current >= max - 5) {
if (!_isAtBottom) {
setState(() {
_isAtBottom = true;
});
print('✅ 已滑动到 timelines 列表底部!');
//
}
} else {
if (_isAtBottom) {
setState(() {
_isAtBottom = false;
});
}
}
}
@override
Widget build(BuildContext context) {
@ -104,6 +135,27 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
Expanded(child: timeLineView())
],
),
floatingActionButton: Visibility(
visible: _isAtBottom,
child: FloatingActionButton(
onPressed: () {
_scrollController.animateTo(
0.0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(48.w),
),
backgroundColor: AppColors.mainColor,
child: Icon(
Icons.arrow_upward,
color: Colors.white,
size: 48.w,
),
),
),
);
}
@ -193,39 +245,33 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
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,
),
() => state.lockLogItemList.isNotEmpty
? Timeline.tileBuilder(
controller: _scrollController,
builder: _timelineBuilderWidget(),
theme: TimelineThemeData(
nodePosition: 0.04, //
connectorTheme: const ConnectorThemeData(
thickness: 1.0,
color: AppColors.greyLineColor,
indent: 0.5,
),
)
: NoData(),
),
indicatorTheme: const IndicatorThemeData(
size: 8.0,
color: AppColors.greyLineColor,
position: 0.4,
),
),
)
: NoData(),
),
);
}
String formatTimestampToDateTimeYYYYMMDD(int timestampMs) {
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampMs);
DateFormat formatter = DateFormat('MM${''.tr}dd${''.tr}'); // 2025-08-18 14:30
DateFormat formatter =
DateFormat('MM${''.tr}dd${''.tr}'); // 2025-08-18 14:30
return formatter.format(dateTime);
}
@ -377,7 +423,6 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
Text(
'${formatTimestampToDateTimeYYYYMMDD(timelineData.operateDate!)}',
style: TextStyle(
fontSize: 20.sp,
)),
// 使 SingleChildScrollView

View File

@ -1,53 +1,47 @@
import 'dart:io'; // dart:io 使 File
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart'; // path_provider
import 'package:video_thumbnail/video_thumbnail.dart'; // video_thumbnail
import 'package:path_provider/path_provider.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
class VideoThumbnailImage extends StatefulWidget {
final String videoUrl;
VideoThumbnailImage({required this.videoUrl});
const VideoThumbnailImage({Key? key, required this.videoUrl})
: super(key: key);
@override
_VideoThumbnailState createState() => _VideoThumbnailState();
}
class _VideoThumbnailState extends State<VideoThumbnailImage> {
final Map<String, String> _thumbnailCache = {}; //
late Future<String?> _thumbnailFuture; // Future
// 使 static
static final Map<String, Future<String?>> _pendingThumbnails = {};
late Future<String?> _thumbnailFuture;
@override
void initState() {
super.initState();
_thumbnailFuture = _generateThumbnail(); // initState Future
// URL Future
_thumbnailFuture = _pendingThumbnails.putIfAbsent(widget.videoUrl, () {
return _generateThumbnail(widget.videoUrl);
});
}
//
Future<String?> _generateThumbnail() async {
// per URL
Future<String?> _generateThumbnail(String url) async {
try {
//
if (_thumbnailCache.containsKey(widget.videoUrl)) {
return _thumbnailCache[widget.videoUrl];
}
//
final tempDir = await getTemporaryDirectory();
final thumbnailPath = await VideoThumbnail.thumbnailFile(
video: widget.videoUrl,
// URL
final thumbnail = await VideoThumbnail.thumbnailFile(
video: url,
thumbnailPath: tempDir.path,
//
imageFormat: ImageFormat.JPEG,
//
maxHeight: 200,
//
quality: 100, // (0-100)
quality: 100,
);
//
_thumbnailCache[widget.videoUrl] = thumbnailPath!;
return thumbnailPath;
return thumbnail;
} catch (e) {
print('Failed to generate thumbnail: $e');
return null;
@ -57,27 +51,25 @@ class _VideoThumbnailState extends State<VideoThumbnailImage> {
@override
Widget build(BuildContext context) {
return FutureBuilder<String?>(
future: _thumbnailFuture, // Future
future: _thumbnailFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
//
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError || !snapshot.hasData) {
//
return Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
return Center(
child: Image.asset(
'images/icon_unHaveData.png',
fit: BoxFit.cover,
),
);
} else {
//
final thumbnailPath = snapshot.data!;
return Stack(
alignment: Alignment.center,
children: <Widget>[
RotatedBox(
quarterTurns: -1,
child: Image.file(
File(thumbnailPath), //
File(snapshot.data!),
width: 200,
height: 200,
fit: BoxFit.cover,