Files
ttstd_family_care/lib/features/auth/presentation/register_page.dart
TongTongStudio 06cdc77172 feat(android): 支持沉浸式状态栏、地图定位与自定义签名
- 配置 edge-to-edge 沉浸式状态栏与刘海屏适配
- 新增百度地图权限、AK 配置及定位权限说明
- 接入 MethodChannel 实现返回键退后台热启动优化
- 配置自定义签名并更新构建逻辑
- 完善 token 刷新与鉴权失效统一处理
- 新增地图页与截图页路由
- 修复返回键拦截导致的 PopScope 失效问题
2026-08-19 03:43:12 +08:00

219 lines
7.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
),
),
],
);
}
}