228 lines
7.0 KiB
Dart
228 lines
7.0 KiB
Dart
/// ============================================================
|
||
/// 【PausePopup】暂停答题弹窗
|
||
/// (1:1 迁移 Vue components/PausePopup/PausePopup.vue)
|
||
///
|
||
/// - 遮罩 rgba(0,0,0,.7),点击任意位置关闭(onClose)
|
||
/// - 组件独立秒表:每次 show=true 从 0 开始计时,HH:MM:SS(不足 1 小时隐去小时)
|
||
/// - 文案:共 {totalCount} 道题,还剩 {remainingCount} 道题待完成
|
||
/// ============================================================
|
||
library;
|
||
|
||
import 'dart:async';
|
||
|
||
import 'package:cached_network_image/cached_network_image.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
import 'package:new_learn/core/utils/img_url.dart';
|
||
|
||
import 'exercise_styles.dart';
|
||
|
||
class PausePopup extends StatefulWidget {
|
||
const PausePopup({
|
||
super.key,
|
||
required this.show,
|
||
this.totalCount = 20,
|
||
this.remainingCount = 0,
|
||
this.onClose,
|
||
});
|
||
|
||
final bool show;
|
||
final int totalCount;
|
||
final int remainingCount;
|
||
final VoidCallback? onClose;
|
||
|
||
@override
|
||
State<PausePopup> createState() => _PausePopupState();
|
||
}
|
||
|
||
class _PausePopupState extends State<PausePopup> {
|
||
int _seconds = 0;
|
||
Timer? _timer;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_syncTimer(widget.show);
|
||
}
|
||
|
||
@override
|
||
void didUpdateWidget(covariant PausePopup oldWidget) {
|
||
super.didUpdateWidget(oldWidget);
|
||
if (oldWidget.show != widget.show) {
|
||
_syncTimer(widget.show);
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_stopTimer();
|
||
super.dispose();
|
||
}
|
||
|
||
void _syncTimer(bool show) {
|
||
if (show) {
|
||
_startTimer();
|
||
} else {
|
||
_stopTimer();
|
||
}
|
||
}
|
||
|
||
void _startTimer() {
|
||
_timer?.cancel();
|
||
_seconds = 0;
|
||
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||
if (mounted) setState(() => _seconds++);
|
||
});
|
||
}
|
||
|
||
void _stopTimer() {
|
||
_timer?.cancel();
|
||
_timer = null;
|
||
}
|
||
|
||
String _two(int v) => v < 10 ? '0$v' : '$v';
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
if (!widget.show) return const SizedBox.shrink();
|
||
final isPad = isPadDevice(context);
|
||
|
||
final hours = _seconds ~/ 3600;
|
||
final showHours = hours > 0;
|
||
final minutes = (_seconds % 3600) ~/ 60;
|
||
final secs = _seconds % 60;
|
||
|
||
final unitW = isPad ? 49.w : 44.w; // 88/98rpx
|
||
final unitH = isPad ? 45.h : 40.h; // 80/90rpx
|
||
|
||
return Positioned.fill(
|
||
child: GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
onTap: widget.onClose,
|
||
child: ColoredBox(
|
||
color: const Color(0xB3000000), // rgba(0,0,0,.7)
|
||
child: Center(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Transform.translate(
|
||
offset: Offset(isPad ? -35.w : 0, 0), // margin-left: -70rpx
|
||
child: Container(
|
||
width: isPad ? 315.5.w : 250.w, // 500/631rpx
|
||
height: isPad ? 299.5.h : 249.h, // 498/599rpx
|
||
padding: EdgeInsets.only(top: isPad ? 175.h : 137.5.h),
|
||
decoration: BoxDecoration(
|
||
image: DecorationImage(
|
||
image: CachedNetworkImageProvider(
|
||
ImgUrl.build(
|
||
isPad
|
||
? '/StaticResource/2026-06-03/conquer-pause-bg-Pad1780466965818.png'
|
||
: '/StaticResource/2026-04-08/conquer-pause-bg.png',
|
||
),
|
||
),
|
||
fit: BoxFit.fill,
|
||
),
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
if (showHours) ...[
|
||
_timeUnit(unitW, unitH, _two(hours)),
|
||
_timeSep(),
|
||
],
|
||
_timeUnit(unitW, unitH, _two(minutes)),
|
||
_timeSep(),
|
||
_timeUnit(unitW, unitH, _two(secs)),
|
||
],
|
||
),
|
||
SizedBox(height: 15.h), // margin-t-30
|
||
Text.rich(
|
||
TextSpan(
|
||
style: TextStyle(
|
||
fontSize: 13.sp, // 26rpx
|
||
color: ExerciseColors.subtitle,
|
||
),
|
||
children: [
|
||
const TextSpan(text: '共'),
|
||
TextSpan(
|
||
text: '${widget.totalCount}',
|
||
style: const TextStyle(
|
||
color: Color(0xFF1578FF),
|
||
),
|
||
),
|
||
const TextSpan(text: '道题,还剩'),
|
||
TextSpan(
|
||
text: '${widget.remainingCount}',
|
||
style: const TextStyle(
|
||
color: Color(0xFF1578FF),
|
||
),
|
||
),
|
||
const TextSpan(text: '道题待完成'),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
SizedBox(height: 25.h), // margin-t-50
|
||
Text(
|
||
'点击任意位置继续',
|
||
style: TextStyle(
|
||
fontSize: 14.sp, // 28rpx
|
||
color: const Color(0xFFB4C2E0),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _timeUnit(double w, double h, String text) {
|
||
return Container(
|
||
width: w,
|
||
height: h,
|
||
alignment: Alignment.center,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF2F8FF),
|
||
borderRadius: BorderRadius.circular(6.r),
|
||
boxShadow: [
|
||
BoxShadow(
|
||
color: const Color(0x1A1578FF),
|
||
offset: Offset(0, 2.h),
|
||
blurRadius: 6.h,
|
||
),
|
||
],
|
||
),
|
||
child: Text(
|
||
text,
|
||
style: TextStyle(
|
||
fontSize: 20.sp, // 40rpx
|
||
fontWeight: FontWeight.w600,
|
||
color: const Color(0xFF1578FF),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _timeSep() {
|
||
return Padding(
|
||
padding: EdgeInsets.symmetric(horizontal: 10.w), // margin-rl-10 + 视觉间距
|
||
child: Text(
|
||
':',
|
||
style: TextStyle(
|
||
fontSize: 20.sp,
|
||
fontWeight: FontWeight.w600,
|
||
color: const Color(0xFF1578FF),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|