import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:go_router/go_router.dart'; import 'package:news_app/main.dart'; import 'package:news_app/util/shared_prefs_instance_util.dart'; import 'package:news_app/util/toast_util.dart'; import 'package:news_app/widget/my_txt.dart'; import '../../constant/api_const.dart'; import '../../constant/color_res.dart'; import '../../constant/config.dart'; import '../../http/http_util.dart'; /// @author: bo.zeng /// @email: cnhbwds@gmail.com /// @date: 2025 2025/4/9 16:00 /// @description: class LoginRegisterPage extends ConsumerStatefulWidget { const LoginRegisterPage({super.key}); @override ConsumerState createState() => _LoginRegisterPageState(); } class _LoginRegisterPageState extends ConsumerState { bool _isLogin = true; bool _agreeProtocol = false; Timer? _timer; int seconds = 60; bool usePasswordLogin = true; bool _obscureText1 = true; // 控制密码是否隐藏 bool _obscureText2 = true; // 控制密码是否隐藏 String _smsLabel = "获取验证码"; final TextEditingController _phoneController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _password2Controller = TextEditingController(); final TextEditingController _smsCodeController = TextEditingController(); @override void dispose() { _phoneController.dispose(); _passwordController.dispose(); _password2Controller.dispose(); _smsCodeController.dispose(); _timer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, resizeToAvoidBottomInset: false, appBar: AppBar( backgroundColor: color5F59F7, systemOverlayStyle: SystemUiOverlayStyle( statusBarColor: color5F59F7, statusBarIconBrightness: Brightness.light, // 状态栏图标颜色 ), ), body: Stack( children: [ Positioned( child: GestureDetector( onTap: (){ FocusScope.of(context).unfocus(); }, child: Container( height: 200.h, decoration: BoxDecoration( gradient: LinearGradient( colors: [color5F59F7, color6592FD, Colors.white], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ), ), Padding( padding: EdgeInsets.only(left: 20.w, right: 20.w, top: 40.h), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 登录/注册标签 Row( spacing: 30.w, children: [ GestureDetector( onTap: _switchLoginOrRegister, child: Column( children: [ myTxt( text: "登录", fontSize: 20.sp, fontWeight: FontWeight.bold, ), Container( height: 4.h, width: 40.w, decoration: BoxDecoration( color: _isLogin ? Colors.black : Colors.transparent, borderRadius: BorderRadius.circular(8.r), ), ), ], ), ), GestureDetector( onTap: _switchLoginOrRegister, child: Column( children: [ myTxt( text: "注册", fontSize: 20.sp, fontWeight: FontWeight.bold, ), Container( height: 4.h, width: 40.w, decoration: BoxDecoration( color: _isLogin ? Colors.transparent : Colors.black, borderRadius: BorderRadius.circular(8.r), ), ), ], ), ), ], ), SizedBox(height: 24.h), GestureDetector( onTap: (){ FocusScope.of(context).unfocus(); }, child: Container( padding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 20.h, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8.r), ), alignment: Alignment.center, child: Column( spacing: 16.h, crossAxisAlignment: CrossAxisAlignment.start, children: [ myTxt(text: "帐号", fontSize: 14.sp), TextField( textInputAction: TextInputAction.next, keyboardType: TextInputType.phone, controller: _phoneController, decoration: InputDecoration( prefixIcon: Icon( Icons.phone_android, color: Colors.grey, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide.none, ), filled: true, fillColor: colorF5F7FD, contentPadding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 10.h, ), hintText: '请输入手机号', hintStyle: TextStyle(color: Colors.grey), ), ), if (_isLogin && !usePasswordLogin) myTxt(text: "验证码", fontSize: 14.sp), if (_isLogin && !usePasswordLogin) TextField( textInputAction: TextInputAction.done, keyboardType: TextInputType.number, controller: _smsCodeController, decoration: InputDecoration( prefixIcon: Icon( Icons.safety_check, color: Colors.grey, ), suffixIcon: Padding( padding: EdgeInsets.only(right: 10.w, top: 13.h), child: GestureDetector( onTap: _startSmsCodeTimer, child: myTxt( text: _smsLabel, color: color5F59F7, fontSize: 12.sp, fontWeight: FontWeight.bold, ), ), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide.none, ), filled: true, fillColor: colorF5F7FD, contentPadding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 10.h, ), hintText: '请输入验证码', hintStyle: TextStyle(color: Colors.grey), ), ), if (usePasswordLogin || !_isLogin) myTxt(text: "密码", fontSize: 14.sp), if (usePasswordLogin || !_isLogin) TextField( obscureText: _obscureText1, controller: _passwordController, textInputAction: TextInputAction.done, keyboardType: TextInputType.text, decoration: InputDecoration( prefixIcon: Icon( Icons.lock_outline, color: Colors.grey, ), suffixIcon: IconButton( onPressed: () { setState(() { _obscureText1 = !_obscureText1; }); }, icon: Icon( _obscureText1 ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide.none, ), filled: true, fillColor: colorF5F7FD, contentPadding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 10.h, ), hintText: '请输入密码', hintStyle: TextStyle(color: Colors.grey), ), ), if (_isLogin) Center( child: TextButton( onPressed: () { setState(() { usePasswordLogin = !usePasswordLogin; }); }, child: myTxt( text: usePasswordLogin ? "使用验证码登录" : "使用密码登录", ), ), ), if (!_isLogin) myTxt(text: "确认密码", fontSize: 14.sp), if (!_isLogin) TextField( obscureText: _obscureText2, controller: _password2Controller, textInputAction: TextInputAction.done, keyboardType: TextInputType.text, decoration: InputDecoration( prefixIcon: Icon( Icons.lock_outline, color: Colors.grey, ), suffixIcon: IconButton( onPressed: () { setState(() { _obscureText2 = !_obscureText2; }); }, icon: Icon( _obscureText2 ? Icons.visibility_off : Icons.visibility, color: Colors.grey, ), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8.r), borderSide: BorderSide.none, ), filled: true, fillColor: colorF5F7FD, contentPadding: EdgeInsets.symmetric( horizontal: 16.w, vertical: 10.h, ), hintText: '请再输入密码', hintStyle: TextStyle(color: Colors.grey), ), ), // Align( // alignment: Alignment.centerRight, // child: myTxt( // text: "忘记密码", // color: color5F59F7, // fontSize: 12.sp, // fontWeight: FontWeight.bold, // ), // ), ], ), ), ), SizedBox(height: 16.h), _buildProtocolAgreement(), SizedBox(height: 24.h), // 登录/注册按钮 GestureDetector( onTap: () { _submitForm(context, ref); }, child: Container( margin: EdgeInsets.symmetric(horizontal: 28.w), height: 42.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.r), gradient: LinearGradient( colors: [color5F59F7, color6592FD], begin: Alignment.centerLeft, end: Alignment.centerRight, ), ), alignment: Alignment.center, child: myTxt( text: _isLogin ? "登录" : "注册", color: Colors.white, fontSize: 15.sp, ), ), ), SizedBox(height: 45.h), // Row( // spacing: 10.w, // children: [ // Expanded(child: Container(height: 1, color: color979797)), // myTxt(text: "其他帐户登录", fontSize: 11.sp, color: color444D43), // Expanded(child: Container(height: 1, color: color979797)), // ], // ), SizedBox(height: 20.h), // Row( // spacing: 20.w, // mainAxisAlignment: MainAxisAlignment.center, // children: [ // GestureDetector( // onTap: () {}, // child: Image.asset( // Assets.images.wxIcon.path, // width: 52.w, // height: 52.w, // ), // ), // GestureDetector( // onTap: () {}, // child: Image.asset( // Assets.images.wbIcon.path, // width: 52.w, // height: 52.w, // ), // ), // GestureDetector( // onTap: () {}, // child: Image.asset( // Assets.images.qqIcon.path, // width: 52.w, // height: 52.w, // ), // ), // ], // ), ], ), ), ], ), ); } void _switchLoginOrRegister() { setState(() { _isLogin = !_isLogin; }); } void _startSmsCodeTimer() async { if (seconds != 60 || _timer?.isActive == true) { //防止重复触发 return; } String mobile = _phoneController.text; if (mobile.isEmpty) { showToast("手机号不能为空"); return; } await HttpUtil().post(apiSendLoginSMSCode, data: {"mobile": mobile}); _timer = Timer.periodic(Duration(seconds: 1), (timer) { if (seconds > 0) { setState(() { seconds--; if (seconds >= 10) { _smsLabel = "${seconds}s后重发"; } else { _smsLabel = "0${seconds}s后重发"; } }); } else { _smsLabel = "获取验证码"; seconds = 60; _timer?.cancel(); setState(() {}); // 这里可以触发倒计时结束后的逻辑 } }); } Widget _buildProtocolAgreement() { return Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, spacing: 1.w, children: [ SizedBox( width: 32.w, height: 32.w, child: Checkbox( activeColor: color5F59F7, value: _agreeProtocol, onChanged: (value) { setState(() { _agreeProtocol = value ?? false; }); }, ), ), GestureDetector( onTap: () { context.push("/user/privacy"); }, child: Container( color: Colors.white, child: Text.rich( TextSpan( children: [ TextSpan( text: _isLogin ? '登录即同意' : '注册即同意', style: TextStyle(fontSize: 11.sp), ), TextSpan( text: '《新华新消费服务协议》', style: TextStyle(color: color5F59F7, fontSize: 11.sp), ), TextSpan(text: '和', style: TextStyle(fontSize: 11.sp)), TextSpan( text: '《隐私政策》', style: TextStyle(color: color5F59F7, fontSize: 11.sp), ), ], ), ), ), ), ], ); } bool isPhoneNumber(String input) { final RegExp phoneRegex = RegExp(r'^1[3-9]\d{9}$'); return phoneRegex.hasMatch(input); } bool isPasswordValid(String password) { // 至少包含一个字母和一个数字,长度至少6位 final RegExp passwordRegex = RegExp( r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$', ); return passwordRegex.hasMatch(password); } void _submitForm(BuildContext context, WidgetRef ref) async { if (!_agreeProtocol) { showToast("请先同意服务协议"); return; } String uid = _phoneController.text; String password = _passwordController.text; String password2 = _password2Controller.text; // String smsCode = _smsCodeController.text; if (uid.isEmpty || isPhoneNumber(uid) == false) { showToast("请输入手机号"); return; } if (usePasswordLogin && password.isEmpty) { showToast("请输入密码"); return; } if (!_isLogin) { if (password != password2) { showToast("密码不一致"); return; } if (isPasswordValid(password) == false) { showToast("密码长度需大于8位,并包含字母和数字"); return; } } if (!_isLogin) { //注册 uuid = await HttpUtil().post( apiRegister, data: {"type": "phone", "phonenumber": uid, "password": password}, ); } else { //登录 if (usePasswordLogin) { uuid = await HttpUtil().post( apiLogin, data: {"username": uid, "password": password}, ); } else { String smsCode = _smsCodeController.text; if (smsCode.isEmpty) { showToast("请输入验证码"); return; } uuid = await HttpUtil().post( apiLoginSMSCode, data: {"mobile": uid, "code": smsCode}, ); } } if (uuid.isEmpty) { return; } saveUuid(uuid); ref.read(globalUserProvider.notifier).fetchUserInfo(); if (context.mounted) context.go("/main"); } }