gcx/frontend/genex-mobile/lib/shared/widgets/countdown_button.dart

103 lines
2.6 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import '../../app/theme/app_colors.dart';
import '../../app/theme/app_typography.dart';
import '../../app/theme/app_spacing.dart';
/// 短信验证码倒计时按钮
///
/// - 初始: 显示 [label] (如 "获取验证码")
/// - 点击后: 显示 "XXs" 倒计时,禁用
/// - 倒计时结束: 恢复可点击
class CountdownButton extends StatefulWidget {
final String label;
final int seconds;
final Future<void> Function() onPressed;
final bool enabled;
const CountdownButton({
super.key,
required this.label,
this.seconds = 60,
required this.onPressed,
this.enabled = true,
});
@override
State<CountdownButton> createState() => _CountdownButtonState();
}
class _CountdownButtonState extends State<CountdownButton> {
Timer? _timer;
int _remaining = 0;
bool _loading = false;
bool get _isCounting => _remaining > 0;
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
Future<void> _handlePress() async {
if (_isCounting || _loading || !widget.enabled) return;
setState(() => _loading = true);
try {
await widget.onPressed();
_startCountdown();
} catch (_) {
// 发送失败不启动倒计时,让用户重试
} finally {
if (mounted) setState(() => _loading = false);
}
}
void _startCountdown() {
setState(() => _remaining = widget.seconds);
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (!mounted) {
timer.cancel();
return;
}
setState(() {
_remaining--;
if (_remaining <= 0) {
timer.cancel();
}
});
});
}
@override
Widget build(BuildContext context) {
final disabled = _isCounting || _loading || !widget.enabled;
final text = _isCounting ? '${_remaining}s' : widget.label;
return SizedBox(
height: AppSpacing.buttonHeightSm,
child: TextButton(
onPressed: disabled ? null : _handlePress,
style: TextButton.styleFrom(
foregroundColor: AppColors.primary,
disabledForegroundColor: AppColors.textTertiary,
padding: const EdgeInsets.symmetric(horizontal: 16),
shape: RoundedRectangleBorder(
borderRadius: AppSpacing.borderRadiusSm,
),
textStyle: AppTypography.labelMedium,
),
child: _loading
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(text),
),
);
}
}