- 配置 edge-to-edge 沉浸式状态栏与刘海屏适配 - 新增百度地图权限、AK 配置及定位权限说明 - 接入 MethodChannel 实现返回键退后台热启动优化 - 配置自定义签名并更新构建逻辑 - 完善 token 刷新与鉴权失效统一处理 - 新增地图页与截图页路由 - 修复返回键拦截导致的 PopScope 失效问题
219 lines
7.0 KiB
Dart
219 lines
7.0 KiB
Dart
import 'package:cupertino_ui/cupertino_ui.dart';
|
||
import 'package:go_router/go_router.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
|
||
import '../../../l10n/app_localizations.dart';
|
||
import '../domain/auth_models.dart';
|
||
import 'auth_controller.dart';
|
||
|
||
/// 注册页。
|
||
///
|
||
/// 业务动作委托给 [authControllerProvider],UI 仅负责输入收集与导航。
|
||
class RegisterPage extends ConsumerStatefulWidget {
|
||
const RegisterPage({super.key});
|
||
|
||
@override
|
||
ConsumerState<RegisterPage> createState() => _RegisterPageState();
|
||
}
|
||
|
||
class _RegisterPageState extends ConsumerState<RegisterPage> {
|
||
final _phoneController = TextEditingController();
|
||
final _codeController = TextEditingController();
|
||
final _passwordController = TextEditingController();
|
||
|
||
@override
|
||
void dispose() {
|
||
_phoneController.dispose();
|
||
_codeController.dispose();
|
||
_passwordController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
Future<void> _submit() async {
|
||
final ok = await ref.read(authControllerProvider.notifier).register(
|
||
phone: _phoneController.text.trim(),
|
||
code: _codeController.text.trim(),
|
||
password: _passwordController.text,
|
||
);
|
||
if (ok && mounted) context.go('/home');
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
ref.listen<String?>(authControllerProvider.select((s) => s.alert),
|
||
(_, alert) {
|
||
if (alert != null) {
|
||
_showAlert(alert);
|
||
ref.read(authControllerProvider.notifier).consumeAlert();
|
||
}
|
||
});
|
||
|
||
final state = ref.watch(authControllerProvider);
|
||
final l10n = AppLocalizations.of(context);
|
||
|
||
return PopScope(
|
||
// 拦截系统返回键:优先出栈;若无法出栈则跳回登录页。
|
||
canPop: false,
|
||
onPopInvokedWithResult: (didPop, _) {
|
||
if (didPop) return;
|
||
if (context.canPop()) {
|
||
context.pop();
|
||
} else {
|
||
context.go('/login');
|
||
}
|
||
},
|
||
child: CupertinoPageScaffold(
|
||
backgroundColor: CupertinoColors.systemGroupedBackground,
|
||
navigationBar: CupertinoNavigationBar(
|
||
middle: Text(l10n.registerTitle),
|
||
leading: CupertinoButton(
|
||
padding: EdgeInsets.zero,
|
||
child: const Icon(CupertinoIcons.back),
|
||
onPressed: () => context.pop(),
|
||
),
|
||
),
|
||
child: SafeArea(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(24),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
const SizedBox(height: 24),
|
||
CupertinoTextField(
|
||
controller: _phoneController,
|
||
placeholder: l10n.phoneHint,
|
||
keyboardType: TextInputType.phone,
|
||
padding: const EdgeInsets.all(14),
|
||
decoration: BoxDecoration(
|
||
color: CupertinoColors.white,
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
),
|
||
const SizedBox(height: 12),
|
||
_CodeField(
|
||
controller: _codeController,
|
||
countdownSeconds: state.countdownSeconds,
|
||
isSending: state.isSendingCode,
|
||
codeHint: l10n.codeHint,
|
||
getCodeLabel: l10n.getCode,
|
||
sendingLabel: l10n.sending,
|
||
onSend: () async {
|
||
final phone = _phoneController.text.trim();
|
||
if (phone.isEmpty) {
|
||
_showAlert(l10n.phoneEmpty);
|
||
return;
|
||
}
|
||
if (!RegExp(r'^1\d{10}$').hasMatch(phone)) {
|
||
_showAlert(l10n.phoneInvalid);
|
||
return;
|
||
}
|
||
await ref.read(authControllerProvider.notifier).sendSmsCode(
|
||
phone: phone,
|
||
scene: SmsScene.register,
|
||
);
|
||
},
|
||
),
|
||
const SizedBox(height: 12),
|
||
CupertinoTextField(
|
||
controller: _passwordController,
|
||
placeholder: l10n.passwordHint,
|
||
obscureText: true,
|
||
padding: const EdgeInsets.all(14),
|
||
decoration: BoxDecoration(
|
||
color: CupertinoColors.white,
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
),
|
||
const SizedBox(height: 24),
|
||
CupertinoButton.filled(
|
||
onPressed: state.isLoading ? null : _submit,
|
||
child: state.isLoading
|
||
? const CupertinoActivityIndicator()
|
||
: Text(l10n.register),
|
||
),
|
||
const SizedBox(height: 12),
|
||
CupertinoButton(
|
||
onPressed: () => context.go('/login'),
|
||
child: Text(l10n.hasAccount),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showAlert(String message) {
|
||
final l10n = AppLocalizations.of(context);
|
||
debugPrint('[Dialog] RegisterPage._showAlert 即将弹出提示弹窗'
|
||
' title=${l10n.alertTitle} message=$message');
|
||
showCupertinoDialog<void>(
|
||
context: context,
|
||
builder: (dialogContext) => CupertinoAlertDialog(
|
||
title: Text(l10n.alertTitle),
|
||
content: Text(message),
|
||
actions: [
|
||
CupertinoDialogAction(
|
||
child: Text(l10n.confirm),
|
||
onPressed: () => Navigator.of(dialogContext).pop(),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 验证码输入 + 发送按钮(独立小组件)。提取为私有无状态组件避免冗长内联。
|
||
class _CodeField extends StatelessWidget {
|
||
const _CodeField({
|
||
required this.controller,
|
||
required this.countdownSeconds,
|
||
required this.isSending,
|
||
required this.codeHint,
|
||
required this.getCodeLabel,
|
||
required this.sendingLabel,
|
||
required this.onSend,
|
||
});
|
||
|
||
final TextEditingController controller;
|
||
final int countdownSeconds;
|
||
final bool isSending;
|
||
final String codeHint;
|
||
final String getCodeLabel;
|
||
final String sendingLabel;
|
||
final Future<void> Function() onSend;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final counting = countdownSeconds > 0;
|
||
return Row(
|
||
children: [
|
||
Expanded(
|
||
child: CupertinoTextField(
|
||
controller: controller,
|
||
placeholder: codeHint,
|
||
keyboardType: TextInputType.number,
|
||
padding: const EdgeInsets.all(14),
|
||
decoration: BoxDecoration(
|
||
color: CupertinoColors.white,
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
CupertinoButton(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
onPressed: (counting || isSending) ? null : () => onSend(),
|
||
child: Text(
|
||
counting
|
||
? '${countdownSeconds}s'
|
||
: (isSending ? sendingLabel : getCodeLabel),
|
||
style: const TextStyle(color: CupertinoColors.activeBlue),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
}
|