diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 40323d9d6..ca4a8bf61 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -38,6 +38,14 @@ wx5bbb7be9324f3e2d + + CFBundleURLName + com.saitong.voitrans.signin + CFBundleURLSchemes + + com.saitong.voitrans + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements index 0286ef500..88b018abb 100644 --- a/ios/Runner/Runner.entitlements +++ b/ios/Runner/Runner.entitlements @@ -12,5 +12,9 @@ com.apple.external-accessory.wireless-configuration + com.apple.developer.applesignin + + Default + diff --git a/lib/modules/login/controllers/appleLogin.dart b/lib/modules/login/controllers/appleLogin.dart new file mode 100644 index 000000000..f77b86393 --- /dev/null +++ b/lib/modules/login/controllers/appleLogin.dart @@ -0,0 +1,158 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; +import 'package:crypto/crypto.dart'; +import 'package:get/get.dart'; + +class Apple { + String? token = ''; + String name = ''; + String email = ''; + String pictureurl = ''; + String id = ''; + + /// 生成随机nonce字符串 + String generateNonce([int length = 32]) { + const charset = + '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; + final random = Random.secure(); + return List.generate(length, (_) => charset[random.nextInt(charset.length)]) + .join(); + } + + /// 返回输入字符串的sha256哈希值(十六进制表示) + String sha256ofString(String input) { + final bytes = utf8.encode(input); + final digest = sha256.convert(bytes); + return digest.toString(); + } + + /// Apple登录主方法 + Future signInWithApple() async { + try { + // 检查Apple登录可用性 + if (!await SignInWithApple.isAvailable()) { + if (kDebugMode) { + print('Apple登录不可用'); + } + return false; + } + + UserCredential userCredential = await loginApple(); + token = await userCredential.user?.getIdToken(); + + if (kDebugMode) { + print('Apple登录响应数据token: $token'); + } + + return token != null && token!.isNotEmpty; + } catch (e) { + if (kDebugMode) { + print('Apple登录异常:$e'); + } + return false; + } + } + + /// 执行Apple登录流程 + Future loginApple() async { + // 生成nonce用于安全验证 + final rawNonce = generateNonce(); + final nonce = sha256ofString(rawNonce); + + try { + // 请求Apple登录凭证 + final appleCredential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + nonce: nonce, + ); + + // 提取用户信息 + _extractUserInfo(appleCredential); + + // 创建Firebase OAuth凭证 + final oauthCredential = OAuthProvider("apple.com").credential( + idToken: appleCredential.identityToken, + rawNonce: rawNonce, + ); + + if (kDebugMode) { + print('Apple OAuth凭证创建成功'); + print('用户信息: name=$name, email=$email, id=$id'); + } + + // 使用Firebase Auth进行登录 + return await FirebaseAuth.instance.signInWithCredential(oauthCredential); + } catch (e) { + if (kDebugMode) { + print('Apple登录流程异常:$e'); + } + throw Exception('Apple登录失败: $e'); + } + } + + /// 提取Apple返回的用户信息 + void _extractUserInfo(AuthorizationCredentialAppleID credential) { + // 用户ID + id = credential.userIdentifier ?? ''; + + // 邮箱(可能为空,如果用户选择隐藏邮箱) + email = credential.email ?? ''; + + // 用户姓名(仅首次登录时提供) + if (credential.givenName != null || credential.familyName != null) { + final givenName = credential.givenName ?? ''; + final familyName = credential.familyName ?? ''; + name = '$givenName $familyName'.trim(); + } + + // Apple不提供头像,使用默认值 + pictureurl = ''; + + if (kDebugMode) { + print('提取的用户信息:'); + print('ID: $id'); + print('Email: $email'); + print('Name: $name'); + print('Picture URL: $pictureurl'); + } + } + + /// 退出Apple登录 + Future signOutApple() async { + try { + await FirebaseAuth.instance.signOut(); + // 清空用户信息 + token = ''; + name = ''; + email = ''; + pictureurl = ''; + id = ''; + + if (kDebugMode) { + print('Apple登录退出成功'); + } + } catch (e) { + if (kDebugMode) { + print('Apple登录退出异常:$e'); + } + } + } + + /// 检查Apple登录环境 + static Future checkAppleSignInEnvironment() async { + if (kDebugMode) { + print('=== Apple登录环境检查 ==='); + print('平台: ${GetPlatform.isIOS ? "iOS" : "非iOS"}'); + print('Apple登录可用性: ${await SignInWithApple.isAvailable()}'); + print('Firebase Auth实例: ${FirebaseAuth.instance.currentUser?.uid ?? "未登录"}'); + print('========================'); + } + } +} diff --git a/lib/modules/login/controllers/login_controller.dart b/lib/modules/login/controllers/login_controller.dart index c77588c20..6852634ef 100644 --- a/lib/modules/login/controllers/login_controller.dart +++ b/lib/modules/login/controllers/login_controller.dart @@ -8,6 +8,7 @@ import 'dart:convert'; import 'facebookLogin.dart'; import 'googleLogin.dart'; import 'weixinLogin.dart'; +import 'appleLogin.dart'; import '../../../data/services/network/api.dart'; import '../../../../data/models/user_Info.dart'; import '../../common/webview_page.dart'; @@ -50,6 +51,7 @@ class LoginController extends GetxController { String _appVersion = ''; // APP版本号 String _countryCode = ''; // 国家代码 UserChannel _userChannel = UserChannel.OFFICIAL; // 默认渠道 + final isChinaMainland = false.obs; //是否中国大陆地区 final _hasAcceptedPrivacy = false.obs; bool get hasAcceptedPrivacy => _hasAcceptedPrivacy.value; @@ -202,6 +204,9 @@ class LoginController extends GetxController { super.onInit(); _viewState.value = LoginViewState.welcome; + isChinaMainland.value = + Get.deviceLocale?.countryCode?.toUpperCase() == 'CN'; + _hasAcceptedPrivacy.value = _storage.read('hasAcceptedPrivacy') ?? false; // 从本地存储中读取是否已接受隐私政策 @@ -874,6 +879,33 @@ class LoginController extends GetxController { } break; } + case 5: //Apple登录 + { + Logger().d("数据获取Apple"); + final apple = Apple(); + bool authResult = await apple.signInWithApple(); + + if (authResult) { + Logger().d("数据获取_Apple.token${apple.token}"); + Logger().d("数据获取_Apple.email${apple.email}"); + Logger().d("数据获取_Apple.name${apple.name}"); + Logger().d("数据获取_Apple.id${apple.id}"); + + // 验证成功,跳转到配对页面 + requestLogin({ + "mail": apple.email, + "phone": "", + "stype": loginType, + "vcode": "", + "ttoken": apple.token, + "phonemodel": "", + "phonemac": _deviceId, + "name": apple.name, + "avatar": apple.pictureurl, + }); + } + break; + } case 6: //游客登录 { requestLogin({ diff --git a/lib/modules/login/views/login_view.dart b/lib/modules/login/views/login_view.dart index 22fbc93b3..bd992b5a9 100644 --- a/lib/modules/login/views/login_view.dart +++ b/lib/modules/login/views/login_view.dart @@ -481,13 +481,18 @@ class LoginView extends GetView { SizedBox(width: 24.w), _buildSocialButton( FontAwesomeIcons.weixin, Colors.green, isDarkMode), - SizedBox(width: 24.w), - _buildSocialButton( - FontAwesomeIcons.facebook, Colors.blue, isDarkMode), + // Apple登录按钮(仅在iOS上显示) + if (GetPlatform.isIOS) ...[ + SizedBox(width: 24.w), + _buildSocialButton(FontAwesomeIcons.apple, Colors.black, isDarkMode), + ], + // SizedBox(width: 24.w), + // _buildSocialButton( + // FontAwesomeIcons.facebook, Colors.blue, isDarkMode), // 根据配置显示游客登录按钮 if (controller.isGuestLoginVisible) ...[ SizedBox(width: 24.w), - _buildSocialButton(FontAwesomeIcons.userSecret, Colors.black, + _buildSocialButton(FontAwesomeIcons.userSecret, Colors.grey, isDarkMode), // 游客登录按钮 ], ], @@ -541,6 +546,11 @@ class LoginView extends GetView { controller.loginType = 2; break; } + case FontAwesomeIcons.apple: // 处理Apple登录 + { + controller.loginType = 5; + break; + } case FontAwesomeIcons.userSecret: // 处理游客登录 { controller.loginType = 6; diff --git a/pubspec.yaml b/pubspec.yaml index 1abb24202..c601eebd1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,6 +65,7 @@ dependencies: firebase_auth: ^5.5.1 google_sign_in: ^6.2.2 flutter_facebook_auth: ^7.1.1 + sign_in_with_apple: ^6.1.2 flutter_image_compress: ^2.4.0 common_utils: ^2.1.0 image_gallery_saver_plus: ^4.0.1