app-starlock/lib/main/lockDetail/doorLockLog/doorLockLog_page.dart

624 lines
20 KiB
Dart
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flustars/flustars.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:star_lock/appRouters.dart';
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_image.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';
import 'package:star_lock/tools/showTipView.dart';
import 'package:timelines/timelines.dart';
import '../../../app_settings/app_colors.dart';
import '../../../tools/appRouteObserver.dart';
import '../../../tools/titleAppBar.dart';
class DoorLockLogPage extends StatefulWidget {
const DoorLockLogPage({Key? key}) : super(key: key);
@override
State<DoorLockLogPage> createState() => _DoorLockLogPageState();
}
class _DoorLockLogPageState extends State<DoorLockLogPage> with RouteAware {
final DoorLockLogLogic logic = Get.put(DoorLockLogLogic());
final DoorLockLogState state = Get.find<DoorLockLogLogic>().state;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.mainBackgroundColor,
appBar: TitleAppBar(
barTitle: '操作记录'.tr,
haveBack: true,
backgroundColor: AppColors.mainColor,
actionsList: <Widget>[
Visibility(
visible: CommonDataManage().currentKeyInfo.isLockOwner == 1 ||
CommonDataManage().currentKeyInfo.keyRight == 1,
child: GestureDetector(
child: Image.asset(
'images/icon_tips_Q.png',
width: 34.w,
height: 32.w,
color: Colors.white,
),
onTap: () {
ShowTipView().showSureAlertDialog(
'1.锁没有联网密码、IC卡、指纹等开门记录无法实时上传可以点击右上角按钮然后读取记录。'.tr +
'\n' +
'2.如果您需要保留历史记录,可以点击右上角按钮,然后导出记录'.tr,
tipTitle: '看不到操作记录,可能原因有'.tr,
sureStr: '我知道了'.tr);
},
)),
Visibility(
visible: CommonDataManage().currentKeyInfo.isLockOwner == 1 ||
CommonDataManage().currentKeyInfo.keyRight == 1,
child: PopupMenuButton<String>(
onSelected: _onMenuItemSelected,
color: Colors.black,
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<String>>[
_buildCustomPopupMenuItem('读取记录'.tr),
if (CommonDataManage().currentKeyInfo.isLockOwner == 1)
const PopupMenuDivider(),
if (CommonDataManage().currentKeyInfo.isLockOwner == 1)
_buildCustomPopupMenuItem('清空记录'.tr),
const PopupMenuDivider(),
_buildCustomPopupMenuItem('导出记录'.tr),
];
},
icon: Image.asset(
'images/icon_bar_more.png',
height: 30.h,
width: 10.w,
),
offset: Offset(0, 70.h), // 设置弹出框位置偏移量
),
),
],
),
body: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
topAdvancedCalendarWidget(),
Divider(
height: 1,
color: AppColors.greyLineColor,
indent: 30.w,
endIndent: 30.w,
),
eventDropDownWidget(),
Expanded(child: timeLineView())
],
),
);
}
//
PopupMenuItem<String> _buildCustomPopupMenuItem(String value) {
return PopupMenuItem<String>(
value: value,
height: 46.h,
child: SizedBox(
height: 46.h,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
value,
style: TextStyle(color: Colors.white, fontSize: 22.sp),
),
],
),
),
);
}
void _onMenuItemSelected(String value) {
if (value == '读取记录'.tr) {
logic.getLockRecordLastUploadDataTime();
} else if (value == '清空记录'.tr) {
ShowCupertinoAlertView().showClearOperationRecordAlert(clearClick: () {
logic.clearOperationRecordRequest();
});
} else if (value == '导出记录'.tr) {
showDialog(
context: context,
builder: (BuildContext context) {
return ExportRecordDialog(
onExport: (String filePath) {
Get.toNamed(Routers.exportSuccessPage,
arguments: <String, String>{'filePath': filePath});
},
);
},
);
}
}
// switch (value) {
// case "读取记录".tr:
// {
// logic.mockNetworkDataRequest(isRefresh: true);
// }
// break;
// case '清空记录'.tr:
// {
// ShowCupertinoAlertView().showClearOperationRecordAlert(
// clearClick: () {
// logic.clearOperationRecordRequest();
// });
// }
// break;
// case '导出记录':
// {
// showDialog(
// context: context,
// builder: (BuildContext context) {
// return ExportRecordDialog(
// onExport: (String filePath) {
// Get.toNamed(Routers.exportSuccessPage,
// arguments: <String, String>{'filePath': filePath});
// },
// );
// },
// );
// }
// break;
// }
// }
//顶部日历小部件
Widget topAdvancedCalendarWidget() {
final ThemeData theme = Theme.of(context);
return Theme(
data: theme.copyWith(
textTheme: theme.textTheme.copyWith(
titleMedium: theme.textTheme.titleMedium!.copyWith(
fontSize: 16,
color: theme.colorScheme.secondary,
),
bodyLarge: theme.textTheme.bodyLarge!.copyWith(
fontSize: 14,
color: Colors.black54,
),
bodyMedium: theme.textTheme.bodyMedium!.copyWith(
fontSize: 12,
color: Colors.black87,
),
),
primaryColor: AppColors.mainColor,
highlightColor: Colors.yellow,
disabledColor: Colors.grey,
),
child: Stack(
children: <Widget>[
AdvancedCalendar(
controller: state.calendarControllerCustom,
events: state.events,
weekLineHeight: 48.0,
startWeekDay: 1,
innerDot: true,
keepLineSize: true,
calendarTextStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w400,
height: 1.3125,
letterSpacing: 0,
),
),
Positioned(
top: 8.0,
right: 8.0,
child: Obx(() => Text(
'${state.currentSelectDate.value.year}${''.tr}${state.currentSelectDate.value.month}${''.tr}',
style: theme.textTheme.titleMedium!.copyWith(
fontSize: 16,
color: theme.colorScheme.secondary,
),
)),
),
],
),
);
}
//事件下拉框组件
Widget eventDropDownWidget() {
return Container(
margin: EdgeInsets.only(top: 20.h, left: 30.w, bottom: 10.h, right: 20.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Obx(
() => XSDropDownWidget(
items: state.getDropDownItemList,
value: state.dropdownTitle.value,
valueChanged: (value) async {
state.dropdownValue.value = int.parse(value);
await logic.mockNetworkDataRequest(isRefresh: true);
},
),
),
],
),
);
}
//时间轴组件
Widget timeLineView() {
return Container(
margin: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20.h, top: 20.h),
decoration: BoxDecoration(
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())),
);
}
String formatTimestampToHHmm(int timestampMs) {
// 1. 将毫秒时间戳转换为秒DateTime 需要秒级时间戳)
int timestampSec = timestampMs ~/ 1000;
// 2. 创建 DateTime 对象
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampMs);
// 3. 使用 DateFormat 格式化为 "HH:mm"
DateFormat formatter = DateFormat('HH:mm');
return formatter.format(dateTime);
}
String _buildIDByType(DoorLockLogDataItem item) {
final recordType = item.recordType;
switch (recordType) {
case 10:
if (item.username != null && item.username != '') {
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'指纹'.tr +
'开锁'.tr +
'ID${item.username}';
} else {
return item.recordStr ?? '';
}
case 20:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'密码'.tr +
'开锁'.tr +
'ID${item.username}';
case 30:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
''.tr +
'开锁'.tr +
'ID${item.username}';
case 40:
if (item.username != null && item.username != '') {
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'蓝牙'.tr +
'开锁'.tr +
'' +
'ID'.tr +
'${item.username}';
} else {
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'蓝牙'.tr +
'开锁'.tr +
'' +
'ID'.tr +
'${item.userid}';
}
case 50:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'组合模式'.tr +
'开锁'.tr +
'ID${item.username}';
case 60:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'添加指纹'.tr +
'ID${item.username}';
case 70:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'添加密码'.tr +
'ID${item.username}';
case 80:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'添加卡'.tr +
'ID${item.username}';
case 90:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'删除'.tr +
'指纹'.tr +
'ID${item.username}';
case 100:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'删除'.tr +
'密码'.tr +
'ID${item.username}';
case 110:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'删除'.tr +
''.tr +
'ID${item.username}';
case 160:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'人脸'.tr +
'开锁'.tr +
'ID${item.username}';
case 190:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'胁迫指纹'.tr +
'开锁'.tr +
'ID${item.username}';
case 200:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'胁迫密码'.tr +
'开锁'.tr +
'ID${item.username}';
case 210:
return '${formatTimestampToHHmm(item.operateDate!)} ' +
'胁迫卡片'.tr +
'开锁'.tr +
'ID${item.username}';
default:
return item.recordStr ?? '';
}
}
Color _buildTextColorByType(DoorLockLogDataItem item) {
final recordType = item.recordType;
switch (recordType) {
case 120:
case 150:
case 130:
case 190:
case 200:
case 210:
case 220:
return Colors.red;
default:
return Colors.black;
}
}
TimelineTileBuilder _timelineBuilderWidget() {
return TimelineTileBuilder.fromStyle(
contentsAlign: ContentsAlign.basic,
itemCount: state.lockLogItemList.length,
contentsBuilder: (BuildContext context, int index) {
final DoorLockLogDataItem timelineData = state.lockLogItemList[index];
return GestureDetector(
onTap: () {
Get.toNamed(
Routers.doorLockLogDetailPage,
arguments: {'doorLockLogDataItem': timelineData},
);
},
child: Padding(
padding: EdgeInsets.only(left: 20.w, top: 20.h, right: 20.w),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// 使用 SingleChildScrollView 实现横向滚动
SingleChildScrollView(
scrollDirection: Axis.horizontal, // 横向滚动
child: Text(
_buildIDByType(timelineData),
textAlign: TextAlign.left,
style: TextStyle(
color: _buildTextColorByType(timelineData),
fontSize: 24.sp,
fontWeight: FontWeight.w600,
),
// 关键:禁用换行,强制单行显示
maxLines: 1,
// 可选:添加省略号(如果文本过长)
overflow: TextOverflow.ellipsis,
),
),
SizedBox(
height: 12.h,
),
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,
),
],
),
),
);
},
);
}
Widget videoItem(RecordListData recordData) {
return GestureDetector(
onTap: () {
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
final lockLogItemList = state.lockLogItemList.value;
final list = lockLogItemList
.where((e) =>
(e.videoUrl != null && e.videoUrl!.isNotEmpty) ||
(e.imagesUrl != null && e.imagesUrl!.isNotEmpty))
.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: ((recordData.imagesUrl != null && recordData.imagesUrl != '') ||
(recordData.videoUrl != null && recordData.videoUrl != ''))
? 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),
),
)
: SizedBox.shrink(),
);
}
_buildImageOrVideoItem(RecordListData recordData) {
if (recordData.videoUrl != null && recordData.videoUrl!.isNotEmpty) {
return _buildVideoItem(recordData);
} else if (recordData.imagesUrl != null &&
recordData.imagesUrl!.isNotEmpty) {
return _buildImageItem(recordData);
} else {
return SizedBox.shrink();
}
}
_buildVideoItem(RecordListData recordData) {
return VideoThumbnailImage(videoUrl: recordData.videoUrl!);
}
_buildImageItem(RecordListData recordData) {
return RotatedBox(
quarterTurns: -1,
child: Image.network(
recordData.imagesUrl!,
fit: BoxFit.cover,
errorBuilder:
(BuildContext context, Object error, StackTrace? stackTrace) {
// 图片加载失败时显示错误图片
return Image.asset(
'images/icon_unHaveData.png', // 错误图片路径
fit: BoxFit.cover,
);
},
),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
/// 路由订阅
AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(context)!);
}
@override
void dispose() {
/// 取消路由订阅
AppRouteObserver().routeObserver.unsubscribe(this);
super.dispose();
}
/// 从上级界面进入 当前界面即将出现
@override
void didPush() {
super.didPush();
state.ifCurrentScreen.value = true;
}
/// 返回上一个界面 当前界面即将消失
@override
void didPop() {
super.didPop();
logic.cancelBlueConnetctToastTimer();
if (EasyLoading.isShow) {
EasyLoading.dismiss(animation: true);
}
state.ifCurrentScreen.value = false;
}
/// 从下级返回 当前界面即将出现
@override
void didPopNext() {
super.didPopNext();
state.ifCurrentScreen.value = true;
}
/// 进入下级界面 当前界面即将消失
@override
void didPushNext() {
super.didPushNext();
logic.cancelBlueConnetctToastTimer();
if (EasyLoading.isShow) {
EasyLoading.dismiss(animation: true);
}
state.ifCurrentScreen.value = false;
}
}