更新
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
/// ============================================================
|
||||
/// 【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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user