Commit 4bfbbcc8 by tanghuan

Merge branch 'feature-2604-menu' into feature-2607

# Conflicts:
#	lib/bloc/web_cubit.dart
#	lib/ui/pages/web_page.dart
2 parents 780ef5a7 b41e0803
Showing 135 changed files with 519 additions and 309 deletions
...@@ -5,6 +5,7 @@ import android.app.AlertDialog; ...@@ -5,6 +5,7 @@ import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.drawable.GradientDrawable;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
...@@ -16,6 +17,7 @@ import android.text.style.ClickableSpan; ...@@ -16,6 +17,7 @@ import android.text.style.ClickableSpan;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
...@@ -62,30 +64,37 @@ public class PrivacyActivity extends Activity { ...@@ -62,30 +64,37 @@ public class PrivacyActivity extends Activity {
} }
private View createContentView() { private View createContentView() {
// 根布局 // 外层根布局(浅灰色背景,垂直居中)
LinearLayout rootLayout = new LinearLayout(this); FrameLayout rootLayout = new FrameLayout(this);
rootLayout.setOrientation(LinearLayout.VERTICAL);
rootLayout.setGravity(Gravity.CENTER_VERTICAL);
rootLayout.setPadding(dpToPx(32), dpToPx(48), dpToPx(32), dpToPx(48));
rootLayout.setBackgroundColor(0xFFFFFFFF); rootLayout.setBackgroundColor(0xFFFFFFFF);
// 内容容器 // 卡片容器(白色圆角卡片)
LinearLayout contentLayout = new LinearLayout(this); LinearLayout cardLayout = new LinearLayout(this);
contentLayout.setOrientation(LinearLayout.VERTICAL); cardLayout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams contentLayoutParams = new LinearLayout.LayoutParams( cardLayout.setPadding(dpToPx(28), dpToPx(36), dpToPx(28), dpToPx(36));
LinearLayout.LayoutParams.MATCH_PARENT, GradientDrawable cardBg = new GradientDrawable();
LinearLayout.LayoutParams.WRAP_CONTENT); cardBg.setColor(0xFFFFFFFF);
rootLayout.addView(contentLayout, contentLayoutParams); cardBg.setCornerRadius(dpToPx(16));
cardLayout.setBackground(cardBg);
FrameLayout.LayoutParams cardParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
int cardMarginH = dpToPx(24);
int cardMarginV = dpToPx(80);
cardParams.setMargins(cardMarginH, cardMarginV, cardMarginH, cardMarginV);
cardParams.gravity = Gravity.CENTER_VERTICAL;
rootLayout.addView(cardLayout, cardParams);
// 标题 // 标题
TextView titleView = new TextView(this); TextView titleView = new TextView(this);
titleView.setText("用户协议与隐私政策"); titleView.setText("用户协议与隐私政策");
titleView.setTextSize(22); titleView.setTextSize(23);
titleView.setTextColor(0xFF333333); titleView.setTextColor(0xFF333333);
titleView.setTypeface(null, Typeface.BOLD); titleView.setTypeface(null, Typeface.BOLD);
titleView.setGravity(Gravity.CENTER); titleView.setGravity(Gravity.CENTER);
titleView.setPadding(0, 0, 0, dpToPx(32)); titleView.setPadding(0, 0, 0, dpToPx(28));
contentLayout.addView(titleView); cardLayout.addView(titleView);
// 内容(带可点击链接) // 内容(带可点击链接)
TextView contentView = new TextView(this); TextView contentView = new TextView(this);
...@@ -93,10 +102,10 @@ public class PrivacyActivity extends Activity { ...@@ -93,10 +102,10 @@ public class PrivacyActivity extends Activity {
contentView.setMovementMethod(LinkMovementMethod.getInstance()); contentView.setMovementMethod(LinkMovementMethod.getInstance());
contentView.setHighlightColor(0x00000000); contentView.setHighlightColor(0x00000000);
contentView.setTextSize(17); contentView.setTextSize(17);
contentView.setTextColor(0xFF666666); contentView.setTextColor(0xFF555555);
contentView.setLineSpacing(dpToPx(6), 1.0f); contentView.setLineSpacing(dpToPx(6), 1.0f);
contentView.setGravity(Gravity.START); contentView.setGravity(Gravity.START);
contentLayout.addView(contentView); cardLayout.addView(contentView);
// 按钮容器 // 按钮容器
LinearLayout buttonLayout = new LinearLayout(this); LinearLayout buttonLayout = new LinearLayout(this);
...@@ -105,45 +114,51 @@ public class PrivacyActivity extends Activity { ...@@ -105,45 +114,51 @@ public class PrivacyActivity extends Activity {
LinearLayout.LayoutParams buttonLayoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams buttonLayoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout.LayoutParams.WRAP_CONTENT);
buttonLayoutParams.topMargin = dpToPx(48); buttonLayoutParams.topMargin = dpToPx(32);
contentLayout.addView(buttonLayout, buttonLayoutParams); cardLayout.addView(buttonLayout, buttonLayoutParams);
// 同意按钮 int btnHeight = dpToPx(44);
int btnRadius = dpToPx(22);
// 同意按钮(蓝紫色填充,大圆角)
Button agreeButton = new Button(this); Button agreeButton = new Button(this);
agreeButton.setText("同意"); agreeButton.setText("同意");
agreeButton.setTextColor(0xFFFFFFFF); agreeButton.setTextColor(0xFFFFFFFF);
agreeButton.setBackgroundColor(0xFF4CAF50);
agreeButton.setTextSize(16); agreeButton.setTextSize(16);
agreeButton.setTypeface(null, Typeface.BOLD);
agreeButton.setGravity(Gravity.CENTER); agreeButton.setGravity(Gravity.CENTER);
agreeButton.setSingleLine(true); agreeButton.setSingleLine(true);
agreeButton.setMinWidth(dpToPx(120)); agreeButton.setPadding(0, 0, 0, 0);
agreeButton.setMinHeight(dpToPx(48)); GradientDrawable agreeBg = new GradientDrawable();
int btnPaddingH = dpToPx(24); agreeBg.setColor(0xFF7691FA);
int btnPaddingV = dpToPx(12); agreeBg.setCornerRadius(btnRadius);
agreeButton.setPadding(btnPaddingH, btnPaddingV, btnPaddingH, btnPaddingV); agreeButton.setBackground(agreeBg);
agreeButton.setOnClickListener(v -> onAgreeClicked()); agreeButton.setOnClickListener(v -> onAgreeClicked());
LinearLayout.LayoutParams agreeParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams agreeParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, 0,
LinearLayout.LayoutParams.WRAP_CONTENT); btnHeight,
agreeParams.setMargins(dpToPx(16), 0, dpToPx(16), 0); 1.0f);
agreeParams.setMargins(0, 0, dpToPx(8), 0);
buttonLayout.addView(agreeButton, agreeParams); buttonLayout.addView(agreeButton, agreeParams);
// 不同意按钮 // 不同意按钮(灰色描边,大圆角)
Button disagreeButton = new Button(this); Button disagreeButton = new Button(this);
disagreeButton.setText("不同意"); disagreeButton.setText("不同意");
disagreeButton.setTextColor(0xFF666666); disagreeButton.setTextColor(0xFF666666);
disagreeButton.setBackgroundColor(0xFFEEEEEE);
disagreeButton.setTextSize(16); disagreeButton.setTextSize(16);
disagreeButton.setGravity(Gravity.CENTER); disagreeButton.setGravity(Gravity.CENTER);
disagreeButton.setSingleLine(true); disagreeButton.setSingleLine(true);
disagreeButton.setMinWidth(dpToPx(120)); disagreeButton.setPadding(0, 0, 0, 0);
disagreeButton.setMinHeight(dpToPx(48)); GradientDrawable disagreeBg = new GradientDrawable();
disagreeButton.setPadding(btnPaddingH, btnPaddingV, btnPaddingH, btnPaddingV); disagreeBg.setColor(0xFFF5F6FA);
disagreeBg.setCornerRadius(btnRadius);
disagreeButton.setBackground(disagreeBg);
disagreeButton.setOnClickListener(v -> onDisagreeClicked()); disagreeButton.setOnClickListener(v -> onDisagreeClicked());
LinearLayout.LayoutParams disagreeParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams disagreeParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT, 0,
LinearLayout.LayoutParams.WRAP_CONTENT); btnHeight,
disagreeParams.setMargins(dpToPx(16), 0, dpToPx(16), 0); 1.0f);
disagreeParams.setMargins(dpToPx(8), 0, 0, 0);
buttonLayout.addView(disagreeButton, disagreeParams); buttonLayout.addView(disagreeButton, disagreeParams);
return rootLayout; return rootLayout;
...@@ -172,7 +187,7 @@ public class PrivacyActivity extends Activity { ...@@ -172,7 +187,7 @@ public class PrivacyActivity extends Activity {
@Override @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds); super.updateDrawState(ds);
ds.setColor(0xFF4CAF50); ds.setColor(0xFF7691FA);
ds.setUnderlineText(true); ds.setUnderlineText(true);
} }
}, userAgreementStart, userAgreementEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); }, userAgreementStart, userAgreementEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
...@@ -188,7 +203,7 @@ public class PrivacyActivity extends Activity { ...@@ -188,7 +203,7 @@ public class PrivacyActivity extends Activity {
@Override @Override
public void updateDrawState(TextPaint ds) { public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds); super.updateDrawState(ds);
ds.setColor(0xFF4CAF50); ds.setColor(0xFF7691FA);
ds.setUnderlineText(true); ds.setUnderlineText(true);
} }
}, privacyPolicyStart, privacyPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); }, privacyPolicyStart, privacyPolicyEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" /> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
<!-- You can insert your own image assets here --> </item>
<!-- <item> <item>
<bitmap <bitmap android:gravity="center" android:src="@drawable/splash"/>
android:gravity="center" </item>
android:src="@mipmap/launch_image" /> <item android:bottom="24dp">
</item> --> <bitmap android:gravity="bottom" android:src="@drawable/branding"/>
</item>
</layer-list> </layer-list>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" /> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
<!-- You can insert your own image assets here --> </item>
<!-- <item> <item>
<bitmap <bitmap android:gravity="center" android:src="@drawable/splash"/>
android:gravity="center" </item>
android:src="@mipmap/launch_image" /> <item android:bottom="24dp">
</item> --> <bitmap android:gravity="bottom" android:src="@drawable/branding"/>
</item>
</layer-list> </layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
<item name="android:windowSplashScreenBrandingImage">@drawable/android12branding</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#FFFFFF</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
<item name="android:windowSplashScreenBrandingImage">@drawable/android12branding</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#FFFFFF</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when <!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame --> the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your
......
...@@ -3,6 +3,7 @@ import 'dart:io'; ...@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:appframe/l10n/gen/app_localizations.dart'; import 'package:appframe/l10n/gen/app_localizations.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'config/routes.dart'; import 'config/routes.dart';
...@@ -10,29 +11,41 @@ class App extends StatelessWidget { ...@@ -10,29 +11,41 @@ class App extends StatelessWidget {
const App({super.key}); const App({super.key});
@override @override
Widget build(BuildContext context) => Platform.isIOS Widget build(BuildContext context) {
? CupertinoApp.router( final Widget app = Platform.isIOS
routerConfig: router, ? CupertinoApp.router(
title: '班小二', routerConfig: router,
theme: const CupertinoThemeData(primaryColor: CupertinoColors.systemBlue), title: '班小二',
localizationsDelegates: AppLocalizations.localizationsDelegates, theme: const CupertinoThemeData(primaryColor: CupertinoColors.systemBlue),
supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates,
// // === 为 iOS 添加本地化配置 === supportedLocales: AppLocalizations.supportedLocales,
// localizationsDelegates: const [ )
// GlobalMaterialLocalizations.delegate, // 为Material组件提供本地化 : MaterialApp.router(
// GlobalCupertinoLocalizations.delegate, // 为Cupertino组件提供本地化 routerConfig: router,
// GlobalWidgetsLocalizations.delegate, // 定义文本方向等 title: '班小二',
// ], theme: ThemeData(primarySwatch: Colors.blue),
// supportedLocales: const [ supportedLocales: AppLocalizations.supportedLocales,
// Locale('zh', 'CN'), // 中文(中国) localizationsDelegates: AppLocalizations.localizationsDelegates,
// Locale('en', 'US'), // 英语(美国) // Android edge-to-edge 模式下虚拟导航键叠在内容上方,
// ], // 通过 builder 统一包裹 SafeArea,所有路由页面自动适配底部安全区域。
) builder: (context, child) => ColoredBox(
: MaterialApp.router( color: Colors.white,
routerConfig: router, child: SafeArea(top: false, child: child!),
title: '班小二', ),
theme: ThemeData(primarySwatch: Colors.blue), );
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates, // 全局默认状态栏样式兑底:透明背景 + 深色图标。
); // 作为最外层 AnnotatedRegion 只挂载一次,不会随页面状态变化反复
// 下发命令,避免污染 Android decorView 上 H5 <video> 全屏依赖的
// systemUiVisibility flag。带 AppBar 的页面由 AppBar.systemOverlayStyle
// 在其 region 区域局部覆盖,隐藏 AppBar 后自动回到本全局值。
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
),
child: app,
);
}
} }
import 'dart:async'; import 'dart:async';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart'; import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/utils/login_util.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
...@@ -102,7 +102,11 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> { ...@@ -102,7 +102,11 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
} }
// 发送验证码 // 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone, 0); var result = await _phoneAuthRepository.verifyCode(phone, 1);
if (result == null) {
Fluttertoast.showToast(msg: '发送请求失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
return;
}
if (result['code'] != 0) { if (result['code'] != 0) {
final err = result['error'] ?? ''; final err = result['error'] ?? '';
// 手机号未注册时,打开指引界面 // 手机号未注册时,打开指引界面
...@@ -154,97 +158,15 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> { ...@@ -154,97 +158,15 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
} }
var data = resultData['data'] as Map<String, dynamic>; var data = resultData['data'] as Map<String, dynamic>;
_handleLoginSuccess(data); int binding = resultData['binding'];
} // binding=1 代表已绑定,不是游客
var visitor = binding == 1 ? 0 : 1;
void _handleLoginSuccess(Map<String, dynamic> data) { if (visitor == 1) {
var roles = data['roles']; var sharedPreferences = getIt.get<SharedPreferences>();
// 过滤出家长角色的数据 sharedPreferences.setString('auth_visitor_type', 'phone');
if (roles?.isNotEmpty ?? false) { sharedPreferences.setString('auth_visitor_id', phone);
roles.removeWhere((element) => element['userType'] != 2);
} else {
roles = [];
}
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var className = '';
var stuName = '';
var relation = '';
var sharedPreferences = getIt.get<SharedPreferences>();
if (roles.isNotEmpty) {
var role = roles[0];
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
className = role['className'];
stuName = role['stuName'];
relation = role['relation'] ?? '';
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
for (var role in roles) {
classIdList.add(role['classCode'] as String);
}
debugPrint('classCodeIds:-------------- $classIdList');
sharedPreferences.setStringList(Constant.classIdSetKey, classIdList);
} else {
sharedPreferences.setStringList(Constant.classIdSetKey, []);
} }
LoginUtil.handleLoginSuccess(data, visitor, 'router');
var preUserCode = sharedPreferences.getString('pre_userCode') ?? '';
if (userCode != preUserCode) {
// 新用户登录
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
} else {
// 前一个登录用户重新登录
var preClassCode = sharedPreferences.getString('pre_classCode') ?? '';
var preUserType = sharedPreferences.getInt('pre_userType') ?? 0;
var preStuId = sharedPreferences.getString('pre_stuId') ?? '';
if (preClassCode != '' &&
roles.any((element) =>
element['classCode'] == preClassCode &&
element['userType'] == preUserType &&
element['stuId'] == preStuId)) {
classCode = preClassCode;
userType = preUserType;
stuId = preStuId;
} else {
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
}
}
sharedPreferences.setString('auth_sessionCode', sessionCode);
sharedPreferences.setString('auth_userCode', userCode);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
sharedPreferences.setString('auth_className', className);
sharedPreferences.setString('auth_stuName', stuName);
sharedPreferences.setString('auth_relation', relation);
router.go(
'/web',
extra: {
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
} }
void toggleAgreed(bool value) { void toggleAgreed(bool value) {
......
...@@ -4,6 +4,7 @@ import 'package:appframe/config/constant.dart'; ...@@ -4,6 +4,7 @@ import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart'; import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/utils/login_util.dart';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -11,7 +12,6 @@ import 'package:flutter/material.dart'; ...@@ -11,7 +12,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginQrState extends Equatable { class LoginQrState extends Equatable {
final int status; final int status;
...@@ -120,11 +120,18 @@ class LoginQrCubit extends Cubit<LoginQrState> { ...@@ -120,11 +120,18 @@ class LoginQrCubit extends Cubit<LoginQrState> {
int? errCode = response.errCode; int? errCode = response.errCode;
if (errCode != null && errCode == 0) { if (errCode != null && errCode == 0) {
_doLogin(response.authCode!); _doLogin(response.authCode!);
} else { } else if (errCode == -4) {
// 用户主动取消
emit(state.copyWith( emit(state.copyWith(
status: 3, status: 3,
tip: '你已取消此次登录', tip: '你已取消此次登录',
)); ));
} else {
// 其它非 0 错误码(含二维码过期/超时/网络异常等),引导用户刷新
emit(state.copyWith(
status: 4,
tip: '请刷新二维码,再次登录',
));
} }
} }
} }
...@@ -150,101 +157,27 @@ class LoginQrCubit extends Cubit<LoginQrState> { ...@@ -150,101 +157,27 @@ class LoginQrCubit extends Cubit<LoginQrState> {
} }
var data = resultData['data'] as Map<String, dynamic>; var data = resultData['data'] as Map<String, dynamic>;
_handleLoginSuccess(data); LoginUtil.handleLoginSuccess(data, 0, 'router');
} }
void _handleLoginSuccess(Map<String, dynamic> data) { void goLoginMain() {
var roles = data['roles']; router.go('/loginMain');
// 过滤出家长角色的数据 }
if (roles?.isNotEmpty ?? false) {
roles.removeWhere((element) => element['userType'] != 2);
} else {
roles = [];
}
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var className = '';
var stuName = '';
var relation = '';
var sharedPreferences = getIt.get<SharedPreferences>();
if (roles.isNotEmpty) {
var role = roles[0];
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
className = role['className'];
stuName = role['stuName'];
relation = role['relation'];
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
for (var role in roles) {
classIdList.add(role['classCode'] as String);
}
debugPrint('classCodeIds:-------------- $classIdList');
sharedPreferences.setStringList(Constant.classIdSetKey, classIdList);
} else {
sharedPreferences.setStringList(Constant.classIdSetKey, []);
}
var preUserCode = sharedPreferences.getString('pre_userCode') ?? '';
if (userCode != preUserCode) {
// 新用户登录
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
} else {
// 前一个登录用户重新登录
var preClassCode = sharedPreferences.getString('pre_classCode') ?? '';
var preUserType = sharedPreferences.getInt('pre_userType') ?? 0;
var preStuId = sharedPreferences.getString('pre_stuId') ?? '';
if (preClassCode != '' &&
roles.any((element) =>
element['classCode'] == preClassCode &&
element['userType'] == preUserType &&
element['stuId'] == preStuId)) {
classCode = preClassCode;
userType = preUserType;
stuId = preStuId;
} else {
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
}
}
sharedPreferences.setString('auth_sessionCode', sessionCode);
sharedPreferences.setString('auth_userCode', userCode);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
sharedPreferences.setString('auth_className', className);
sharedPreferences.setString('auth_stuName', stuName);
sharedPreferences.setString('auth_relation', relation);
router.go( void goLoginPhone() {
'/web', router.go('/loginPhone');
extra: {
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
} }
void goLoginMain() { /// 刷新二维码:停止当前授权流程并重新走 init() 生成新的二维码
router.go('/loginMain'); Future<void> refresh() async {
// 重置为加载中状态(tip 与初次进入区分,避免 Equatable 短路掉相同 state 的 emit)
emit(const LoginQrState(status: 0, tip: '正在重新生成二维码...'));
try {
_fluwx.stopAuthByQRCode();
} catch (e) {
debugPrint('stopAuthByQRCode error: $e');
}
await init();
} }
@override @override
......
import 'dart:async';
import 'dart:io';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class AccountAppleState extends Equatable {
final String appleId;
final bool allowBind;
const AccountAppleState({
this.appleId = '',
this.allowBind = false,
});
AccountAppleState copyWith({
String? appleId,
bool? allowBind,
}) {
return AccountAppleState(
appleId: appleId ?? this.appleId,
allowBind: allowBind ?? this.allowBind,
);
}
@override
List<Object?> get props => [
appleId,
allowBind,
];
}
class AccountAppleCubit extends Cubit<AccountAppleState> {
late final UserAuthRepository _userAuthRepository;
AccountAppleCubit(super.initialState) {
_userAuthRepository = getIt.get<UserAuthRepository>();
}
///
/// 更换绑定Apple账号
///
void change() async {
// 仅iOS平台支持Apple登录
if (!Platform.isIOS) {
Fluttertoast.showToast(msg: '当前平台不支持Apple账号绑定', gravity: ToastGravity.TOP);
return;
}
try {
// 1. 获取Apple授权凭证
AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
debugPrint('Apple更换绑定 - 用户唯一标识: ${credential.userIdentifier}');
debugPrint('Apple更换绑定 - 授权码: ${credential.authorizationCode}');
debugPrint('Apple更换绑定 - 身份令牌: ${credential.identityToken}');
if (credential.userIdentifier == null) {
Fluttertoast.showToast(msg: '授权失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
return;
}
// 2. 获取当前用户信息
var sharedPreferences = getIt.get<SharedPreferences>();
var currentUserCode = sharedPreferences.getString('auth_userCode') ?? '';
if (currentUserCode.isEmpty) {
Fluttertoast.showToast(msg: '用户信息获取失败,请重新登录', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
return;
}
// 3. 调用绑定接口
var bindResult = await _userAuthRepository.newBinding(
credential.userIdentifier!,
currentUserCode,
'apple',
) as Map<String, dynamic>?;
if (bindResult != null && bindResult['code'] == 0) {
Fluttertoast.showToast(msg: 'Apple账号更换绑定成功', gravity: ToastGravity.TOP);
emit(state.copyWith(appleId: credential.userIdentifier!));
router.pop({'appleId': credential.userIdentifier!});
} else {
var errorMsg = bindResult?['error'] ?? 'Apple账号更换绑定失败';
Fluttertoast.showToast(msg: errorMsg, gravity: ToastGravity.TOP, backgroundColor: Colors.red);
}
} catch (e) {
debugPrint('appleChange error: $e');
// 用户取消授权时不提示错误
if (e is SignInWithAppleAuthorizationException && e.code == AuthorizationErrorCode.canceled) {
return;
}
Fluttertoast.showToast(msg: 'Apple账号更换绑定异常', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
}
}
Future<void> unbindApple() async {
var sharedPreferences = getIt.get<SharedPreferences>();
var userCode = sharedPreferences.getString('auth_userCode') ?? '';
var result = await _userAuthRepository.unbind(userCode, null);
if (result == null) {
Fluttertoast.showToast(msg: '解绑失败');
return;
}
if (result['code'] != 0) {
Fluttertoast.showToast(msg: result['error']);
return;
}
router.pop({'appleId': ''});
}
@override
Future<void> close() async {
await super.close();
}
}
import 'dart:io';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart'; import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class AccountState extends Equatable { class AccountState extends Equatable {
final bool loaded; final bool loaded;
...@@ -12,6 +18,8 @@ class AccountState extends Equatable { ...@@ -12,6 +18,8 @@ class AccountState extends Equatable {
final String nickname; final String nickname;
final String imgIcon; final String imgIcon;
final String appleId;
// 孩子信息 // 孩子信息
final String className; final String className;
final String stuName; final String stuName;
...@@ -25,6 +33,7 @@ class AccountState extends Equatable { ...@@ -25,6 +33,7 @@ class AccountState extends Equatable {
this.phone = '', this.phone = '',
this.nickname = '', this.nickname = '',
this.imgIcon = '', this.imgIcon = '',
this.appleId = '',
this.className = '', this.className = '',
this.stuName = '', this.stuName = '',
this.bindStu = true, this.bindStu = true,
...@@ -36,6 +45,7 @@ class AccountState extends Equatable { ...@@ -36,6 +45,7 @@ class AccountState extends Equatable {
String? phone, String? phone,
String? nickname, String? nickname,
String? imgIcon, String? imgIcon,
String? appleId,
String? className, String? className,
String? stuName, String? stuName,
bool? bindStu, bool? bindStu,
...@@ -46,6 +56,7 @@ class AccountState extends Equatable { ...@@ -46,6 +56,7 @@ class AccountState extends Equatable {
phone: phone ?? this.phone, phone: phone ?? this.phone,
nickname: nickname ?? this.nickname, nickname: nickname ?? this.nickname,
imgIcon: imgIcon ?? this.imgIcon, imgIcon: imgIcon ?? this.imgIcon,
appleId: appleId ?? this.appleId,
className: className ?? this.className, className: className ?? this.className,
stuName: stuName ?? this.stuName, stuName: stuName ?? this.stuName,
bindStu: bindStu ?? this.bindStu, bindStu: bindStu ?? this.bindStu,
...@@ -59,6 +70,7 @@ class AccountState extends Equatable { ...@@ -59,6 +70,7 @@ class AccountState extends Equatable {
phone, phone,
nickname, nickname,
imgIcon, imgIcon,
appleId,
className, className,
stuName, stuName,
bindStu, bindStu,
...@@ -67,9 +79,11 @@ class AccountState extends Equatable { ...@@ -67,9 +79,11 @@ class AccountState extends Equatable {
class AccountCubit extends Cubit<AccountState> { class AccountCubit extends Cubit<AccountState> {
late final PhoneAuthRepository _phoneAuthRepository; late final PhoneAuthRepository _phoneAuthRepository;
late final UserAuthRepository _userAuthRepository;
AccountCubit(super.initialState) { AccountCubit(super.initialState) {
_phoneAuthRepository = getIt.get<PhoneAuthRepository>(); _phoneAuthRepository = getIt.get<PhoneAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
init(); init();
} }
...@@ -80,26 +94,37 @@ class AccountCubit extends Cubit<AccountState> { ...@@ -80,26 +94,37 @@ class AccountCubit extends Cubit<AccountState> {
var stuName = sharedPreferences.getString('auth_stuName') ?? ''; var stuName = sharedPreferences.getString('auth_stuName') ?? '';
try { try {
var result = await _phoneAuthRepository.bindCheck(userCode); var result = await _phoneAuthRepository.bindCheck(userCode);
var code = result['code']; if (result != null) {
var data = result['data']; var code = result['code'];
if (code != 0) { var data = result['data'];
emit(state.copyWith(loaded: true, bindStu: false)); if (code != 0) {
return; emit(state.copyWith(loaded: true, bindStu: false));
return;
}
emit(
state.copyWith(
loaded: true,
name: data['name'],
phone: data['phone'],
nickname: data['nickname'],
imgIcon: data['imgIcon'],
className: className,
stuName: stuName,
),
);
} }
emit( var result2 = await _userAuthRepository.binded(userCode);
state.copyWith( if (result2 != null) {
loaded: true, var code2 = result2['code'];
name: data['name'], var data2 = result2['data'];
phone: data['phone'], if (code2 == 0 && data2 != null) {
nickname: data['nickname'], emit(state.copyWith(appleId: data2['appleId']));
imgIcon: data['imgIcon'], }
className: className, }
stuName: stuName,
),
);
} catch (e) { } catch (e) {
print(e); debugPrint('init error: $e');
} }
} }
...@@ -120,19 +145,22 @@ class AccountCubit extends Cubit<AccountState> { ...@@ -120,19 +145,22 @@ class AccountCubit extends Cubit<AccountState> {
} }
Future<void> goBind() async { Future<void> goBind() async {
String? result = await router.push( dynamic result = await router.push(
'/account/phone', '/account/phone',
extra: { extra: {
'phone': state.phone, 'phone': state.phone,
}, },
); );
if (result != null && result.isNotEmpty) { if (result != null) {
emit(state.copyWith(phone: result)); emit(state.copyWith(phone: result['phone']));
} }
// if (result != null && result.isNotEmpty) {
// emit(state.copyWith(phone: result));
// }
} }
void goLogoff() { Future<void> goLogoff() async {
router.push( await router.push(
'/account/logoff', '/account/logoff',
extra: { extra: {
'phone': state.phone, 'phone': state.phone,
...@@ -140,6 +168,84 @@ class AccountCubit extends Cubit<AccountState> { ...@@ -140,6 +168,84 @@ class AccountCubit extends Cubit<AccountState> {
); );
} }
Future<void> goApple() async {
dynamic result = await router.push(
'/account/apple',
extra: {
'appleId': state.appleId,
},
);
if (result != null) {
emit(state.copyWith(appleId: result['appleId']));
}
}
///
/// 绑定Apple ID
///
void appleBind() async {
// 仅iOS平台支持Apple登录
if (!Platform.isIOS) {
Fluttertoast.showToast(msg: '当前平台不支持Apple账号绑定', gravity: ToastGravity.TOP);
return;
}
// 已绑定Apple ID,不允许重复绑定
if (state.appleId.isNotEmpty) {
Fluttertoast.showToast(msg: '已绑定Apple账号', gravity: ToastGravity.TOP);
return;
}
try {
// 1. 获取Apple授权凭证
AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
debugPrint('Apple绑定 - 用户唯一标识: ${credential.userIdentifier}');
debugPrint('Apple绑定 - 授权码: ${credential.authorizationCode}');
debugPrint('Apple绑定 - 身份令牌: ${credential.identityToken}');
if (credential.userIdentifier == null) {
Fluttertoast.showToast(msg: '授权失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
return;
}
// 2. 获取当前用户信息
var sharedPreferences = getIt.get<SharedPreferences>();
var currentUserCode = sharedPreferences.getString('auth_userCode') ?? '';
if (currentUserCode.isEmpty) {
Fluttertoast.showToast(msg: '用户信息获取失败,请重新登录', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
return;
}
// 3. 调用绑定接口
var bindResult = await _userAuthRepository.newBinding(
credential.userIdentifier!,
currentUserCode,
'apple',
) as Map<String, dynamic>?;
if (bindResult != null && bindResult['code'] == 0) {
Fluttertoast.showToast(msg: 'Apple账号绑定成功', gravity: ToastGravity.TOP);
emit(state.copyWith(appleId: credential.userIdentifier!));
} else {
var errorMsg = bindResult?['error'] ?? 'Apple账号绑定失败';
Fluttertoast.showToast(msg: errorMsg, gravity: ToastGravity.TOP, backgroundColor: Colors.red);
}
} catch (e) {
debugPrint('appleBind error: $e');
// 用户取消授权时不提示错误
if (e is SignInWithAppleAuthorizationException && e.code == AuthorizationErrorCode.canceled) {
return;
}
Fluttertoast.showToast(msg: 'Apple账号绑定异常', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
}
}
Future<void> unbind() async { Future<void> unbind() async {
// var sharedPreferences = getIt.get<SharedPreferences>(); // var sharedPreferences = getIt.get<SharedPreferences>();
// var userCode = sharedPreferences.getString('auth_userCode') ?? ''; // var userCode = sharedPreferences.getString('auth_userCode') ?? '';
......
...@@ -3,6 +3,7 @@ import 'dart:async'; ...@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart'; import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
...@@ -60,6 +61,7 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> { ...@@ -60,6 +61,7 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> {
int countdown = 60; int countdown = 60;
late final PhoneAuthRepository _phoneAuthRepository; late final PhoneAuthRepository _phoneAuthRepository;
late final UserAuthRepository _userAuthRepository;
TextEditingController get codeController => _codeController; TextEditingController get codeController => _codeController;
...@@ -69,6 +71,7 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> { ...@@ -69,6 +71,7 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> {
_codeController.text = ''; _codeController.text = '';
_phoneAuthRepository = getIt.get<PhoneAuthRepository>(); _phoneAuthRepository = getIt.get<PhoneAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
} }
/// 开始倒计时 /// 开始倒计时
...@@ -154,6 +157,23 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> { ...@@ -154,6 +157,23 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> {
router.go('/loginMain'); router.go('/loginMain');
} }
Future<void> doLogoff() async {
if (RegExp(r'^1[3-9][0-9]{9}$').hasMatch(state.phone)) {
_userAuthRepository.unbind(null, state.phone);
}
await Future.delayed(Duration(seconds: 1));
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) {
if (key.startsWith('auth_')) {
sharedPreferences.remove(key);
}
});
router.go('/loginMain');
}
@override @override
Future<void> close() async { Future<void> close() async {
_timer?.cancel(); _timer?.cancel();
......
...@@ -3,44 +3,42 @@ import 'dart:async'; ...@@ -3,44 +3,42 @@ import 'dart:async';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart'; import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class AccountPhoneState extends Equatable { class AccountPhoneState extends Equatable {
final String phone; final String phone;
final bool allowBind; final bool allowBind;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend; final bool allowSend;
final int seconds; final int seconds;
final bool allowSubmit;
const AccountPhoneState({ const AccountPhoneState({
this.phone = '', this.phone = '',
this.allowBind = false, this.allowBind = false,
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true, this.allowSend = true,
this.seconds = 0, this.seconds = 0,
this.allowSubmit = false,
}); });
AccountPhoneState copyWith({ AccountPhoneState copyWith({
String? phone, String? phone,
bool? allowBind, bool? allowBind,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend, bool? allowSend,
int? seconds, int? seconds,
bool? allowSubmit,
}) { }) {
return AccountPhoneState( return AccountPhoneState(
phone: phone ?? this.phone, phone: phone ?? this.phone,
allowBind: allowBind ?? this.allowBind, allowBind: allowBind ?? this.allowBind,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend, allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds, seconds: seconds ?? this.seconds,
allowSubmit: allowSubmit ?? this.allowSubmit,
); );
} }
...@@ -48,10 +46,9 @@ class AccountPhoneState extends Equatable { ...@@ -48,10 +46,9 @@ class AccountPhoneState extends Equatable {
List<Object?> get props => [ List<Object?> get props => [
phone, phone,
allowBind, allowBind,
showSnackBar,
snackBarMsg,
allowSend, allowSend,
seconds, seconds,
allowSubmit,
]; ];
} }
...@@ -62,6 +59,7 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> { ...@@ -62,6 +59,7 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
int countdown = 60; // 倒计时秒数 int countdown = 60; // 倒计时秒数
late final PhoneAuthRepository _phoneAuthRepository; late final PhoneAuthRepository _phoneAuthRepository;
late final UserAuthRepository _userAuthRepository;
TextEditingController get phoneController => _phoneController; TextEditingController get phoneController => _phoneController;
...@@ -75,6 +73,16 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> { ...@@ -75,6 +73,16 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
_codeController.text = ''; _codeController.text = '';
_phoneAuthRepository = getIt.get<PhoneAuthRepository>(); _phoneAuthRepository = getIt.get<PhoneAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
_phoneController.addListener(_updateAllowSubmit);
_codeController.addListener(_updateAllowSubmit);
}
void _updateAllowSubmit() {
final phoneOk = RegExp(r'^1[3-9][0-9]{9}$').hasMatch(_phoneController.text);
final codeOk = RegExp(r'^\d{4}$').hasMatch(_codeController.text);
emit(state.copyWith(allowSubmit: phoneOk && codeOk));
} }
void change(){ void change(){
...@@ -103,16 +111,14 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> { ...@@ -103,16 +111,14 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
// 验证手机号码 // 验证手机号码
String phone = _phoneController.text; String phone = _phoneController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) { if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码')); Fluttertoast.showToast(msg: '请输入正确的手机号码');
emit(state.copyWith(showSnackBar: false));
return; return;
} }
// 发送验证码 // 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone, 1); var result = await _phoneAuthRepository.verifyCode(phone, 1);
if (result['code'] != 0) { if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error'])); Fluttertoast.showToast(msg: result['error']);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
...@@ -128,14 +134,12 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> { ...@@ -128,14 +134,12 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
String verifyCode = _codeController.text; String verifyCode = _codeController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) { if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码')); Fluttertoast.showToast(msg: '请输入正确的手机号码');
emit(state.copyWith(showSnackBar: false));
return; return;
} }
if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) { if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的验证码')); Fluttertoast.showToast(msg: '请输入正确的验证码');
emit(state.copyWith(showSnackBar: false));
return; return;
} }
...@@ -143,13 +147,27 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> { ...@@ -143,13 +147,27 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
var userCode = sharedPreferences.getString('auth_userCode') ?? ''; var userCode = sharedPreferences.getString('auth_userCode') ?? '';
var result = await _phoneAuthRepository.bind(userCode, phone, verifyCode); var result = await _phoneAuthRepository.bind(userCode, phone, verifyCode);
if (result['code'] != 0) { if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error'])); Fluttertoast.showToast(msg: result['error']);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
// 绑定成功,返回手机号码 // 绑定成功,返回手机号码
router.pop(phone); router.pop({'phone': phone});
}
Future<void> unbindPhone() async {
var result = await _userAuthRepository.unbind(null, state.phone);
if(result==null) {
Fluttertoast.showToast(msg: '解绑失败');
return;
}
if (result['code'] != 0) {
Fluttertoast.showToast(msg: result['error']);
return;
}
// 解绑成功,路由出栈
router.pop({'phone': ''});
} }
@override @override
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!