| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- 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<LoginRegisterPage> createState() => _LoginRegisterPageState();
- }
- class _LoginRegisterPageState extends ConsumerState<LoginRegisterPage> {
- 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");
- }
- }
|