From e8841d917c1a9914e34e4314c8471d4f6d638901 Mon Sep 17 00:00:00 2001
From: lxm <1274844347@qq.com>
Date: Fri, 25 Jul 2025 10:44:49 +0800
Subject: [PATCH] =?UTF-8?q?feat(=E7=99=BB=E5=BD=95):=20=E6=B7=BB=E5=8A=A0A?=
=?UTF-8?q?pple=E7=99=BB=E5=BD=95=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 添加sign_in_with_apple依赖
- 配置iOS entitlements和Info.plist
- 实现Apple登录按钮及控制器逻辑
- 新增Apple登录处理类
- 根据地区显示不同登录选项
---
ios/Runner/Info.plist | 8 +
ios/Runner/Runner.entitlements | 4 +
lib/modules/login/controllers/appleLogin.dart | 158 ++++++++++++++++++
.../login/controllers/login_controller.dart | 32 ++++
lib/modules/login/views/login_view.dart | 18 +-
pubspec.yaml | 1 +
6 files changed, 217 insertions(+), 4 deletions(-)
create mode 100644 lib/modules/login/controllers/appleLogin.dart
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