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/routes.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: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 AccountState extends Equatable {
final bool loaded;
......@@ -12,6 +18,8 @@ class AccountState extends Equatable {
final String nickname;
final String imgIcon;
final String appleId;
// 孩子信息
final String className;
final String stuName;
......@@ -25,6 +33,7 @@ class AccountState extends Equatable {
this.phone = '',
this.nickname = '',
this.imgIcon = '',
this.appleId = '',
this.className = '',
this.stuName = '',
this.bindStu = true,
......@@ -36,6 +45,7 @@ class AccountState extends Equatable {
String? phone,
String? nickname,
String? imgIcon,
String? appleId,
String? className,
String? stuName,
bool? bindStu,
......@@ -46,6 +56,7 @@ class AccountState extends Equatable {
phone: phone ?? this.phone,
nickname: nickname ?? this.nickname,
imgIcon: imgIcon ?? this.imgIcon,
appleId: appleId ?? this.appleId,
className: className ?? this.className,
stuName: stuName ?? this.stuName,
bindStu: bindStu ?? this.bindStu,
......@@ -59,6 +70,7 @@ class AccountState extends Equatable {
phone,
nickname,
imgIcon,
appleId,
className,
stuName,
bindStu,
......@@ -67,9 +79,11 @@ class AccountState extends Equatable {
class AccountCubit extends Cubit<AccountState> {
late final PhoneAuthRepository _phoneAuthRepository;
late final UserAuthRepository _userAuthRepository;
AccountCubit(super.initialState) {
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
init();
}
......@@ -80,26 +94,37 @@ class AccountCubit extends Cubit<AccountState> {
var stuName = sharedPreferences.getString('auth_stuName') ?? '';
try {
var result = await _phoneAuthRepository.bindCheck(userCode);
var code = result['code'];
var data = result['data'];
if (code != 0) {
emit(state.copyWith(loaded: true, bindStu: false));
return;
if (result != null) {
var code = result['code'];
var data = result['data'];
if (code != 0) {
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(
state.copyWith(
loaded: true,
name: data['name'],
phone: data['phone'],
nickname: data['nickname'],
imgIcon: data['imgIcon'],
className: className,
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) {
print(e);
debugPrint('init error: $e');
}
}
......@@ -120,19 +145,22 @@ class AccountCubit extends Cubit<AccountState> {
}
Future<void> goBind() async {
String? result = await router.push(
dynamic result = await router.push(
'/account/phone',
extra: {
'phone': state.phone,
},
);
if (result != null && result.isNotEmpty) {
emit(state.copyWith(phone: result));
if (result != null) {
emit(state.copyWith(phone: result['phone']));
}
// if (result != null && result.isNotEmpty) {
// emit(state.copyWith(phone: result));
// }
}
void goLogoff() {
router.push(
Future<void> goLogoff() async {
await router.push(
'/account/logoff',
extra: {
'phone': state.phone,
......@@ -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 {
// var sharedPreferences = getIt.get<SharedPreferences>();
// var userCode = sharedPreferences.getString('auth_userCode') ?? '';
......
......@@ -3,6 +3,7 @@ import 'dart:async';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
......@@ -60,6 +61,7 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> {
int countdown = 60;
late final PhoneAuthRepository _phoneAuthRepository;
late final UserAuthRepository _userAuthRepository;
TextEditingController get codeController => _codeController;
......@@ -69,6 +71,7 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> {
_codeController.text = '';
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
}
/// 开始倒计时
......@@ -154,6 +157,23 @@ class AccountLogoffCubit extends Cubit<AccountLogoffState> {
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
Future<void> close() async {
_timer?.cancel();
......
......@@ -3,44 +3,42 @@ import 'dart:async';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.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:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AccountPhoneState extends Equatable {
final String phone;
final bool allowBind;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend;
final int seconds;
final bool allowSubmit;
const AccountPhoneState({
this.phone = '',
this.allowBind = false,
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true,
this.seconds = 0,
this.allowSubmit = false,
});
AccountPhoneState copyWith({
String? phone,
bool? allowBind,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend,
int? seconds,
bool? allowSubmit,
}) {
return AccountPhoneState(
phone: phone ?? this.phone,
allowBind: allowBind ?? this.allowBind,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds,
allowSubmit: allowSubmit ?? this.allowSubmit,
);
}
......@@ -48,10 +46,9 @@ class AccountPhoneState extends Equatable {
List<Object?> get props => [
phone,
allowBind,
showSnackBar,
snackBarMsg,
allowSend,
seconds,
allowSubmit,
];
}
......@@ -62,6 +59,7 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
int countdown = 60; // 倒计时秒数
late final PhoneAuthRepository _phoneAuthRepository;
late final UserAuthRepository _userAuthRepository;
TextEditingController get phoneController => _phoneController;
......@@ -75,6 +73,16 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
_codeController.text = '';
_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(){
......@@ -103,16 +111,14 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
// 验证手机号码
String phone = _phoneController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '请输入正确的手机号码');
return;
}
// 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone, 1);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: result['error']);
return;
}
......@@ -128,14 +134,12 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
String verifyCode = _codeController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '请输入正确的手机号码');
return;
}
if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的验证码'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '请输入正确的验证码');
return;
}
......@@ -143,13 +147,27 @@ class AccountPhoneCubit extends Cubit<AccountPhoneState> {
var userCode = sharedPreferences.getString('auth_userCode') ?? '';
var result = await _phoneAuthRepository.bind(userCode, phone, verifyCode);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: result['error']);
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
......
......@@ -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_user_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/widgets/ios_edge_swipe_detector.dart';
import 'package:flutter/material.dart';
......@@ -66,13 +70,21 @@ final GoRouter router = GoRouter(
GoRoute(
path: '/account',
builder: (BuildContext context, GoRouterState state) {
return const AccountPage();
// return const AccountPage();
return const AccountPageV2();
},
),
GoRoute(
path: '/account/phone',
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(
......@@ -84,7 +96,8 @@ final GoRouter router = GoRouter(
GoRoute(
path: '/account/logoff',
builder: (BuildContext context, GoRouterState state) {
return const AccountLogoffPage();
// return const AccountLogoffPage();
return const AccountLogoffPageV2();
},
),
GoRoute(
......
......@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
class UserAuthRepository {
late final ApiService _appService;
......@@ -91,4 +92,41 @@ class UserAuthRepository {
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 {
),
);
},
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,
),
),
),
listener: (context, state) {},
),
);
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!