207 lines
7.6 KiB
Dart
207 lines
7.6 KiB
Dart
import 'dart:async';
|
|
import 'dart:math';
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:star_lock/app_settings/app_colors.dart';
|
|
import 'package:star_lock/network/api_repository.dart';
|
|
|
|
class RemoteUnlockOverlay {
|
|
static Future<void> show({required int lockId, required String lockAlias, required int operateDate, int timeoutSeconds = 60}) async {
|
|
await Get.to(() => RemoteUnlockOverlayPage(lockId: lockId, lockAlias: lockAlias, operateDate: operateDate, timeoutSeconds: timeoutSeconds), opaque: false);
|
|
}
|
|
}
|
|
|
|
class RemoteUnlockOverlayPage extends StatefulWidget {
|
|
const RemoteUnlockOverlayPage({required this.lockId, required this.lockAlias, required this.operateDate, this.timeoutSeconds = 60, Key? key}) : super(key: key);
|
|
final int lockId;
|
|
final String lockAlias;
|
|
final int operateDate;
|
|
final int timeoutSeconds;
|
|
|
|
@override
|
|
State<RemoteUnlockOverlayPage> createState() => _RemoteUnlockOverlayPageState();
|
|
}
|
|
|
|
class _RemoteUnlockOverlayPageState extends State<RemoteUnlockOverlayPage> {
|
|
late int seconds;
|
|
Timer? timer;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
seconds = widget.timeoutSeconds;
|
|
timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (seconds <= 0) {
|
|
t.cancel();
|
|
timer?.cancel();
|
|
setState(() {
|
|
seconds = 0;
|
|
});
|
|
Get.back();
|
|
return;
|
|
}
|
|
setState(() {
|
|
seconds = seconds - 1;
|
|
});
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
timer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final double cardWidth = 0.9.sw;
|
|
final double ringSize = 260.r;
|
|
return PopScope(
|
|
canPop: false,
|
|
child: Stack(
|
|
children: [
|
|
Positioned.fill(
|
|
child: BackdropFilter(
|
|
filter: ui.ImageFilter.blur(sigmaX: 3, sigmaY: 3),
|
|
child: Container(color: Colors.black.withOpacity(0.18)),
|
|
),
|
|
),
|
|
Center(
|
|
child: Container(
|
|
width: cardWidth,
|
|
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 20.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.08), blurRadius: 20.r, offset: const Offset(0, 6))],
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.lock, color: AppColors.mainColor, size: 24.r),
|
|
SizedBox(width: 8.w),
|
|
Expanded(child: Text(widget.lockAlias, style: TextStyle(fontSize: 22.sp, color: AppColors.blackColor))),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Text('远程开锁请求'.tr, style: TextStyle(fontSize: 28.sp, color: AppColors.blackColor, fontWeight: FontWeight.w700)),
|
|
SizedBox(height: 20.h),
|
|
SizedBox(
|
|
width: ringSize,
|
|
height: ringSize,
|
|
child: Stack(
|
|
alignment: Alignment.center,
|
|
children: [
|
|
_DottedCountdownRing(seconds: seconds, size: ringSize),
|
|
Text('${seconds} s', style: TextStyle(fontSize: 48.sp, color: AppColors.mainColor, fontWeight: FontWeight.w700)),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text('请确认是否允许该门锁进行远程开锁'.tr, style: TextStyle(fontSize: 18.sp, color: AppColors.btnDisableColor)),
|
|
SizedBox(height: 8.h),
|
|
Text('请求将在倒计时结束后自动取消'.tr, style: TextStyle(fontSize: 16.sp, color: AppColors.btnDisableColor)),
|
|
SizedBox(height: 20.h),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: _reject,
|
|
child: Container(
|
|
width: 0.35.sw,
|
|
height: 48.h,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(color: Colors.redAccent, borderRadius: BorderRadius.circular(8.r)),
|
|
child: Text('拒绝'.tr, style: TextStyle(color: Colors.white, fontSize: 22.sp)),
|
|
),
|
|
),
|
|
SizedBox(width: 20.w),
|
|
GestureDetector(
|
|
onTap: _accept,
|
|
child: Container(
|
|
width: 0.35.sw,
|
|
height: 48.h,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(color: AppColors.mainColor, borderRadius: BorderRadius.circular(8.r)),
|
|
child: Text('同意'.tr, style: TextStyle(color: Colors.white, fontSize: 22.sp)),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 12.w),
|
|
decoration: BoxDecoration(color: const Color(0xFFF7F9FC), borderRadius: BorderRadius.circular(8.r)),
|
|
child: Row(children: [Icon(Icons.info_outline, color: AppColors.mainColor, size: 18.r), SizedBox(width: 6.w), Expanded(child: Text('远程开锁需确保现场安全与人员确认'.tr, style: TextStyle(color: AppColors.btnDisableColor, fontSize: 14.sp)))]),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _accept() async {
|
|
timer?.cancel();
|
|
await ApiRepository.to.remoteOpenLock(lockId: widget.lockId.toString(), timeOut: 60);
|
|
Get.back();
|
|
}
|
|
|
|
void _reject() {
|
|
timer?.cancel();
|
|
Get.back();
|
|
}
|
|
}
|
|
|
|
class _DottedCountdownRing extends StatelessWidget {
|
|
const _DottedCountdownRing({required this.seconds, required this.size});
|
|
final int seconds;
|
|
final double size;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final double progress = seconds.clamp(0, 60) / 60.0;
|
|
return CustomPaint(size: Size(size, size), painter: _DottedCirclePainter(progress: progress));
|
|
}
|
|
}
|
|
|
|
class _DottedCirclePainter extends CustomPainter {
|
|
_DottedCirclePainter({required this.progress});
|
|
final double progress;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final Paint paint = Paint()
|
|
..color = AppColors.mainColor
|
|
..strokeWidth = 4.w
|
|
..style = PaintingStyle.stroke;
|
|
final double radius = size.width / 2 - 2.w;
|
|
final Offset center = Offset(size.width / 2, size.height / 2);
|
|
final Path path = Path();
|
|
final double angle = -pi / 2 + 2 * pi * progress.clamp(0.0, 1.0);
|
|
for (int i = 0; i <= 60; i++) {
|
|
final double startAngle = (2 * pi / 60) * i - pi / 2;
|
|
final double endAngle = startAngle + (2 * pi / 60) * 0.7;
|
|
if (startAngle <= angle) {
|
|
final Path segmentPath = Path();
|
|
segmentPath.arcTo(Rect.fromCircle(center: center, radius: radius), startAngle, endAngle - startAngle, true);
|
|
path.addPath(segmentPath, Offset.zero);
|
|
}
|
|
}
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
|
}
|