Commit 2dc71814 by tanghuan

整改账号与安全相关功能界面

1 parent dc801714
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,6 +94,7 @@ class AccountCubit extends Cubit<AccountState> { ...@@ -80,6 +94,7 @@ 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);
if (result != null) {
var code = result['code']; var code = result['code'];
var data = result['data']; var data = result['data'];
if (code != 0) { if (code != 0) {
...@@ -98,8 +113,18 @@ class AccountCubit extends Cubit<AccountState> { ...@@ -98,8 +113,18 @@ class AccountCubit extends Cubit<AccountState> {
stuName: stuName, stuName: stuName,
), ),
); );
}
var result2 = await _userAuthRepository.binded(userCode);
if (result2 != null) {
var code2 = result2['code'];
var data2 = result2['data'];
if (code2 == 0 && data2 != null) {
emit(state.copyWith(appleId: data2['appleId']));
}
}
} 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
......
...@@ -15,6 +15,10 @@ import 'package:appframe/ui/pages/setting/account_page.dart'; ...@@ -15,6 +15,10 @@ import 'package:appframe/ui/pages/setting/account_page.dart';
import 'package:appframe/ui/pages/setting/account_phone_page.dart'; import 'package:appframe/ui/pages/setting/account_phone_page.dart';
import 'package:appframe/ui/pages/setting/account_user_page.dart'; import 'package:appframe/ui/pages/setting/account_user_page.dart';
import 'package:appframe/ui/pages/setting/setting_page.dart'; import 'package:appframe/ui/pages/setting/setting_page.dart';
import 'package:appframe/ui/pages/setting_v2/account_apple_page_v2.dart';
import 'package:appframe/ui/pages/setting_v2/account_logoff_page_v2.dart';
import 'package:appframe/ui/pages/setting_v2/account_page_v2.dart';
import 'package:appframe/ui/pages/setting_v2/account_phone_page_v2.dart';
import 'package:appframe/ui/pages/web_page.dart'; import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/widgets/ios_edge_swipe_detector.dart'; import 'package:appframe/ui/widgets/ios_edge_swipe_detector.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
...@@ -66,13 +70,21 @@ final GoRouter router = GoRouter( ...@@ -66,13 +70,21 @@ final GoRouter router = GoRouter(
GoRoute( GoRoute(
path: '/account', path: '/account',
builder: (BuildContext context, GoRouterState state) { builder: (BuildContext context, GoRouterState state) {
return const AccountPage(); // return const AccountPage();
return const AccountPageV2();
}, },
), ),
GoRoute( GoRoute(
path: '/account/phone', path: '/account/phone',
builder: (BuildContext context, GoRouterState state) { builder: (BuildContext context, GoRouterState state) {
return const AccountPhonePage(); // return const AccountPhonePage();
return const AccountPhonePageV2();
},
),
GoRoute(
path: '/account/apple',
builder: (BuildContext context, GoRouterState state) {
return const AccountApplePageV2();
}, },
), ),
GoRoute( GoRoute(
...@@ -84,7 +96,8 @@ final GoRouter router = GoRouter( ...@@ -84,7 +96,8 @@ final GoRouter router = GoRouter(
GoRoute( GoRoute(
path: '/account/logoff', path: '/account/logoff',
builder: (BuildContext context, GoRouterState state) { builder: (BuildContext context, GoRouterState state) {
return const AccountLogoffPage(); // return const AccountLogoffPage();
return const AccountLogoffPageV2();
}, },
), ),
GoRoute( GoRoute(
......
...@@ -3,6 +3,7 @@ import 'dart:io'; ...@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart'; import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
class UserAuthRepository { class UserAuthRepository {
late final ApiService _appService; late final ApiService _appService;
...@@ -91,4 +92,41 @@ class UserAuthRepository { ...@@ -91,4 +92,41 @@ class UserAuthRepository {
return null; return null;
} }
} }
///
/// 查询绑定信息
///
/// 返回示例 {"cdoe":0,"data":{"appleId":"001161.5105e0b7b7b7429fabda11c8d28af432.0846","phone":"","unionId":"oCyks03FGEe1RfBMtKfZlMeLR31o"}}
///
Future<dynamic> binded(String userId) async {
try {
Response resp = await _appService.post(
'/api/v1/comm/user/binded',
{"userId": userId},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
} on DioException {
return null;
}
}
Future<dynamic> unbind(String? userId, String? phone) async {
try {
final body = <String, dynamic>{};
if (userId != null && userId.isNotEmpty) {
body['userId'] = userId;
} else if (phone != null && phone.isNotEmpty) {
body['phone'] = phone;
}
Response resp = await _appService.post(
'/api/v1/comm/user/unbind',
body,
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
} on DioException catch (e) {
debugPrint('unbind error: $e');
return null;
}
}
} }
...@@ -225,42 +225,8 @@ class AccountPhonePage extends StatelessWidget { ...@@ -225,42 +225,8 @@ class AccountPhonePage extends StatelessWidget {
), ),
); );
}, },
listener: (context, state) { listener: (context, state) {},
if (state.showSnackBar) {
_showTip(context, state.snackBarMsg);
}
},
),
);
}
void _showTip(BuildContext context, String tip) {
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: 200,
left: 20,
right: 20,
child: Material(
color: Colors.transparent,
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8),
),
child: Text(
tip,
style: TextStyle(color: Colors.white),
textAlign: TextAlign.center,
),
),
),
), ),
); );
Overlay.of(context).insert(overlayEntry);
Future.delayed(Duration(seconds: 2), () {
overlayEntry.remove();
});
} }
} }
import 'package:appframe/bloc/setting/account_apple_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class AccountApplePageV2 extends StatelessWidget {
const AccountApplePageV2({super.key});
@override
Widget build(BuildContext context) {
final Map<String, dynamic>? extraData = GoRouterState.of(context).extra as Map<String, dynamic>?;
var appleId = extraData?['appleId'] ?? '';
return BlocProvider(
create: (context) => AccountAppleCubit(AccountAppleState(appleId: appleId)),
child: BlocConsumer<AccountAppleCubit, AccountAppleState>(
builder: (context, state) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text(
'Apple账号',
style: TextStyle(
color: Color(0xFF333333),
fontSize: 16,
fontWeight: FontWeight.bold,
height: 20 / 16,
),
),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0,
iconTheme: const IconThemeData(color: Colors.black),
),
body: (state.appleId != '' && !state.allowBind)
? _buildBound(context, state)
: _buildPlaceholder(context, state),
);
},
listener: (context, state) {},
),
);
}
// 已绑定状态视图
Widget _buildBound(BuildContext context, AccountAppleState state) {
return Column(
children: [
const SizedBox(height: 80),
const Text(
'已绑定Apple账号',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.normal,
letterSpacing: 0,
color: Color(0xFF333333),
),
),
const SizedBox(height: 15),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Text(
state.appleId,
style: const TextStyle(
fontSize: 21,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
),
const Spacer(),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 15),
child: SizedBox(
width: double.infinity,
height: 44,
child: ElevatedButton(
onPressed: () {
context.read<AccountAppleCubit>().change();
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF7691FA),
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(22),
),
),
child: const Text(
'更换绑定Apple账号',
style: TextStyle(
fontSize: 16,
height: 20 / 16,
color: Colors.white,
),
),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 40),
child: SizedBox(
width: double.infinity,
height: 44,
child: OutlinedButton(
onPressed: () => _showUnbindDialog(context),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF333333),
side: const BorderSide(color: Color(0xFFE5E5E5), width: 0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
),
child: const Text(
'解绑',
style: TextStyle(
fontSize: 16,
height: 20 / 16,
color: Color(0xFF333333),
),
),
),
),
),
],
);
}
void _showUnbindDialog(BuildContext context) {
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext ctx) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: const Text(
'提示',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
),
textAlign: TextAlign.center,
),
content: const Text.rich(
TextSpan(
text: '解绑后将无法继续使用Apple账号登录该班小二账号',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
),
actions: [
Table(
children: [
TableRow(
children: [
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(ctx).pop();
},
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF333333),
textStyle: const TextStyle(fontSize: 17),
minimumSize: const Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: const Text('取消'),
),
),
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(ctx).pop();
context.read<AccountAppleCubit>().unbindApple();
},
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF7691FA),
textStyle: const TextStyle(fontSize: 17),
minimumSize: const Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: const Text('确定'),
),
),
],
),
],
),
],
);
},
);
}
// 未绑定/更换绑定占位视图
Widget _buildPlaceholder(BuildContext context, AccountAppleState state) {
return const Center(
child: Text(
'未绑定Apple账号',
style: TextStyle(
fontSize: 16,
color: Color(0xFF999999),
),
),
);
}
}
import 'package:appframe/bloc/setting/account_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AccountPageV2 extends StatelessWidget {
const AccountPageV2({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => AccountCubit(AccountState()),
child: BlocConsumer<AccountCubit, AccountState>(
builder: (context, state) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text(
'账号与安全',
style: TextStyle(
color: Color(0xFF333333),
fontSize: 16,
fontWeight: FontWeight.bold,
height: 20 / 16,
),
),
centerTitle: true,
backgroundColor: Colors.white,
elevation: 0,
iconTheme: const IconThemeData(color: Colors.black),
),
body: state.loaded
? _buildBody(context, state)
: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(color: Color(0xFF7691FA)),
SizedBox(height: 16),
Text('加载中...'),
],
),
),
);
},
listener: (context, state) {},
),
);
}
Widget _buildBody(BuildContext context, AccountState state) {
final cubit = context.read<AccountCubit>();
return Column(
children: [
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: [
const Divider(height: 1, thickness: 0.5, color: Color(0xFFE5E5E5)),
_buildItem(
label: '微信账号',
value: state.name.isNotEmpty ? state.nickname : '未绑定',
showArrow: false,
onTap: null,
),
const Divider(height: 1, thickness: 0.5, color: Color(0xFFE5E5E5)),
_buildItem(
label: '手机号',
value: state.phone.isNotEmpty
? '${state.phone.substring(0, 3)}****${state.phone.substring(7, 11)}'
: '未绑定',
showArrow: true,
onTap: () => cubit.goBind(),
),
const Divider(height: 1, thickness: 0.5, color: Color(0xFFE5E5E5)),
_buildItem(
label: 'Apple账号',
value: state.appleId.isNotEmpty ? '已绑定' : '未绑定',
showArrow: true,
onTap: state.appleId.isEmpty ? () => cubit.appleBind() : () => cubit.goApple(),
),
const Divider(height: 1, thickness: 0.5, color: Color(0xFFE5E5E5)),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 40),
child: SizedBox(
width: double.infinity,
height: 44,
child: OutlinedButton(
onPressed: () => cubit.goLogoff(),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF333333),
side: const BorderSide(color: Color(0xFFE5E5E5), width: 0.5),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
),
child: const Text(
'注销用户',
style: TextStyle(
fontSize: 16,
height: 20 / 16,
color: Color(0xFF333333),
),
),
),
),
),
],
);
}
Widget _buildItem({
required String label,
required String value,
required bool showArrow,
VoidCallback? onTap,
}) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 18),
child: Row(
children: [
Text(
label,
style: const TextStyle(
fontSize: 16,
height: 1.0,
color: Color(0xFF333333),
),
),
const Spacer(),
Text(
value,
style: const TextStyle(
fontSize: 16,
color: Color(0xFF333333),
),
),
if (showArrow) ...const [
SizedBox(width: 7.5),
Icon(Icons.arrow_forward_ios, size: 18, color: Color(0xFFCCCCCC)),
],
],
),
),
);
}
}
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!