Commit 990951ff by tanghuan

增加通过Apple登录的功能,以及优化了toast方式的错误信息提示

1 parent 75241a4c
import 'package:appframe/config/constant.dart'; 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/user_auth_repository.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart'; import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.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:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.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 LoginMainState extends Equatable { class LoginMainState extends Equatable {
final bool agreed; final bool agreed;
final bool showAgreed; final bool showAgreed;
final bool showNeedWechatForApple;
final int loginType; // 1=Wechat,2=Apple
final String appleUserIdentifier;
final bool loading; final bool loading;
final bool showSnackBar;
final String snackBarMsg;
const LoginMainState({ const LoginMainState({
this.agreed = false, this.agreed = false,
this.showAgreed = false, this.showAgreed = false,
this.showNeedWechatForApple = false,
this.loginType = 0,
this.appleUserIdentifier = '',
this.loading = false, this.loading = false,
this.showSnackBar = false,
this.snackBarMsg = '',
}); });
LoginMainState copyWith({ LoginMainState copyWith({
bool? agreed, bool? agreed,
bool? showAgreed, bool? showAgreed,
bool? showNeedWechatForApple,
int? loginType,
bool? loading, bool? loading,
bool? showSnackBar, String? appleUserIdentifier,
String? snackBarMsg,
}) { }) {
return LoginMainState( return LoginMainState(
agreed: agreed ?? this.agreed, agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed, showAgreed: showAgreed ?? this.showAgreed,
showNeedWechatForApple: showNeedWechatForApple ?? this.showNeedWechatForApple,
loginType: loginType ?? this.loginType,
loading: loading ?? this.loading, loading: loading ?? this.loading,
appleUserIdentifier: appleUserIdentifier ?? this.appleUserIdentifier,
); );
} }
...@@ -42,8 +51,9 @@ class LoginMainState extends Equatable { ...@@ -42,8 +51,9 @@ class LoginMainState extends Equatable {
agreed, agreed,
showAgreed, showAgreed,
loading, loading,
showSnackBar, showNeedWechatForApple,
snackBarMsg, loginType,
appleUserIdentifier,
]; ];
} }
...@@ -51,11 +61,13 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -51,11 +61,13 @@ class LoginMainCubit extends Cubit<LoginMainState> {
late final Fluwx _fluwx; late final Fluwx _fluwx;
late final FluwxCancelable _fluwxCancelable; late final FluwxCancelable _fluwxCancelable;
late final WechatAuthRepository _wechatAuthRepository; late final WechatAuthRepository _wechatAuthRepository;
late final UserAuthRepository _userAuthRepository;
LoginMainCubit(super.initialState) { LoginMainCubit(super.initialState) {
_fluwx = getIt.get<Fluwx>(); _fluwx = getIt.get<Fluwx>();
_fluwxCancelable = _fluwx.addSubscriber(_responseListener); _fluwxCancelable = _fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt.get<WechatAuthRepository>(); _wechatAuthRepository = getIt.get<WechatAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
} }
void toggleAgreed(bool value) { void toggleAgreed(bool value) {
...@@ -64,16 +76,79 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -64,16 +76,79 @@ class LoginMainCubit extends Cubit<LoginMainState> {
void confirmAgreed() { void confirmAgreed() {
emit(state.copyWith(agreed: true, showAgreed: false)); emit(state.copyWith(agreed: true, showAgreed: false));
wechatAuth(); if (state.loginType == 1) {
wechatAuth();
} else if (state.loginType == 2) {
appleAuth();
}
} }
void cancelAgreed() { void cancelAgreed() {
emit(state.copyWith(showAgreed: false)); emit(state.copyWith(showAgreed: false));
} }
void wechatAuth() async { ///
/// 通过 Apple 登录
///
void appleAuth() async {
if (!state.agreed) { if (!state.agreed) {
emit(state.copyWith(showAgreed: true)); emit(state.copyWith(showAgreed: true, loginType: 2));
return;
}
AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
debugPrint('用户唯一标识: ${credential.userIdentifier}');
debugPrint('用户邮箱: ${credential.email}');
debugPrint('用户姓名: ${credential.givenName} ${credential.familyName}');
// 应将 credential.authorizationCode 发送给后端服务器,由后端与 Apple 服务器验证该码的有效性
debugPrint('授权码 (authorizationCode): ${credential.authorizationCode}');
debugPrint('身份令牌 (identityToken): ${credential.identityToken}');
if (credential.userIdentifier == null) {
Fluttertoast.showToast(msg: '授权失败', backgroundColor: Colors.red);
return;
}
///
/// 处理 credential,发送到服务器获取用户信息。有关联用户信息,则登录成功;没有关联用户信息,则弹出提示框提示用户授权微信认证
///
var resultData = await _userAuthRepository.appleLogin(
credential.userIdentifier!, credential.authorizationCode, credential.identityToken!) as Map<String, dynamic>?;
if (resultData == null) {
Fluttertoast.showToast(msg: '登录请求处理失败', backgroundColor: Colors.red);
return;
}
if (resultData['code'] != 0) {
Fluttertoast.showToast(msg: resultData['error'], backgroundColor: Colors.red);
return;
}
var data = resultData['data'] as Map<String, dynamic>;
int binding = resultData['binding'];
if (binding == 1) {
_handleLoginSuccess(data);
} else {
// 设置 appleUserIdentifier 状态
emit(state.copyWith(appleUserIdentifier: data['appleUid']!));
// 通知用户需要授权微信认证
emit(state.copyWith(showNeedWechatForApple: true));
}
}
Future<void> wechatAuthForApple() async {
emit(state.copyWith(showNeedWechatForApple: false));
wechatAuth();
}
Future<void> wechatAuth() async {
if (!state.agreed) {
emit(state.copyWith(showAgreed: true, loginType: 1));
return; return;
} }
...@@ -82,8 +157,7 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -82,8 +157,7 @@ class LoginMainCubit extends Cubit<LoginMainState> {
); );
if (!authResult) { if (!authResult) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '微信授权处理失败')); Fluttertoast.showToast(msg: '微信授权处理失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
...@@ -110,94 +184,99 @@ class LoginMainCubit extends Cubit<LoginMainState> { ...@@ -110,94 +184,99 @@ class LoginMainCubit extends Cubit<LoginMainState> {
// 请求接口异常 // 请求接口异常
if (resultData == null) { if (resultData == null) {
emit(state.copyWith(loading: false, showSnackBar: true, snackBarMsg: '登录请求处理失败')); Fluttertoast.showToast(msg: '登录请求处理失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
// 状态码错误 // 状态码错误
if (resultData['resultCode'] != '001') { if (resultData['resultCode'] != '001') {
emit(state.copyWith(loading: false, showSnackBar: true, snackBarMsg: '登录请求状态失败')); Fluttertoast.showToast(msg: '登录请求状态失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
var data = resultData['data'] as Map<String, dynamic>; var data = resultData['data'] as Map<String, dynamic>;
var roles = data['roles']; _handleLoginSuccess(data);
// 过滤出家长角色的数据 }
roles.removeWhere((element) => element['userType'] != 2); }
var sessionCode = data['sessionCode']; void _handleLoginSuccess(Map<String, dynamic> data) {
var userCode = data['userCode']; var roles = data['roles'];
var classCode = ''; // 过滤出家长角色的数据
var userType = 0; roles.removeWhere((element) => element['userType'] != 2);
var stuId = '';
var sessionCode = data['sessionCode'];
var sharedPreferences = getIt.get<SharedPreferences>(); var userCode = data['userCode'];
var classCode = '';
if (roles.isNotEmpty) { var userType = 0;
var role = roles[0]; var stuId = '';
classCode = role['classCode'];
userType = role['userType']; var sharedPreferences = getIt.get<SharedPreferences>();
stuId = role['stuId'];
if (roles.isNotEmpty) {
// 将角色中的班级数据处理后,进行缓存 var role = roles[0];
List<String> classIdList = []; classCode = role['classCode'];
for (var role in roles) { userType = role['userType'];
classIdList.add(role['classCode'] as String); stuId = role['stuId'];
}
debugPrint('classCodeIds:-------------- $classIdList'); List<String> classIdList = [];
sharedPreferences.setStringList(Constant.classIdSetKey, classIdList); for (var role in roles) {
} else { classIdList.add(role['classCode'] as String);
sharedPreferences.setStringList(Constant.classIdSetKey, []);
} }
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') ?? '';
var preUserCode = sharedPreferences.getString('pre_userCode') ?? ''; if (preClassCode != '' &&
if (userCode != preUserCode) { 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_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode); sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType); sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId); 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_sessionCode', sessionCode); // 针对 Apple 登录
sharedPreferences.setString('auth_userCode', userCode); if (state.loginType == 2 && state.appleUserIdentifier.isNotEmpty) {
sharedPreferences.setString('auth_classCode', classCode); // appleUserIdentifier未绑定,则进行绑定
sharedPreferences.setInt('auth_userType', userType); _userAuthRepository.newBinding(state.appleUserIdentifier, userCode);
sharedPreferences.setString('auth_stuId', stuId);
router.go(
'/web',
extra: {
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
} }
router.go(
'/web',
extra: {
'sessionCode': sessionCode,
'userCode': userCode,
'classCode': classCode,
'userType': userType,
'stuId': stuId,
},
);
} }
@override @override
......
...@@ -7,22 +7,19 @@ import 'package:appframe/data/repositories/phone_auth_repository.dart'; ...@@ -7,22 +7,19 @@ import 'package:appframe/data/repositories/phone_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 LoginPhoneState extends Equatable { class LoginPhoneState extends Equatable {
final bool agreed; final bool agreed;
final bool showAgreed; final bool showAgreed;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend; final bool allowSend;
final int seconds; final int seconds;
const LoginPhoneState({ const LoginPhoneState({
this.agreed = false, this.agreed = false,
this.showAgreed = false, this.showAgreed = false,
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true, this.allowSend = true,
this.seconds = 0, this.seconds = 0,
}); });
...@@ -30,16 +27,12 @@ class LoginPhoneState extends Equatable { ...@@ -30,16 +27,12 @@ class LoginPhoneState extends Equatable {
LoginPhoneState copyWith({ LoginPhoneState copyWith({
bool? agreed, bool? agreed,
bool? showAgreed, bool? showAgreed,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend, bool? allowSend,
int? seconds, int? seconds,
}) { }) {
return LoginPhoneState( return LoginPhoneState(
agreed: agreed ?? this.agreed, agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed, showAgreed: showAgreed ?? this.showAgreed,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend, allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds, seconds: seconds ?? this.seconds,
); );
...@@ -49,8 +42,6 @@ class LoginPhoneState extends Equatable { ...@@ -49,8 +42,6 @@ class LoginPhoneState extends Equatable {
List<Object?> get props => [ List<Object?> get props => [
agreed, agreed,
showAgreed, showAgreed,
showSnackBar,
snackBarMsg,
allowSend, allowSend,
seconds, seconds,
]; ];
...@@ -100,16 +91,15 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> { ...@@ -100,16 +91,15 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
// 验证手机号码 // 验证手机号码
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: '请输入正确的手机号码', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
// 发送验证码 // 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone, 0); var result = await _phoneAuthRepository.verifyCode(phone, 0);
if (result['code'] != 0) { if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error'])); Fluttertoast.showToast(msg: result['error'], backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
...@@ -125,14 +115,12 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> { ...@@ -125,14 +115,12 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
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: '请输入正确的手机号码', backgroundColor: Colors.red);
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: '请输入正确的验证码', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
...@@ -143,17 +131,19 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> { ...@@ -143,17 +131,19 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
var resultData = await _phoneAuthRepository.login(phone, verifyCode); var resultData = await _phoneAuthRepository.login(phone, verifyCode);
if (resultData == null) { if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求失败')); Fluttertoast.showToast(msg: '登录请求失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
if (resultData['code'] != 0) { if (resultData['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: resultData['error'])); Fluttertoast.showToast(msg: resultData['error'], backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
var data = resultData['data'] as Map<String, dynamic>; var data = resultData['data'] as Map<String, dynamic>;
_handleLoginSuccess(data);
}
void _handleLoginSuccess(Map<String, dynamic> data) {
var roles = data['roles']; var roles = data['roles'];
// 过滤出家长角色的数据 // 过滤出家长角色的数据
roles.removeWhere((element) => element['userType'] != 2); roles.removeWhere((element) => element['userType'] != 2);
......
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:appframe/config/constant.dart'; import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart'; import 'package:appframe/config/locator.dart';
...@@ -8,7 +7,9 @@ import 'package:appframe/data/repositories/wechat_auth_repository.dart'; ...@@ -8,7 +7,9 @@ import 'package:appframe/data/repositories/wechat_auth_repository.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';
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:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
...@@ -16,30 +17,22 @@ class LoginQrState extends Equatable { ...@@ -16,30 +17,22 @@ class LoginQrState extends Equatable {
final int status; final int status;
final Uint8List? image; final Uint8List? image;
final String tip; final String tip;
final bool showSnackBar;
final String snackBarMsg;
const LoginQrState({ const LoginQrState({
this.status = 0, this.status = 0,
this.image, this.image,
this.tip = '', this.tip = '',
this.showSnackBar = false,
this.snackBarMsg = '',
}); });
LoginQrState copyWith({ LoginQrState copyWith({
int? status, int? status,
Uint8List? image, Uint8List? image,
String? tip, String? tip,
bool? showSnackBar,
String? snackBarMsg,
}) { }) {
return LoginQrState( return LoginQrState(
status: status ?? this.status, status: status ?? this.status,
image: image ?? this.image, image: image ?? this.image,
tip: tip ?? this.tip, tip: tip ?? this.tip,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
); );
} }
...@@ -48,8 +41,6 @@ class LoginQrState extends Equatable { ...@@ -48,8 +41,6 @@ class LoginQrState extends Equatable {
status, status,
image, image,
tip, tip,
showSnackBar,
snackBarMsg,
]; ];
} }
...@@ -71,15 +62,13 @@ class LoginQrCubit extends Cubit<LoginQrState> { ...@@ -71,15 +62,13 @@ class LoginQrCubit extends Cubit<LoginQrState> {
var resultData = await _wechatAuthRepository.getTicket() as Map<String, dynamic>?; var resultData = await _wechatAuthRepository.getTicket() as Map<String, dynamic>?;
// 请求接口异常 // 请求接口异常
if (resultData == null) { if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '生成二维码失败')); Fluttertoast.showToast(msg: '生成二维码失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
// 状态码错误 // 状态码错误
if (resultData['resultCode'] != '001') { if (resultData['resultCode'] != '001') {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '生成二维码状态错误')); Fluttertoast.showToast(msg: '生成二维码状态错误', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
...@@ -100,8 +89,7 @@ class LoginQrCubit extends Cubit<LoginQrState> { ...@@ -100,8 +89,7 @@ class LoginQrCubit extends Cubit<LoginQrState> {
); );
if (!authResult) { if (!authResult) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请求微信失败')); Fluttertoast.showToast(msg: '请求微信失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
} }
...@@ -151,15 +139,13 @@ class LoginQrCubit extends Cubit<LoginQrState> { ...@@ -151,15 +139,13 @@ class LoginQrCubit extends Cubit<LoginQrState> {
// 请求接口异常 // 请求接口异常
if (resultData == null) { if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求处理失败')); Fluttertoast.showToast(msg: '登录请求处理失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
// 状态码错误 // 状态码错误
if (resultData['resultCode'] != '001') { if (resultData['resultCode'] != '001') {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求状态失败')); Fluttertoast.showToast(msg: '登录请求状态失败', backgroundColor: Colors.red);
emit(state.copyWith(showSnackBar: false));
return; return;
} }
......
...@@ -5,7 +5,7 @@ import 'package:appframe/config/constant.dart'; ...@@ -5,7 +5,7 @@ import 'package:appframe/config/constant.dart';
import 'package:appframe/config/env_config.dart'; import 'package:appframe/config/env_config.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/user_auth_repository.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:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
...@@ -68,7 +68,7 @@ class AccountUserCubit extends Cubit<AccountUserState> { ...@@ -68,7 +68,7 @@ class AccountUserCubit extends Cubit<AccountUserState> {
late TextEditingController _nameController; late TextEditingController _nameController;
late TextEditingController _nickNameController; late TextEditingController _nickNameController;
late final PhoneAuthRepository _phoneAuthRepository; late final UserAuthRepository _userAuthRepository;
TextEditingController get nameController => _nameController; TextEditingController get nameController => _nameController;
...@@ -78,7 +78,7 @@ class AccountUserCubit extends Cubit<AccountUserState> { ...@@ -78,7 +78,7 @@ class AccountUserCubit extends Cubit<AccountUserState> {
_nameController = TextEditingController(text: state.name); _nameController = TextEditingController(text: state.name);
_nickNameController = TextEditingController(text: state.nickname); _nickNameController = TextEditingController(text: state.nickname);
_phoneAuthRepository = getIt.get<PhoneAuthRepository>(); _userAuthRepository = getIt.get<UserAuthRepository>();
} }
void updateAvatar(String avatarPath) { void updateAvatar(String avatarPath) {
...@@ -110,7 +110,7 @@ class AccountUserCubit extends Cubit<AccountUserState> { ...@@ -110,7 +110,7 @@ class AccountUserCubit extends Cubit<AccountUserState> {
var sharedPreferences = getIt.get<SharedPreferences>(); var sharedPreferences = getIt.get<SharedPreferences>();
var userCode = sharedPreferences.getString('auth_userCode') ?? ''; var userCode = sharedPreferences.getString('auth_userCode') ?? '';
var result = await _phoneAuthRepository.updateUser(userCode, name, nickname, imgPath); var result = await _userAuthRepository.updateUser(userCode, name, nickname, imgPath);
emit(state.copyWith(isLoading: false)); emit(state.copyWith(isLoading: false));
......
...@@ -17,6 +17,7 @@ import 'package:dio/dio.dart'; ...@@ -17,6 +17,7 @@ import 'package:dio/dio.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:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
...@@ -66,10 +67,6 @@ class WebState extends Equatable { ...@@ -66,10 +67,6 @@ class WebState extends Equatable {
final bool chooseVideoCmdFlag; final bool chooseVideoCmdFlag;
final String chooseVideoCmdMessage; final String chooseVideoCmdMessage;
/// 提示信息
final bool showSnackBar;
final String snackBarMsg;
final String h5Version; final String h5Version;
/// 用于测试监测问题 /// 用于测试监测问题
...@@ -99,8 +96,6 @@ class WebState extends Equatable { ...@@ -99,8 +96,6 @@ class WebState extends Equatable {
this.chooseImageCmdMessage = '', this.chooseImageCmdMessage = '',
this.chooseVideoCmdFlag = false, this.chooseVideoCmdFlag = false,
this.chooseVideoCmdMessage = '', this.chooseVideoCmdMessage = '',
this.showSnackBar = false,
this.snackBarMsg = '',
this.h5Version = '', this.h5Version = '',
this.testMsg = '', this.testMsg = '',
}); });
...@@ -130,8 +125,6 @@ class WebState extends Equatable { ...@@ -130,8 +125,6 @@ class WebState extends Equatable {
String? chooseImageCmdMessage, String? chooseImageCmdMessage,
bool? chooseVideoCmdFlag, bool? chooseVideoCmdFlag,
String? chooseVideoCmdMessage, String? chooseVideoCmdMessage,
bool? showSnackBar,
String? snackBarMsg,
String? h5Version, String? h5Version,
String? testMsg, String? testMsg,
}) { }) {
...@@ -159,8 +152,6 @@ class WebState extends Equatable { ...@@ -159,8 +152,6 @@ class WebState extends Equatable {
chooseImageCmdMessage: chooseImageCmdMessage ?? this.chooseImageCmdMessage, chooseImageCmdMessage: chooseImageCmdMessage ?? this.chooseImageCmdMessage,
chooseVideoCmdFlag: chooseVideoCmdFlag ?? this.chooseVideoCmdFlag, chooseVideoCmdFlag: chooseVideoCmdFlag ?? this.chooseVideoCmdFlag,
chooseVideoCmdMessage: chooseVideoCmdMessage ?? this.chooseVideoCmdMessage, chooseVideoCmdMessage: chooseVideoCmdMessage ?? this.chooseVideoCmdMessage,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
h5Version: h5Version ?? this.h5Version, h5Version: h5Version ?? this.h5Version,
testMsg: testMsg ?? this.testMsg, testMsg: testMsg ?? this.testMsg,
); );
...@@ -190,8 +181,6 @@ class WebState extends Equatable { ...@@ -190,8 +181,6 @@ class WebState extends Equatable {
chooseImageCmdMessage, chooseImageCmdMessage,
chooseVideoCmdFlag, chooseVideoCmdFlag,
chooseVideoCmdMessage, chooseVideoCmdMessage,
showSnackBar,
snackBarMsg,
h5Version, h5Version,
testMsg, testMsg,
]; ];
...@@ -1128,12 +1117,22 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver { ...@@ -1128,12 +1117,22 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
var vol = await volumeController.getVolume(); var vol = await volumeController.getVolume();
debugPrint('检测音量: $vol'); debugPrint('检测音量: $vol');
if (vol == 0) { if (vol == 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备处于静音状态,请调整音量')); // emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备处于静音状态,请调整音量'));
emit(state.copyWith(showSnackBar: false, snackBarMsg: '')); // emit(state.copyWith(showSnackBar: false, snackBarMsg: ''));
Fluttertoast.showToast(
msg: '设备处于静音状态,请调整音量',
backgroundColor: Colors.red,
gravity: ToastGravity.TOP,
);
_isFirstTimeOfCheck = false; _isFirstTimeOfCheck = false;
} else if (vol <= 0.15) { } else if (vol <= 0.15) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备音量过低,建议调高音量')); // emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备音量过低,建议调高音量'));
emit(state.copyWith(showSnackBar: false, snackBarMsg: '')); // emit(state.copyWith(showSnackBar: false, snackBarMsg: ''));
Fluttertoast.showToast(
msg: '设备音量过低,建议调高音量',
backgroundColor: Colors.red,
gravity: ToastGravity.TOP,
);
_isFirstTimeOfCheck = false; _isFirstTimeOfCheck = false;
} }
} }
......
...@@ -34,6 +34,7 @@ import 'package:appframe/data/repositories/message/video_info_handler.dart'; ...@@ -34,6 +34,7 @@ import 'package:appframe/data/repositories/message/video_info_handler.dart';
import 'package:appframe/data/repositories/message/wifi_info_handler.dart'; import 'package:appframe/data/repositories/message/wifi_info_handler.dart';
import 'package:appframe/data/repositories/message/window_info_handler.dart'; import 'package:appframe/data/repositories/message/window_info_handler.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:appframe/data/repositories/wechat_auth_repository.dart'; import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/api_service.dart'; import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart'; import 'package:appframe/services/dispatcher.dart';
...@@ -219,4 +220,5 @@ Future<void> setupLocator() async { ...@@ -219,4 +220,5 @@ Future<void> setupLocator() async {
/// repository /// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository()); getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
getIt.registerLazySingleton<PhoneAuthRepository>(() => PhoneAuthRepository()); getIt.registerLazySingleton<PhoneAuthRepository>(() => PhoneAuthRepository());
getIt.registerLazySingleton<UserAuthRepository>(() => UserAuthRepository());
} }
...@@ -112,23 +112,4 @@ class PhoneAuthRepository { ...@@ -112,23 +112,4 @@ class PhoneAuthRepository {
); );
return resp.data; return resp.data;
} }
///
/// {
/// "code": 0,
/// "error": "操作成功"
/// }
///
Future<dynamic> updateUser(String userid, String name, String nickName, String avatar) async {
Response resp = await _appService.post(
'/api/v1/comm/user/update',
{
"userid": userid,
"name": name,
"nickName": nickName,
"avatar": avatar,
},
);
return resp.data;
}
} }
import 'dart:io';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart';
class UserAuthRepository {
late final ApiService _appService;
UserAuthRepository() {
_appService = getIt<ApiService>(instanceName: 'appApiService');
}
///
/// {
/// "code": 0,
/// "error": "操作成功"
/// }
///
Future<dynamic> updateUser(String userid, String name, String nickName, String avatar) async {
Response resp = await _appService.post(
'/api/v1/comm/user/update',
{
"userid": userid,
"name": name,
"nickName": nickName,
"avatar": avatar,
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
Future<dynamic> appleLogin(String userid, String authorizationCode, String identityToken) async {
Response resp = await _appService.post(
'/api/v1/comm/user/applelogin',
{
"user": userid,
"authorizationCode": authorizationCode,
"identityToken": identityToken,
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
///
/// {
/// "code": 1,
/// "data":"bxe userid" // 存在会返回
/// "error": "",
/// }
Future<dynamic> exchangeId(String userid) async {
Response resp = await _appService.post(
'/api/v1/comm/user/exchangeid',
{
"userId": userid,
"type": "apple",
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
///
/// {
/// "error": "",
/// "code": 0,
/// }
Future<dynamic> newBinding(String userid, String bxeUserId) async {
Response resp = await _appService.post(
'/api/v1/comm/user/newbinding',
{
"userId": userid,
"bxeUserId": userid,
"type": "apple",
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
}
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';
...@@ -21,7 +23,7 @@ class WechatAuthRepository { ...@@ -21,7 +23,7 @@ class WechatAuthRepository {
debugPrint('登录结果: $resp'); debugPrint('登录结果: $resp');
return resp.statusCode == 200 ? resp.data : null; return resp.statusCode == HttpStatus.ok ? resp.data : null;
} }
Future<dynamic> getTicket() async { Future<dynamic> getTicket() async {
...@@ -35,6 +37,6 @@ class WechatAuthRepository { ...@@ -35,6 +37,6 @@ class WechatAuthRepository {
debugPrint('获取ticket: $resp'); debugPrint('获取ticket: $resp');
return resp.statusCode == 200 ? resp.data : null; return resp.statusCode == HttpStatus.ok ? resp.data : null;
} }
} }
import 'dart:io';
import 'package:appframe/bloc/login_main_cubit.dart'; import 'package:appframe/bloc/login_main_cubit.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/ui/widgets/login/login_page_agreed_widget.dart'; import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart';
import 'package:appframe/ui/widgets/tip_overlay_widget.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.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:fluwx/fluwx.dart'; import 'package:fluwx/fluwx.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class LoginMainPage extends StatelessWidget { class LoginMainPage extends StatelessWidget {
const LoginMainPage({super.key}); const LoginMainPage({super.key});
...@@ -18,100 +20,121 @@ class LoginMainPage extends StatelessWidget { ...@@ -18,100 +20,121 @@ class LoginMainPage extends StatelessWidget {
child: BlocConsumer<LoginMainCubit, LoginMainState>( child: BlocConsumer<LoginMainCubit, LoginMainState>(
builder: (context, state) { builder: (context, state) {
var loginMainCubit = context.read<LoginMainCubit>(); var loginMainCubit = context.read<LoginMainCubit>();
return Stack( var appleLoginBtn = Platform.isIOS
children: [ ? [
Scaffold( Padding(
backgroundColor: Colors.white, padding: EdgeInsets.symmetric(horizontal: 42.5),
body: SafeArea( child: SizedBox(
top: false, width: double.infinity,
child: SingleChildScrollView( height: 50,
child: Column( child: SignInWithAppleButton(
children: [ text: '通过Apple登录',
SizedBox(height: 100), borderRadius: BorderRadius.circular(27),
Center( onPressed: () {
child: Image.asset( loginMainCubit.appleAuth();
'assets/images/login_v2/banner_1.png', },
),
),
),
SizedBox(height: 15),
]
: [];
var scaffold = Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 100),
Center(
child: Image.asset(
'assets/images/login_v2/banner_1.png',
),
),
SizedBox(height: 30),
Center(
child: Image.asset(
'assets/images/login_v2/main.png',
),
),
SizedBox(height: 40),
...appleLoginBtn,
Padding(
padding: EdgeInsets.symmetric(horizontal: 42.5),
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () async {
if (await getIt.get<Fluwx>().isWeChatInstalled) {
loginMainCubit.wechatAuth();
} else {
if (!context.mounted) return;
_showWechatNotInstallDialog(context);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF26C445),
foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(27),
),
), ),
), child: Row(
SizedBox(height: 30), mainAxisAlignment: MainAxisAlignment.center,
Center( children: [
child: Image.asset( Image.asset('assets/images/login_v2/wechat_white_icon.png'),
'assets/images/login_v2/main.png', SizedBox(width: 4.5),
Text('微信登录'),
],
), ),
), ),
SizedBox(height: 40), ),
Padding( ),
padding: EdgeInsets.symmetric(horizontal: 42.5), SizedBox(height: 15),
child: SizedBox( _buildAgreement(context, loginMainCubit, state.agreed),
width: double.infinity, SizedBox(height: 82.5),
height: 50, Text(
child: ElevatedButton( '其他方式登录',
onPressed: () async { style: TextStyle(
if (await getIt.get<Fluwx>().isWeChatInstalled) { fontSize: 14,
loginMainCubit.wechatAuth(); color: Color(0xFF999999),
} else { ),
if (!context.mounted) return; strutStyle: StrutStyle(height: 16 / 14),
_showWechatNotInstallDialog(context); ),
} SizedBox(height: 15),
}, Row(
style: ElevatedButton.styleFrom( mainAxisAlignment: MainAxisAlignment.center,
backgroundColor: Color(0xFF26C445), children: [
foregroundColor: Colors.white, InkWell(
textStyle: TextStyle(fontSize: 19), onTap: () {
shape: RoundedRectangleBorder( context.read<LoginMainCubit>().goLoginQr();
borderRadius: BorderRadius.circular(27), },
), child: Image.asset(
), 'assets/images/login_v2/qr_green_icon.png',
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/login_v2/wechat_white_icon.png'),
SizedBox(width: 4.5),
Text('微信登录'),
],
),
),
), ),
), ),
SizedBox(height: 15), SizedBox(width: 25),
_buildAgreement(context, loginMainCubit, state.agreed), InkWell(
SizedBox(height: 82.5), onTap: () {
Text( context.read<LoginMainCubit>().goLoginPhone();
'其他方式登录', },
style: TextStyle( child: Image.asset(
fontSize: 14, 'assets/images/login_v2/phone_blue_icon.png',
color: Color(0xFF999999),
), ),
strutStyle: StrutStyle(height: 16 / 14),
),
SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
context.read<LoginMainCubit>().goLoginQr();
},
child: Image.asset(
'assets/images/login_v2/qr_green_icon.png',
),
),
SizedBox(width: 25),
InkWell(
onTap: () {
context.read<LoginMainCubit>().goLoginPhone();
},
child: Image.asset(
'assets/images/login_v2/phone_blue_icon.png',
),
),
],
), ),
], ],
), ),
), ],
), ),
), ),
),
);
return Stack(
children: [
scaffold,
state.loading state.loading
? Container( ? Container(
color: Colors.black54, color: Colors.black54,
...@@ -134,8 +157,8 @@ class LoginMainPage extends StatelessWidget { ...@@ -134,8 +157,8 @@ class LoginMainPage extends StatelessWidget {
listener: (context, state) { listener: (context, state) {
if (state.showAgreed) { if (state.showAgreed) {
_showAgreementDialog(context, context.read<LoginMainCubit>()); _showAgreementDialog(context, context.read<LoginMainCubit>());
} else if (state.showSnackBar) { } else if (state.showNeedWechatForApple) {
TipOverlayUtil.showTip(context, state.snackBarMsg); _showNeedWechatDialogForApple(context, context.read<LoginMainCubit>());
} }
}, },
), ),
...@@ -352,4 +375,58 @@ class LoginMainPage extends StatelessWidget { ...@@ -352,4 +375,58 @@ class LoginMainPage extends StatelessWidget {
}, },
); );
} }
void _showNeedWechatDialogForApple(BuildContext context, LoginMainCubit loginMainCubit) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext ctx) {
return PopScope(
canPop: false,
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: Text(
'温馨提示',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
// fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
content: Text.rich(
TextSpan(
text: '为了避免您之前在微信小程序的使用数据不丢失,必须绑定微信才可以继续!',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
),
actions: [
Center(
child: TextButton(
onPressed: () {
Navigator.of(ctx).pop('OK');
loginMainCubit.wechatAuthForApple();
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF7691FA),
textStyle: TextStyle(fontSize: 17),
minimumSize: Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('绑定微信'),
),
),
],
),
);
},
);
}
} }
import 'package:appframe/bloc/login_phone_cubit.dart'; import 'package:appframe/bloc/login_phone_cubit.dart';
import 'package:appframe/config/routes.dart'; import 'package:appframe/config/routes.dart';
import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart'; import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart';
import 'package:appframe/ui/widgets/tip_overlay_widget.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
...@@ -81,8 +80,6 @@ class LoginPhonePage extends StatelessWidget { ...@@ -81,8 +80,6 @@ class LoginPhonePage extends StatelessWidget {
listener: (context, state) { listener: (context, state) {
if (state.showAgreed) { if (state.showAgreed) {
_showAgreementDialog(context, context.read<LoginPhoneCubit>()); _showAgreementDialog(context, context.read<LoginPhoneCubit>());
} else if (state.showSnackBar) {
TipOverlayUtil.showTip(context, state.snackBarMsg);
} }
}, },
), ),
......
import 'package:appframe/bloc/login_qr_cubit.dart'; import 'package:appframe/bloc/login_qr_cubit.dart';
import 'package:appframe/ui/widgets/tip_overlay_widget.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';
...@@ -61,11 +60,7 @@ class LoginQrPage extends StatelessWidget { ...@@ -61,11 +60,7 @@ class LoginQrPage extends StatelessWidget {
), ),
); );
}, },
listener: (context, state) { listener: (context, state) {},
if (state.showSnackBar) {
TipOverlayUtil.showTip(context, state.snackBarMsg);
}
},
)); ));
} }
......
...@@ -151,8 +151,6 @@ class WebPage extends StatelessWidget { ...@@ -151,8 +151,6 @@ class WebPage extends StatelessWidget {
context.read<WebCubit>().chooseImage(context); context.read<WebCubit>().chooseImage(context);
} else if (state.chooseVideoCmdFlag) { } else if (state.chooseVideoCmdFlag) {
context.read<WebCubit>().chooseVideo(context); context.read<WebCubit>().chooseVideo(context);
} else if (state.showSnackBar) {
TipOverlayUtil.showTip(context, state.snackBarMsg);
} }
}, },
), ),
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!