Files
keyboard/keyBoard/Class/Login/VM/KBLoginVM.m
2025-12-04 15:28:11 +08:00

181 lines
8.5 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBLoginVM.m
//
#import "KBLoginVM.h"
#import "AppleSignInManager.h"
#import "KBNetworkManager.h"
#import "KBAuthManager.h"
#import "KBAPI.h"
#import "KBUser.h"
@interface KBLoginVM ()
@property (atomic, strong, readwrite, nullable) KBUser *currentUser;
@end
@implementation KBLoginVM
+ (instancetype)shared {
static KBLoginVM *vm; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ vm = [KBLoginVM new]; });
return vm;
}
- (BOOL)isLoggedIn {
return [[KBAuthManager shared] isLoggedIn];
}
- (void)signInWithAppleFromViewController:(UIViewController *)presenter
completion:(KBLoginCompletion)completion {
// 调起 Apple 登录
[[AppleSignInManager shared] signInFromViewController:presenter completion:^(ASAuthorizationAppleIDCredential * _Nullable credential, NSError * _Nullable error) {
if (error) { if (completion) completion(NO, error); return; }
if (![credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-1 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Invalid login credential")}]);
return;
}
ASAuthorizationAppleIDCredential *cred = (ASAuthorizationAppleIDCredential *)credential;
// identityToken/authorizationCode 均可按后端要求传递;本项目后端使用 identityToken 映射为 code
NSString *identityToken = cred.identityToken ? [[NSString alloc] initWithData:cred.identityToken encoding:NSUTF8StringEncoding] : nil;
NSString *authorizationCode = cred.authorizationCode ? [[NSString alloc] initWithData:cred.authorizationCode encoding:NSUTF8StringEncoding] : nil;
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (identityToken.length) params[@"identityToken"] = identityToken;
if (authorizationCode.length) params[@"accessCode"] = authorizationCode; // 仅供后端需要时使用
if (cred.user.length) params[@"userID"] = cred.user; // 可选
// 如有本地缓存的性别(首次进入首页时选择),一并上传给后端
NSNumber *genderNumber = [self kb_localGenderParamIfAvailable];
if (genderNumber != nil) {
params[@"gender"] = genderNumber;
}
[KBHUD show];
// 向服务端发起校验
[[KBNetworkManager shared] POST:API_APPLE_LOGIN jsonBody:params headers:nil completion:^(NSDictionary * _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[KBHUD dismiss];
if (error) { if (completion) completion(NO, error); return; }
// 从返回 JSON 中提取 token支持常见命名与层级 data/user
// 先解析用户模型(使用 MJExtension
NSDictionary *dict = jsonOrData[@"data"];
KBUser *user = [KBUser mj_objectWithKeyValues:dict];
self.currentUser = user;
if (user.token.length == 0) {
if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-2 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"No token returned")}]);
return;
}
[[KBUserSessionManager shared] handleLoginSuccessWithUser:user];
completion(true,nil);
// 保存登录态到共享钥匙串;供 App 与扩展共享
// BOOL ok = [[KBAuthManager shared] saveAccessToken:user.token
// refreshToken:nil
// expiryDate:nil
// userIdentifier:cred.user];
// if (completion) completion(ok, ok ? nil : [NSError errorWithDomain:@"KBLogin" code:-3 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"Failed to save login state")}]);
}];
}];
}
/// 邮箱登录
- (void)emailLoginEmail:(NSString *)email password:(NSString *)password WithCompletion:(KBLoginCompletion)completion;
{
[KBHUD show];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
if (email.length) params[@"email"] = email;
if (password.length) params[@"password"] = password;
// 如有本地缓存的性别(首次进入首页时选择),一并上传给后端
NSNumber *genderNumber = [self kb_localGenderParamIfAvailable];
if (genderNumber != nil) {
params[@"gender"] = genderNumber;
}
// 向服务端发起校验
[[KBNetworkManager shared] POST:API_EMAIL_LOGIN jsonBody:params headers:nil completion:^(NSDictionary * _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[KBHUD dismiss];
if (error) { if (completion) completion(NO, error); return; }
NSDictionary *dict = jsonOrData[@"data"];
KBUser *user = [KBUser mj_objectWithKeyValues:dict];
self.currentUser = user;
if (user.token.length == 0) {
if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-2 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"No token returned")}]);
return;
}
[[KBUserSessionManager shared] handleLoginSuccessWithUser:user];
completion(true,nil);
}];
}
/// 邮箱注册
- (void)emailRegisterParams:(NSDictionary *)params withCompletion:(KBRegisterCompletion)completion{
[KBHUD show];
// NSMutableDictionary *params = [NSMutableDictionary dictionary];
// if (mailAddress.length) params[@"mailAddress"] = mailAddress;
// if (password.length) params[@"password"] = password;
// if (passwordConfirm.length) params[@"passwordConfirm"] = passwordConfirm;
//
// // 如有本地缓存的性别(首次进入首页时选择),一并上传给后端
// NSNumber *genderNumber = [self kb_localGenderParamIfAvailable];
// if (genderNumber != nil) {
// params[@"gender"] = genderNumber;
// }
// 向服务端发起校验
[[KBNetworkManager shared] POST:API_EMAIL_REGISTER jsonBody:params headers:nil completion:^(NSDictionary * _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) {
[KBHUD dismiss];
if (error) { if (completion) completion(NO, error); return; }
NSDictionary *dict = jsonOrData[@"data"];
KBUser *user = [KBUser mj_objectWithKeyValues:dict];
self.currentUser = user;
if (user.token.length == 0) {
if (completion) completion(NO, [NSError errorWithDomain:@"KBLogin" code:-2 userInfo:@{NSLocalizedDescriptionKey: KBLocalized(@"No token returned")}]);
return;
}
[[KBUserSessionManager shared] handleLoginSuccessWithUser:user];
completion(true,nil);
}];
}
#pragma mark - Helpers
// 宽松解析token / access_token / accessToken支持顶层或 data/user 层
+ (NSString *)tokenFromResponseObject:(id)obj {
if (![obj isKindOfClass:[NSDictionary class]]) return nil;
NSDictionary *dict = (NSDictionary *)obj;
NSString *(^pick)(NSDictionary *) = ^NSString *(NSDictionary *d) {
NSArray *keys = @[ @"token", @"access_token", @"accessToken" ];
for (NSString *k in keys) {
id v = d[k]; if ([v isKindOfClass:NSString.class] && ((NSString *)v).length > 0) return v;
}
return nil;
};
NSString *t = pick(dict);
if (t.length) return t;
id data = dict[@"data"]; if ([data isKindOfClass:NSDictionary.class]) { t = pick(data); if (t.length) return t; }
id user = dict[@"user"]; if ([user isKindOfClass:NSDictionary.class]) { t = pick(user); if (t.length) return t; }
// 扩展:允许后端将 token 放在 data.session.token
NSDictionary *d2 = dict[@"data"]; if ([d2 isKindOfClass:NSDictionary.class]) {
NSDictionary *session = d2[@"session"]; if ([session isKindOfClass:NSDictionary.class]) { t = pick(session); if (t.length) return t; }
}
return nil;
}
/// 读取本地缓存的性别枚举值,用于登录接口上传到后端。
/// - 用户在首次进入首页的性别选择页或个人资料页中选择后,会持久化到 NSUserDefaults。
- (nullable NSNumber *)kb_localGenderParamIfAvailable {
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
// 只有在用户真正看过性别选择页后,才认为本地的性别有效
BOOL hasShownSexVC = [ud boolForKey:KBSexSelectShownKey];
if (!hasShownSexVC) {
return nil;
}
NSInteger value = [ud integerForKey:KBSexSelectedGenderKey];
if (value < UserSexMan || value > UserSexTwoSex) {
return nil;
}
return @(value);
}
@end