Showing
11 changed files
with
603 additions
and
23 deletions
| @@ -128,6 +128,46 @@ data class AppleSignInModel ( | @@ -128,6 +128,46 @@ data class AppleSignInModel ( | ||
| 128 | } | 128 | } |
| 129 | 129 | ||
| 130 | /** Generated class from Pigeon that represents data sent in messages. */ | 130 | /** Generated class from Pigeon that represents data sent in messages. */ |
| 131 | +data class GoogleSignInModel ( | ||
| 132 | + val email: String? = null, | ||
| 133 | + val clientID: String? = null, | ||
| 134 | + val idToken: String, | ||
| 135 | + val nickname: String? = null, | ||
| 136 | + val avatarUrl: String? = null | ||
| 137 | +) | ||
| 138 | + { | ||
| 139 | + companion object { | ||
| 140 | + fun fromList(pigeonVar_list: List<Any?>): GoogleSignInModel { | ||
| 141 | + val email = pigeonVar_list[0] as String? | ||
| 142 | + val clientID = pigeonVar_list[1] as String? | ||
| 143 | + val idToken = pigeonVar_list[2] as String | ||
| 144 | + val nickname = pigeonVar_list[3] as String? | ||
| 145 | + val avatarUrl = pigeonVar_list[4] as String? | ||
| 146 | + return GoogleSignInModel(email, clientID, idToken, nickname, avatarUrl) | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + fun toList(): List<Any?> { | ||
| 150 | + return listOf( | ||
| 151 | + email, | ||
| 152 | + clientID, | ||
| 153 | + idToken, | ||
| 154 | + nickname, | ||
| 155 | + avatarUrl, | ||
| 156 | + ) | ||
| 157 | + } | ||
| 158 | + override fun equals(other: Any?): Boolean { | ||
| 159 | + if (other !is GoogleSignInModel) { | ||
| 160 | + return false | ||
| 161 | + } | ||
| 162 | + if (this === other) { | ||
| 163 | + return true | ||
| 164 | + } | ||
| 165 | + return PlatformApiPigeonUtils.deepEquals(toList(), other.toList()) } | ||
| 166 | + | ||
| 167 | + override fun hashCode(): Int = toList().hashCode() | ||
| 168 | +} | ||
| 169 | + | ||
| 170 | +/** Generated class from Pigeon that represents data sent in messages. */ | ||
| 131 | data class WatchAppOtherInfo ( | 171 | data class WatchAppOtherInfo ( |
| 132 | val userId: Long? = null, | 172 | val userId: Long? = null, |
| 133 | val nickname: String? = null, | 173 | val nickname: String? = null, |
| @@ -287,16 +327,21 @@ private open class PlatformApiPigeonCodec : StandardMessageCodec() { | @@ -287,16 +327,21 @@ private open class PlatformApiPigeonCodec : StandardMessageCodec() { | ||
| 287 | } | 327 | } |
| 288 | 131.toByte() -> { | 328 | 131.toByte() -> { |
| 289 | return (readValue(buffer) as? List<Any?>)?.let { | 329 | return (readValue(buffer) as? List<Any?>)?.let { |
| 290 | - WatchAppOtherInfo.fromList(it) | 330 | + GoogleSignInModel.fromList(it) |
| 291 | } | 331 | } |
| 292 | } | 332 | } |
| 293 | 132.toByte() -> { | 333 | 132.toByte() -> { |
| 294 | return (readValue(buffer) as? List<Any?>)?.let { | 334 | return (readValue(buffer) as? List<Any?>)?.let { |
| 295 | - AppleProductInfo.fromList(it) | 335 | + WatchAppOtherInfo.fromList(it) |
| 296 | } | 336 | } |
| 297 | } | 337 | } |
| 298 | 133.toByte() -> { | 338 | 133.toByte() -> { |
| 299 | return (readValue(buffer) as? List<Any?>)?.let { | 339 | return (readValue(buffer) as? List<Any?>)?.let { |
| 340 | + AppleProductInfo.fromList(it) | ||
| 341 | + } | ||
| 342 | + } | ||
| 343 | + 134.toByte() -> { | ||
| 344 | + return (readValue(buffer) as? List<Any?>)?.let { | ||
| 300 | AppleProductPaymentResult.fromList(it) | 345 | AppleProductPaymentResult.fromList(it) |
| 301 | } | 346 | } |
| 302 | } | 347 | } |
| @@ -313,18 +358,22 @@ private open class PlatformApiPigeonCodec : StandardMessageCodec() { | @@ -313,18 +358,22 @@ private open class PlatformApiPigeonCodec : StandardMessageCodec() { | ||
| 313 | stream.write(130) | 358 | stream.write(130) |
| 314 | writeValue(stream, value.toList()) | 359 | writeValue(stream, value.toList()) |
| 315 | } | 360 | } |
| 316 | - is WatchAppOtherInfo -> { | 361 | + is GoogleSignInModel -> { |
| 317 | stream.write(131) | 362 | stream.write(131) |
| 318 | writeValue(stream, value.toList()) | 363 | writeValue(stream, value.toList()) |
| 319 | } | 364 | } |
| 320 | - is AppleProductInfo -> { | 365 | + is WatchAppOtherInfo -> { |
| 321 | stream.write(132) | 366 | stream.write(132) |
| 322 | writeValue(stream, value.toList()) | 367 | writeValue(stream, value.toList()) |
| 323 | } | 368 | } |
| 324 | - is AppleProductPaymentResult -> { | 369 | + is AppleProductInfo -> { |
| 325 | stream.write(133) | 370 | stream.write(133) |
| 326 | writeValue(stream, value.toList()) | 371 | writeValue(stream, value.toList()) |
| 327 | } | 372 | } |
| 373 | + is AppleProductPaymentResult -> { | ||
| 374 | + stream.write(134) | ||
| 375 | + writeValue(stream, value.toList()) | ||
| 376 | + } | ||
| 328 | else -> super.writeValue(stream, value) | 377 | else -> super.writeValue(stream, value) |
| 329 | } | 378 | } |
| 330 | } | 379 | } |
| @@ -371,6 +420,10 @@ interface PlatformHostApi { | @@ -371,6 +420,10 @@ interface PlatformHostApi { | ||
| 371 | fun requestUnhandedUrl(): String? | 420 | fun requestUnhandedUrl(): String? |
| 372 | /** 请求苹果登录 */ | 421 | /** 请求苹果登录 */ |
| 373 | fun requestAppleSignIn(callback: (Result<AppleSignInModel?>) -> Unit) | 422 | fun requestAppleSignIn(callback: (Result<AppleSignInModel?>) -> Unit) |
| 423 | + /** google登录 */ | ||
| 424 | + fun requestGoogleSignIn(callback: (Result<GoogleSignInModel?>) -> Unit) | ||
| 425 | + /** 发送邮件 */ | ||
| 426 | + fun sendEmail(mailTo: String, title: String, content: String, callback: (Result<Boolean>) -> Unit) | ||
| 374 | /** | 427 | /** |
| 375 | * 查询指定id的苹果商品 | 428 | * 查询指定id的苹果商品 |
| 376 | * productId: 苹果商品id | 429 | * productId: 苹果商品id |
| @@ -666,6 +719,46 @@ interface PlatformHostApi { | @@ -666,6 +719,46 @@ interface PlatformHostApi { | ||
| 666 | } | 719 | } |
| 667 | } | 720 | } |
| 668 | run { | 721 | run { |
| 722 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestGoogleSignIn$separatedMessageChannelSuffix", codec) | ||
| 723 | + if (api != null) { | ||
| 724 | + channel.setMessageHandler { _, reply -> | ||
| 725 | + api.requestGoogleSignIn{ result: Result<GoogleSignInModel?> -> | ||
| 726 | + val error = result.exceptionOrNull() | ||
| 727 | + if (error != null) { | ||
| 728 | + reply.reply(PlatformApiPigeonUtils.wrapError(error)) | ||
| 729 | + } else { | ||
| 730 | + val data = result.getOrNull() | ||
| 731 | + reply.reply(PlatformApiPigeonUtils.wrapResult(data)) | ||
| 732 | + } | ||
| 733 | + } | ||
| 734 | + } | ||
| 735 | + } else { | ||
| 736 | + channel.setMessageHandler(null) | ||
| 737 | + } | ||
| 738 | + } | ||
| 739 | + run { | ||
| 740 | + val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.sendEmail$separatedMessageChannelSuffix", codec) | ||
| 741 | + if (api != null) { | ||
| 742 | + channel.setMessageHandler { message, reply -> | ||
| 743 | + val args = message as List<Any?> | ||
| 744 | + val mailToArg = args[0] as String | ||
| 745 | + val titleArg = args[1] as String | ||
| 746 | + val contentArg = args[2] as String | ||
| 747 | + api.sendEmail(mailToArg, titleArg, contentArg) { result: Result<Boolean> -> | ||
| 748 | + val error = result.exceptionOrNull() | ||
| 749 | + if (error != null) { | ||
| 750 | + reply.reply(PlatformApiPigeonUtils.wrapError(error)) | ||
| 751 | + } else { | ||
| 752 | + val data = result.getOrNull() | ||
| 753 | + reply.reply(PlatformApiPigeonUtils.wrapResult(data)) | ||
| 754 | + } | ||
| 755 | + } | ||
| 756 | + } | ||
| 757 | + } else { | ||
| 758 | + channel.setMessageHandler(null) | ||
| 759 | + } | ||
| 760 | + } | ||
| 761 | + run { | ||
| 669 | val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo$separatedMessageChannelSuffix", codec) | 762 | val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo$separatedMessageChannelSuffix", codec) |
| 670 | if (api != null) { | 763 | if (api != null) { |
| 671 | channel.setMessageHandler { message, reply -> | 764 | channel.setMessageHandler { message, reply -> |
| @@ -29,6 +29,7 @@ flutter_ios_podfile_setup | @@ -29,6 +29,7 @@ flutter_ios_podfile_setup | ||
| 29 | target 'Runner' do | 29 | target 'Runner' do |
| 30 | use_frameworks! | 30 | use_frameworks! |
| 31 | pod 'OBS', :path => './Runner/Library' | 31 | pod 'OBS', :path => './Runner/Library' |
| 32 | + pod 'GoogleSignIn' | ||
| 32 | 33 | ||
| 33 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) |
| 34 | end | 35 | end |
| 1 | PODS: | 1 | PODS: |
| 2 | + - AppAuth (2.1.0): | ||
| 3 | + - AppAuth/Core (= 2.1.0) | ||
| 4 | + - AppAuth/ExternalUserAgent (= 2.1.0) | ||
| 5 | + - AppAuth/Core (2.1.0) | ||
| 6 | + - AppAuth/ExternalUserAgent (2.1.0): | ||
| 7 | + - AppAuth/Core | ||
| 8 | + - AppCheckCore (11.3.1): | ||
| 9 | + - GoogleUtilities/Environment (~> 8.0) | ||
| 10 | + - GoogleUtilities/UserDefaults (~> 8.0) | ||
| 11 | + - PromisesObjC (~> 2.4) | ||
| 12 | + - PromisesSwift (~> 2.4) | ||
| 13 | + - RecaptchaInterop (~> 101.0) | ||
| 2 | - Flutter (1.0.0) | 14 | - Flutter (1.0.0) |
| 3 | - fluttertoast (0.0.2): | 15 | - fluttertoast (0.0.2): |
| 4 | - Flutter | 16 | - Flutter |
| 17 | + - GoogleSignIn (9.2.0): | ||
| 18 | + - AppAuth (~> 2.1) | ||
| 19 | + - AppCheckCore (~> 11.0) | ||
| 20 | + - GTMAppAuth (~> 5.0) | ||
| 21 | + - GTMSessionFetcher/Core (~> 3.3) | ||
| 22 | + - GoogleUtilities/Environment (8.1.2): | ||
| 23 | + - GoogleUtilities/Privacy | ||
| 24 | + - GoogleUtilities/Logger (8.1.2): | ||
| 25 | + - GoogleUtilities/Environment | ||
| 26 | + - GoogleUtilities/Privacy | ||
| 27 | + - GoogleUtilities/Privacy (8.1.2) | ||
| 28 | + - GoogleUtilities/UserDefaults (8.1.2): | ||
| 29 | + - GoogleUtilities/Logger | ||
| 30 | + - GoogleUtilities/Privacy | ||
| 31 | + - GTMAppAuth (5.0.0): | ||
| 32 | + - AppAuth/Core (~> 2.0) | ||
| 33 | + - GTMSessionFetcher/Core (< 4.0, >= 3.3) | ||
| 34 | + - GTMSessionFetcher/Core (3.5.0) | ||
| 5 | - image_cropper (0.0.4): | 35 | - image_cropper (0.0.4): |
| 6 | - Flutter | 36 | - Flutter |
| 7 | - TOCropViewController (~> 2.7.4) | 37 | - TOCropViewController (~> 2.7.4) |
| @@ -25,6 +55,10 @@ PODS: | @@ -25,6 +55,10 @@ PODS: | ||
| 25 | - FlutterMacOS | 55 | - FlutterMacOS |
| 26 | - permission_handler_apple (9.3.0): | 56 | - permission_handler_apple (9.3.0): |
| 27 | - Flutter | 57 | - Flutter |
| 58 | + - PromisesObjC (2.4.1) | ||
| 59 | + - PromisesSwift (2.4.1): | ||
| 60 | + - PromisesObjC (= 2.4.1) | ||
| 61 | + - RecaptchaInterop (101.0.0) | ||
| 28 | - share_plus (0.0.1): | 62 | - share_plus (0.0.1): |
| 29 | - Flutter | 63 | - Flutter |
| 30 | - shared_preferences_foundation (0.0.1): | 64 | - shared_preferences_foundation (0.0.1): |
| @@ -65,6 +99,7 @@ PODS: | @@ -65,6 +99,7 @@ PODS: | ||
| 65 | DEPENDENCIES: | 99 | DEPENDENCIES: |
| 66 | - Flutter (from `Flutter`) | 100 | - Flutter (from `Flutter`) |
| 67 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) | 101 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) |
| 102 | + - GoogleSignIn | ||
| 68 | - image_cropper (from `.symlinks/plugins/image_cropper/ios`) | 103 | - image_cropper (from `.symlinks/plugins/image_cropper/ios`) |
| 69 | - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) | 104 | - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) |
| 70 | - OBS (from `./Runner/Library`) | 105 | - OBS (from `./Runner/Library`) |
| @@ -79,7 +114,16 @@ DEPENDENCIES: | @@ -79,7 +114,16 @@ DEPENDENCIES: | ||
| 79 | 114 | ||
| 80 | SPEC REPOS: | 115 | SPEC REPOS: |
| 81 | trunk: | 116 | trunk: |
| 117 | + - AppAuth | ||
| 118 | + - AppCheckCore | ||
| 119 | + - GoogleSignIn | ||
| 120 | + - GoogleUtilities | ||
| 121 | + - GTMAppAuth | ||
| 122 | + - GTMSessionFetcher | ||
| 82 | - libwebp | 123 | - libwebp |
| 124 | + - PromisesObjC | ||
| 125 | + - PromisesSwift | ||
| 126 | + - RecaptchaInterop | ||
| 83 | - TAThirdParty | 127 | - TAThirdParty |
| 84 | - ThinkingDataCore | 128 | - ThinkingDataCore |
| 85 | - ThinkingSDK | 129 | - ThinkingSDK |
| @@ -114,14 +158,23 @@ EXTERNAL SOURCES: | @@ -114,14 +158,23 @@ EXTERNAL SOURCES: | ||
| 114 | :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" | 158 | :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" |
| 115 | 159 | ||
| 116 | SPEC CHECKSUMS: | 160 | SPEC CHECKSUMS: |
| 161 | + AppAuth: ef4da5a3fc2e10b90c09a0a94a9baeaedc0341d5 | ||
| 162 | + AppCheckCore: e215d35177a9cf469927863e69c13e220df32a8b | ||
| 117 | Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 | 163 | Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 |
| 118 | fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 | 164 | fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 |
| 165 | + GoogleSignIn: e449a40a92e9f2eea56e98b5214d13725dc5a00a | ||
| 166 | + GoogleUtilities: 766ace00c6b10d8148408f329d10c4f051931850 | ||
| 167 | + GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238 | ||
| 168 | + GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 | ||
| 119 | image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537 | 169 | image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537 |
| 120 | image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a | 170 | image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a |
| 121 | libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 | 171 | libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 |
| 122 | OBS: 4cf1e6db5515aefd6c3c6ae3e90e85ea5f028e83 | 172 | OBS: 4cf1e6db5515aefd6c3c6ae3e90e85ea5f028e83 |
| 123 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 | 173 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 |
| 124 | permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d | 174 | permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d |
| 175 | + PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273 | ||
| 176 | + PromisesSwift: 217dea0fd5d2ad65222a109c48698add13cc1c5b | ||
| 177 | + RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba | ||
| 125 | share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a | 178 | share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a |
| 126 | shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 | 179 | shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 |
| 127 | sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 | 180 | sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 |
| @@ -133,6 +186,6 @@ SPEC CHECKSUMS: | @@ -133,6 +186,6 @@ SPEC CHECKSUMS: | ||
| 133 | video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140 | 186 | video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140 |
| 134 | webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 | 187 | webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 |
| 135 | 188 | ||
| 136 | -PODFILE CHECKSUM: 9846d4e0a8198893058beb0df941f3d2ecdcca2c | 189 | +PODFILE CHECKSUM: fe3137a8e1c8e115fcac0a685d21e651b2d2788b |
| 137 | 190 | ||
| 138 | COCOAPODS: 1.16.2 | 191 | COCOAPODS: 1.16.2 |
| @@ -11,6 +11,7 @@ import Flutter | @@ -11,6 +11,7 @@ import Flutter | ||
| 11 | import UserNotifications | 11 | import UserNotifications |
| 12 | import AppTrackingTransparency | 12 | import AppTrackingTransparency |
| 13 | import AdSupport | 13 | import AdSupport |
| 14 | +import GoogleSignIn | ||
| 14 | 15 | ||
| 15 | typealias FlutterBridgeMethod = ((_ params: Any?, _ result: FlutterResult) -> Void) | 16 | typealias FlutterBridgeMethod = ((_ params: Any?, _ result: FlutterResult) -> Void) |
| 16 | 17 | ||
| @@ -48,6 +49,9 @@ class AppDelegate: NSObject, UIApplicationDelegate { | @@ -48,6 +49,9 @@ class AppDelegate: NSObject, UIApplicationDelegate { | ||
| 48 | open url: URL, | 49 | open url: URL, |
| 49 | options: [UIApplication.OpenURLOptionsKey: Any] = [:] | 50 | options: [UIApplication.OpenURLOptionsKey: Any] = [:] |
| 50 | ) -> Bool { | 51 | ) -> Bool { |
| 52 | + if GIDSignIn.sharedInstance.handle(url) { | ||
| 53 | + return true | ||
| 54 | + } | ||
| 51 | handleFlutterUrl(url.absoluteString) | 55 | handleFlutterUrl(url.absoluteString) |
| 52 | return true | 56 | return true |
| 53 | } | 57 | } |
| @@ -11,16 +11,25 @@ import AuthenticationServices | @@ -11,16 +11,25 @@ import AuthenticationServices | ||
| 11 | enum PlatformHostApiError: LocalizedError { | 11 | enum PlatformHostApiError: LocalizedError { |
| 12 | case deallocated | 12 | case deallocated |
| 13 | case invalidAppleCredential | 13 | case invalidAppleCredential |
| 14 | + case invalidGoogleCredential | ||
| 14 | case missingPresentationAnchor | 15 | case missingPresentationAnchor |
| 16 | + case missingGoogleIDToken | ||
| 17 | + case mailComposerAlreadyPresented | ||
| 15 | 18 | ||
| 16 | var errorDescription: String? { | 19 | var errorDescription: String? { |
| 17 | switch self { | 20 | switch self { |
| 18 | case .deallocated: | 21 | case .deallocated: |
| 19 | - return "PlatformHostApiImpl was released before Apple sign-in started." | 22 | + return "PlatformHostApiImpl was released before the native request started." |
| 20 | case .invalidAppleCredential: | 23 | case .invalidAppleCredential: |
| 21 | return "Apple sign-in did not return an Apple ID credential." | 24 | return "Apple sign-in did not return an Apple ID credential." |
| 25 | + case .invalidGoogleCredential: | ||
| 26 | + return "Google sign-in did not return a Google user." | ||
| 22 | case .missingPresentationAnchor: | 27 | case .missingPresentationAnchor: |
| 23 | - return "Unable to find a window for Apple sign-in presentation." | 28 | + return "Unable to find a window for native presentation." |
| 29 | + case .missingGoogleIDToken: | ||
| 30 | + return "Google sign-in did not return an ID token." | ||
| 31 | + case .mailComposerAlreadyPresented: | ||
| 32 | + return "A mail composer is already being presented." | ||
| 24 | } | 33 | } |
| 25 | } | 34 | } |
| 26 | } | 35 | } |
| @@ -11,6 +11,14 @@ | @@ -11,6 +11,14 @@ | ||
| 11 | <key>CFBundleURLTypes</key> | 11 | <key>CFBundleURLTypes</key> |
| 12 | <array> | 12 | <array> |
| 13 | <dict> | 13 | <dict> |
| 14 | + <key>CFBundleURLSchemes</key> | ||
| 15 | + <array> | ||
| 16 | + <string>com.googleusercontent.apps.518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6</string> | ||
| 17 | + </array> | ||
| 18 | + <key>CFBundleTypeRole</key> | ||
| 19 | + <string>Editor</string> | ||
| 20 | + </dict> | ||
| 21 | + <dict> | ||
| 14 | <key>CFBundleTypeRole</key> | 22 | <key>CFBundleTypeRole</key> |
| 15 | <string>Editor</string> | 23 | <string>Editor</string> |
| 16 | <key>CFBundleURLName</key> | 24 | <key>CFBundleURLName</key> |
| @@ -26,8 +34,18 @@ | @@ -26,8 +34,18 @@ | ||
| 26 | <key>NSAllowsArbitraryLoads</key> | 34 | <key>NSAllowsArbitraryLoads</key> |
| 27 | <true/> | 35 | <true/> |
| 28 | </dict> | 36 | </dict> |
| 37 | + <key>LSApplicationQueriesSchemes</key> | ||
| 38 | + <array> | ||
| 39 | + <string>googlegmail</string> | ||
| 40 | + <string>ms-outlook</string> | ||
| 41 | + <string>ymail</string> | ||
| 42 | + <string>readdle-spark</string> | ||
| 43 | + <string>mqqapi</string> | ||
| 44 | + </array> | ||
| 29 | <key>NSHealthShareUsageDescription</key> | 45 | <key>NSHealthShareUsageDescription</key> |
| 30 | <string>需要读取 Apple Health 中的心率、HRV、睡眠、步数、活动能量等数据,用于展示健康状态并同步到 Apple Watch。</string> | 46 | <string>需要读取 Apple Health 中的心率、HRV、睡眠、步数、活动能量等数据,用于展示健康状态并同步到 Apple Watch。</string> |
| 47 | + <key>GIDClientID</key> | ||
| 48 | + <string>518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6.apps.googleusercontent.com</string> | ||
| 31 | <key>NSHealthUpdateUsageDescription</key> | 49 | <key>NSHealthUpdateUsageDescription</key> |
| 32 | <string>需要写入少量健康数据权限以保持与旧版 Apple Health 同步流程兼容。</string> | 50 | <string>需要写入少量健康数据权限以保持与旧版 Apple Health 同步流程兼容。</string> |
| 33 | <key>NSPhotoLibraryUsageDescription</key> | 51 | <key>NSPhotoLibraryUsageDescription</key> |
| @@ -5,6 +5,12 @@ | @@ -5,6 +5,12 @@ | ||
| 5 | <key>CFBundleURLTypes</key> | 5 | <key>CFBundleURLTypes</key> |
| 6 | <array> | 6 | <array> |
| 7 | <dict> | 7 | <dict> |
| 8 | + <key>CFBundleURLSchemes</key> | ||
| 9 | + <array> | ||
| 10 | + <string>com.googleusercontent.apps.518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6</string> | ||
| 11 | + </array> | ||
| 12 | + </dict> | ||
| 13 | + <dict> | ||
| 8 | <key>CFBundleTypeRole</key> | 14 | <key>CFBundleTypeRole</key> |
| 9 | <string>Editor</string> | 15 | <string>Editor</string> |
| 10 | <key>CFBundleURLName</key> | 16 | <key>CFBundleURLName</key> |
| @@ -20,6 +26,14 @@ | @@ -20,6 +26,14 @@ | ||
| 20 | <key>NSAllowsArbitraryLoads</key> | 26 | <key>NSAllowsArbitraryLoads</key> |
| 21 | <true/> | 27 | <true/> |
| 22 | </dict> | 28 | </dict> |
| 29 | + <key>LSApplicationQueriesSchemes</key> | ||
| 30 | + <array> | ||
| 31 | + <string>googlegmail</string> | ||
| 32 | + <string>ms-outlook</string> | ||
| 33 | + <string>ymail</string> | ||
| 34 | + <string>readdle-spark</string> | ||
| 35 | + <string>mqqapi</string> | ||
| 36 | + </array> | ||
| 23 | <key>NSHealthShareUsageDescription</key> | 37 | <key>NSHealthShareUsageDescription</key> |
| 24 | <string>需要读取 Apple Health 中的心率、HRV、睡眠、步数、活动能量等数据,用于展示健康状态并同步到 Apple Watch。</string> | 38 | <string>需要读取 Apple Health 中的心率、HRV、睡眠、步数、活动能量等数据,用于展示健康状态并同步到 Apple Watch。</string> |
| 25 | <key>NSHealthUpdateUsageDescription</key> | 39 | <key>NSHealthUpdateUsageDescription</key> |
| @@ -32,9 +46,11 @@ | @@ -32,9 +46,11 @@ | ||
| 32 | <true/> | 46 | <true/> |
| 33 | <key>UIApplicationSupportsIndirectInputEvents</key> | 47 | <key>UIApplicationSupportsIndirectInputEvents</key> |
| 34 | <true/> | 48 | <true/> |
| 35 | - <key>NSCameraUsageDescription</key> | ||
| 36 | - <string>需要相机拍摄用户头像</string> | ||
| 37 | - <key>NSMicrophoneUsageDescription</key> | ||
| 38 | - <string>需要相机拍摄用户头像</string> | 49 | + <key>NSCameraUsageDescription</key> |
| 50 | + <string>需要相机拍摄用户头像</string> | ||
| 51 | + <key>GIDClientID</key> | ||
| 52 | + <string>518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6.apps.googleusercontent.com</string> | ||
| 53 | + <key>NSMicrophoneUsageDescription</key> | ||
| 54 | + <string>需要相机拍摄用户头像</string> | ||
| 39 | </dict> | 55 | </dict> |
| 40 | </plist> | 56 | </plist> |
| @@ -155,6 +155,47 @@ struct AppleSignInModel: Hashable { | @@ -155,6 +155,47 @@ struct AppleSignInModel: Hashable { | ||
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | /// Generated class from Pigeon that represents data sent in messages. | 157 | /// Generated class from Pigeon that represents data sent in messages. |
| 158 | +struct GoogleSignInModel: Hashable { | ||
| 159 | + var email: String? = nil | ||
| 160 | + var clientID: String? = nil | ||
| 161 | + var idToken: String | ||
| 162 | + var nickname: String? = nil | ||
| 163 | + var avatarUrl: String? = nil | ||
| 164 | + | ||
| 165 | + | ||
| 166 | + // swift-format-ignore: AlwaysUseLowerCamelCase | ||
| 167 | + static func fromList(_ pigeonVar_list: [Any?]) -> GoogleSignInModel? { | ||
| 168 | + let email: String? = nilOrValue(pigeonVar_list[0]) | ||
| 169 | + let clientID: String? = nilOrValue(pigeonVar_list[1]) | ||
| 170 | + let idToken = pigeonVar_list[2] as! String | ||
| 171 | + let nickname: String? = nilOrValue(pigeonVar_list[3]) | ||
| 172 | + let avatarUrl: String? = nilOrValue(pigeonVar_list[4]) | ||
| 173 | + | ||
| 174 | + return GoogleSignInModel( | ||
| 175 | + email: email, | ||
| 176 | + clientID: clientID, | ||
| 177 | + idToken: idToken, | ||
| 178 | + nickname: nickname, | ||
| 179 | + avatarUrl: avatarUrl | ||
| 180 | + ) | ||
| 181 | + } | ||
| 182 | + func toList() -> [Any?] { | ||
| 183 | + return [ | ||
| 184 | + email, | ||
| 185 | + clientID, | ||
| 186 | + idToken, | ||
| 187 | + nickname, | ||
| 188 | + avatarUrl, | ||
| 189 | + ] | ||
| 190 | + } | ||
| 191 | + static func == (lhs: GoogleSignInModel, rhs: GoogleSignInModel) -> Bool { | ||
| 192 | + return deepEqualsPlatformApi(lhs.toList(), rhs.toList()) } | ||
| 193 | + func hash(into hasher: inout Hasher) { | ||
| 194 | + deepHashPlatformApi(value: toList(), hasher: &hasher) | ||
| 195 | + } | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +/// Generated class from Pigeon that represents data sent in messages. | ||
| 158 | struct WatchAppOtherInfo: Hashable { | 199 | struct WatchAppOtherInfo: Hashable { |
| 159 | var userId: Int64? = nil | 200 | var userId: Int64? = nil |
| 160 | var nickname: String? = nil | 201 | var nickname: String? = nil |
| @@ -314,10 +355,12 @@ private class PlatformApiPigeonCodecReader: FlutterStandardReader { | @@ -314,10 +355,12 @@ private class PlatformApiPigeonCodecReader: FlutterStandardReader { | ||
| 314 | case 130: | 355 | case 130: |
| 315 | return AppleSignInModel.fromList(self.readValue() as! [Any?]) | 356 | return AppleSignInModel.fromList(self.readValue() as! [Any?]) |
| 316 | case 131: | 357 | case 131: |
| 317 | - return WatchAppOtherInfo.fromList(self.readValue() as! [Any?]) | 358 | + return GoogleSignInModel.fromList(self.readValue() as! [Any?]) |
| 318 | case 132: | 359 | case 132: |
| 319 | - return AppleProductInfo.fromList(self.readValue() as! [Any?]) | 360 | + return WatchAppOtherInfo.fromList(self.readValue() as! [Any?]) |
| 320 | case 133: | 361 | case 133: |
| 362 | + return AppleProductInfo.fromList(self.readValue() as! [Any?]) | ||
| 363 | + case 134: | ||
| 321 | return AppleProductPaymentResult.fromList(self.readValue() as! [Any?]) | 364 | return AppleProductPaymentResult.fromList(self.readValue() as! [Any?]) |
| 322 | default: | 365 | default: |
| 323 | return super.readValue(ofType: type) | 366 | return super.readValue(ofType: type) |
| @@ -333,15 +376,18 @@ private class PlatformApiPigeonCodecWriter: FlutterStandardWriter { | @@ -333,15 +376,18 @@ private class PlatformApiPigeonCodecWriter: FlutterStandardWriter { | ||
| 333 | } else if let value = value as? AppleSignInModel { | 376 | } else if let value = value as? AppleSignInModel { |
| 334 | super.writeByte(130) | 377 | super.writeByte(130) |
| 335 | super.writeValue(value.toList()) | 378 | super.writeValue(value.toList()) |
| 336 | - } else if let value = value as? WatchAppOtherInfo { | 379 | + } else if let value = value as? GoogleSignInModel { |
| 337 | super.writeByte(131) | 380 | super.writeByte(131) |
| 338 | super.writeValue(value.toList()) | 381 | super.writeValue(value.toList()) |
| 339 | - } else if let value = value as? AppleProductInfo { | 382 | + } else if let value = value as? WatchAppOtherInfo { |
| 340 | super.writeByte(132) | 383 | super.writeByte(132) |
| 341 | super.writeValue(value.toList()) | 384 | super.writeValue(value.toList()) |
| 342 | - } else if let value = value as? AppleProductPaymentResult { | 385 | + } else if let value = value as? AppleProductInfo { |
| 343 | super.writeByte(133) | 386 | super.writeByte(133) |
| 344 | super.writeValue(value.toList()) | 387 | super.writeValue(value.toList()) |
| 388 | + } else if let value = value as? AppleProductPaymentResult { | ||
| 389 | + super.writeByte(134) | ||
| 390 | + super.writeValue(value.toList()) | ||
| 345 | } else { | 391 | } else { |
| 346 | super.writeValue(value) | 392 | super.writeValue(value) |
| 347 | } | 393 | } |
| @@ -397,6 +443,10 @@ protocol PlatformHostApi { | @@ -397,6 +443,10 @@ protocol PlatformHostApi { | ||
| 397 | func requestUnhandedUrl() throws -> String? | 443 | func requestUnhandedUrl() throws -> String? |
| 398 | /// 请求苹果登录 | 444 | /// 请求苹果登录 |
| 399 | func requestAppleSignIn(completion: @escaping (Result<AppleSignInModel?, Error>) -> Void) | 445 | func requestAppleSignIn(completion: @escaping (Result<AppleSignInModel?, Error>) -> Void) |
| 446 | + /// google登录 | ||
| 447 | + func requestGoogleSignIn(completion: @escaping (Result<GoogleSignInModel?, Error>) -> Void) | ||
| 448 | + /// 发送邮件 | ||
| 449 | + func sendEmail(mailTo: String, title: String, content: String, completion: @escaping (Result<Bool, Error>) -> Void) | ||
| 400 | /// 查询指定id的苹果商品 | 450 | /// 查询指定id的苹果商品 |
| 401 | /// productId: 苹果商品id | 451 | /// productId: 苹果商品id |
| 402 | /// baseUnit: 除数基础单位(多少个天、周、月) -> | 452 | /// baseUnit: 除数基础单位(多少个天、周、月) -> |
| @@ -658,6 +708,42 @@ class PlatformHostApiSetup { | @@ -658,6 +708,42 @@ class PlatformHostApiSetup { | ||
| 658 | } else { | 708 | } else { |
| 659 | requestAppleSignInChannel.setMessageHandler(nil) | 709 | requestAppleSignInChannel.setMessageHandler(nil) |
| 660 | } | 710 | } |
| 711 | + /// google登录 | ||
| 712 | + let requestGoogleSignInChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestGoogleSignIn\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 713 | + if let api = api { | ||
| 714 | + requestGoogleSignInChannel.setMessageHandler { _, reply in | ||
| 715 | + api.requestGoogleSignIn { result in | ||
| 716 | + switch result { | ||
| 717 | + case .success(let res): | ||
| 718 | + reply(wrapResult(res)) | ||
| 719 | + case .failure(let error): | ||
| 720 | + reply(wrapError(error)) | ||
| 721 | + } | ||
| 722 | + } | ||
| 723 | + } | ||
| 724 | + } else { | ||
| 725 | + requestGoogleSignInChannel.setMessageHandler(nil) | ||
| 726 | + } | ||
| 727 | + /// 发送邮件 | ||
| 728 | + let sendEmailChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.sendEmail\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) | ||
| 729 | + if let api = api { | ||
| 730 | + sendEmailChannel.setMessageHandler { message, reply in | ||
| 731 | + let args = message as! [Any?] | ||
| 732 | + let mailToArg = args[0] as! String | ||
| 733 | + let titleArg = args[1] as! String | ||
| 734 | + let contentArg = args[2] as! String | ||
| 735 | + api.sendEmail(mailTo: mailToArg, title: titleArg, content: contentArg) { result in | ||
| 736 | + switch result { | ||
| 737 | + case .success(let res): | ||
| 738 | + reply(wrapResult(res)) | ||
| 739 | + case .failure(let error): | ||
| 740 | + reply(wrapError(error)) | ||
| 741 | + } | ||
| 742 | + } | ||
| 743 | + } | ||
| 744 | + } else { | ||
| 745 | + sendEmailChannel.setMessageHandler(nil) | ||
| 746 | + } | ||
| 661 | /// 查询指定id的苹果商品 | 747 | /// 查询指定id的苹果商品 |
| 662 | /// productId: 苹果商品id | 748 | /// productId: 苹果商品id |
| 663 | /// baseUnit: 除数基础单位(多少个天、周、月) -> | 749 | /// baseUnit: 除数基础单位(多少个天、周、月) -> |
| @@ -3,6 +3,8 @@ import AuthenticationServices | @@ -3,6 +3,8 @@ import AuthenticationServices | ||
| 3 | import StoreKit | 3 | import StoreKit |
| 4 | import UIKit | 4 | import UIKit |
| 5 | import WebKit | 5 | import WebKit |
| 6 | +import GoogleSignIn | ||
| 7 | +import MessageUI | ||
| 6 | 8 | ||
| 7 | class PriceFormatter{ | 9 | class PriceFormatter{ |
| 8 | static func formatPrice(_ price: Decimal, currencyCode: String) -> String { | 10 | static func formatPrice(_ price: Decimal, currencyCode: String) -> String { |
| @@ -46,7 +48,9 @@ extension WatchAppOtherInfo{ | @@ -46,7 +48,9 @@ extension WatchAppOtherInfo{ | ||
| 46 | * 组装完整 User-Agent,格式与 Android 端保持一致: | 48 | * 组装完整 User-Agent,格式与 Android 端保持一致: |
| 47 | * `{systemWebViewUA} doublefeel/{versionCode}({versionName})(Apple##Apple##{model}; iOS{osVersion}; {height}x{width})(huawei)` | 49 | * `{systemWebViewUA} doublefeel/{versionCode}({versionName})(Apple##Apple##{model}; iOS{osVersion}; {height}x{width})(huawei)` |
| 48 | */ | 50 | */ |
| 49 | -final class PlatformHostApiImpl: PlatformHostApi { | 51 | +final class PlatformHostApiImpl: NSObject, PlatformHostApi { |
| 52 | + private var sendEmailCompletion: ((Result<Bool, any Error>) -> Void)? | ||
| 53 | + | ||
| 50 | func isDebugEnvoriment() throws -> Bool { | 54 | func isDebugEnvoriment() throws -> Bool { |
| 51 | #if DEBUG || CI_ENV | 55 | #if DEBUG || CI_ENV |
| 52 | return true | 56 | return true |
| @@ -348,6 +352,124 @@ final class PlatformHostApiImpl: PlatformHostApi { | @@ -348,6 +352,124 @@ final class PlatformHostApiImpl: PlatformHostApi { | ||
| 348 | } | 352 | } |
| 349 | } | 353 | } |
| 350 | 354 | ||
| 355 | + func requestGoogleSignIn(completion: @escaping (Result<GoogleSignInModel?, any Error>) -> Void) { | ||
| 356 | + DispatchQueue.main.async { | ||
| 357 | + guard let viewController = Self.topViewController() else { | ||
| 358 | + print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(PlatformHostApiError.missingPresentationAnchor.localizedDescription)") | ||
| 359 | + completion(.failure(PlatformHostApiError.missingPresentationAnchor)) | ||
| 360 | + return | ||
| 361 | + } | ||
| 362 | + | ||
| 363 | + GIDSignIn.sharedInstance.signIn(withPresenting: viewController) { signInResult, error in | ||
| 364 | + if let error { | ||
| 365 | + let nsError = error as NSError | ||
| 366 | + if nsError.domain == kGIDSignInErrorDomain, nsError.code == -5 { | ||
| 367 | + print("[PlatformHostApiImpl.requestGoogleSignIn] return: nil") | ||
| 368 | + completion(.success(nil)) | ||
| 369 | + } else { | ||
| 370 | + print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(error.localizedDescription)") | ||
| 371 | + completion(.failure(error)) | ||
| 372 | + } | ||
| 373 | + return | ||
| 374 | + } | ||
| 375 | + | ||
| 376 | + guard let user = signInResult?.user else { | ||
| 377 | + print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(PlatformHostApiError.invalidGoogleCredential.localizedDescription)") | ||
| 378 | + completion(.failure(PlatformHostApiError.invalidGoogleCredential)) | ||
| 379 | + return | ||
| 380 | + } | ||
| 381 | + | ||
| 382 | + guard let token = user.idToken?.tokenString else { | ||
| 383 | + print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(PlatformHostApiError.missingGoogleIDToken.localizedDescription)") | ||
| 384 | + completion(.failure(PlatformHostApiError.missingGoogleIDToken)) | ||
| 385 | + return | ||
| 386 | + } | ||
| 387 | + | ||
| 388 | + let profilePicUrl = user.profile?.imageURL(withDimension: 240) | ||
| 389 | + let model = GoogleSignInModel( | ||
| 390 | + email: user.profile?.email, | ||
| 391 | + clientID: user.configuration.clientID, | ||
| 392 | + idToken: token, | ||
| 393 | + nickname: user.profile?.name, | ||
| 394 | + avatarUrl: profilePicUrl?.absoluteString | ||
| 395 | + ) | ||
| 396 | + print("[PlatformHostApiImpl.requestGoogleSignIn] return: \(model)") | ||
| 397 | + completion(.success(model)) | ||
| 398 | + } | ||
| 399 | + } | ||
| 400 | + } | ||
| 401 | + | ||
| 402 | + func sendEmail(mailTo: String, title: String, content: String, completion: @escaping (Result<Bool, any Error>) -> Void) { | ||
| 403 | + DispatchQueue.main.async { [weak self] in | ||
| 404 | + guard let self else { | ||
| 405 | + print("[PlatformHostApiImpl.sendEmail] error: \(PlatformHostApiError.deallocated.localizedDescription)") | ||
| 406 | + completion(.failure(PlatformHostApiError.deallocated)) | ||
| 407 | + return | ||
| 408 | + } | ||
| 409 | + | ||
| 410 | + guard MFMailComposeViewController.canSendMail() else { | ||
| 411 | + UIApplication.openEmailWithSchema(to: mailTo, subject: title, body: content) { opened in | ||
| 412 | + print("[PlatformHostApiImpl.sendEmail] return: \(opened)") | ||
| 413 | + completion(.success(opened)) | ||
| 414 | + } | ||
| 415 | + return | ||
| 416 | + } | ||
| 417 | + | ||
| 418 | + guard sendEmailCompletion == nil else { | ||
| 419 | + print("[PlatformHostApiImpl.sendEmail] error: \(PlatformHostApiError.mailComposerAlreadyPresented.localizedDescription)") | ||
| 420 | + completion(.failure(PlatformHostApiError.mailComposerAlreadyPresented)) | ||
| 421 | + return | ||
| 422 | + } | ||
| 423 | + | ||
| 424 | + guard let viewController = Self.topViewController() else { | ||
| 425 | + print("[PlatformHostApiImpl.sendEmail] error: \(PlatformHostApiError.missingPresentationAnchor.localizedDescription)") | ||
| 426 | + completion(.failure(PlatformHostApiError.missingPresentationAnchor)) | ||
| 427 | + return | ||
| 428 | + } | ||
| 429 | + | ||
| 430 | + sendEmailCompletion = completion | ||
| 431 | + let mailVC = MFMailComposeViewController() | ||
| 432 | + mailVC.mailComposeDelegate = self | ||
| 433 | + mailVC.setToRecipients([mailTo]) | ||
| 434 | + mailVC.setSubject(title) | ||
| 435 | + mailVC.setMessageBody(content, isHTML: false) | ||
| 436 | + viewController.present(mailVC, animated: true) | ||
| 437 | + } | ||
| 438 | + } | ||
| 439 | +} | ||
| 440 | + | ||
| 441 | +extension PlatformHostApiImpl: MFMailComposeViewControllerDelegate { | ||
| 442 | + func mailComposeController( | ||
| 443 | + _ controller: MFMailComposeViewController, | ||
| 444 | + didFinishWith result: MFMailComposeResult, | ||
| 445 | + error: Error? | ||
| 446 | + ) { | ||
| 447 | + controller.dismiss(animated: true) { [weak self] in | ||
| 448 | + guard let self else { return } | ||
| 449 | + let completion = self.sendEmailCompletion | ||
| 450 | + self.sendEmailCompletion = nil | ||
| 451 | + | ||
| 452 | + if let error { | ||
| 453 | + print("[PlatformHostApiImpl.sendEmail] error: \(error.localizedDescription)") | ||
| 454 | + completion?(.failure(error)) | ||
| 455 | + return | ||
| 456 | + } | ||
| 457 | + | ||
| 458 | + let success: Bool | ||
| 459 | + switch result { | ||
| 460 | + case .cancelled: | ||
| 461 | + success = false | ||
| 462 | + case .saved, .sent: | ||
| 463 | + success = true | ||
| 464 | + case .failed: | ||
| 465 | + success = false | ||
| 466 | + @unknown default: | ||
| 467 | + success = false | ||
| 468 | + } | ||
| 469 | + print("[PlatformHostApiImpl.sendEmail] return: \(success)") | ||
| 470 | + completion?(.success(success)) | ||
| 471 | + } | ||
| 472 | + } | ||
| 351 | } | 473 | } |
| 352 | 474 | ||
| 353 | //MARK: - 分享 | 475 | //MARK: - 分享 |
| @@ -419,3 +541,48 @@ extension PlatformHostApiImpl{ | @@ -419,3 +541,48 @@ extension PlatformHostApiImpl{ | ||
| 419 | return rootViewController | 541 | return rootViewController |
| 420 | } | 542 | } |
| 421 | } | 543 | } |
| 544 | + | ||
| 545 | +extension UIApplication{ | ||
| 546 | + | ||
| 547 | + static func openEmailWithSchema( | ||
| 548 | + to: String, | ||
| 549 | + subject: String, | ||
| 550 | + body: String, | ||
| 551 | + completion: ((Bool) -> Void)? = nil | ||
| 552 | + ) { | ||
| 553 | + if let url = createEmailUrl(to: to, subject: subject, body: body), | ||
| 554 | + UIApplication.shared.canOpenURL(url){ | ||
| 555 | + UIApplication.shared.open(url, completionHandler: completion) | ||
| 556 | + }else{ | ||
| 557 | + let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! | ||
| 558 | + let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! | ||
| 559 | + let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")! | ||
| 560 | + UIApplication.shared.open(defaultUrl, completionHandler: completion) | ||
| 561 | + } | ||
| 562 | + } | ||
| 563 | + | ||
| 564 | + static func createEmailUrl(to: String, subject: String, body: String) -> URL? { | ||
| 565 | + let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! | ||
| 566 | + let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)! | ||
| 567 | + let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)") | ||
| 568 | + let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)") | ||
| 569 | + let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)") | ||
| 570 | + let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)") | ||
| 571 | + let qqEmail = URL(string: "mqqapi://composeemail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)") | ||
| 572 | + | ||
| 573 | + let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)") | ||
| 574 | + | ||
| 575 | + if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) { | ||
| 576 | + return gmailUrl | ||
| 577 | + } else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) { | ||
| 578 | + return outlookUrl | ||
| 579 | + } else if let yahooMail = yahooMail,UIApplication.shared.canOpenURL(yahooMail) { | ||
| 580 | + return yahooMail | ||
| 581 | + } else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) { | ||
| 582 | + return sparkUrl | ||
| 583 | + }else if let qqEmail, UIApplication.shared.canOpenURL(qqEmail) { | ||
| 584 | + return qqEmail | ||
| 585 | + } | ||
| 586 | + return defaultUrl | ||
| 587 | + } | ||
| 588 | +} |
| @@ -91,6 +91,67 @@ class AppleSignInModel { | @@ -91,6 +91,67 @@ class AppleSignInModel { | ||
| 91 | ; | 91 | ; |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | +class GoogleSignInModel { | ||
| 95 | + GoogleSignInModel({ | ||
| 96 | + this.email, | ||
| 97 | + this.clientID, | ||
| 98 | + required this.idToken, | ||
| 99 | + this.nickname, | ||
| 100 | + this.avatarUrl, | ||
| 101 | + }); | ||
| 102 | + | ||
| 103 | + String? email; | ||
| 104 | + | ||
| 105 | + String? clientID; | ||
| 106 | + | ||
| 107 | + String idToken; | ||
| 108 | + | ||
| 109 | + String? nickname; | ||
| 110 | + | ||
| 111 | + String? avatarUrl; | ||
| 112 | + | ||
| 113 | + List<Object?> _toList() { | ||
| 114 | + return <Object?>[ | ||
| 115 | + email, | ||
| 116 | + clientID, | ||
| 117 | + idToken, | ||
| 118 | + nickname, | ||
| 119 | + avatarUrl, | ||
| 120 | + ]; | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + Object encode() { | ||
| 124 | + return _toList(); } | ||
| 125 | + | ||
| 126 | + static GoogleSignInModel decode(Object result) { | ||
| 127 | + result as List<Object?>; | ||
| 128 | + return GoogleSignInModel( | ||
| 129 | + email: result[0] as String?, | ||
| 130 | + clientID: result[1] as String?, | ||
| 131 | + idToken: result[2]! as String, | ||
| 132 | + nickname: result[3] as String?, | ||
| 133 | + avatarUrl: result[4] as String?, | ||
| 134 | + ); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + @override | ||
| 138 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 139 | + bool operator ==(Object other) { | ||
| 140 | + if (other is! GoogleSignInModel || other.runtimeType != runtimeType) { | ||
| 141 | + return false; | ||
| 142 | + } | ||
| 143 | + if (identical(this, other)) { | ||
| 144 | + return true; | ||
| 145 | + } | ||
| 146 | + return _deepEquals(encode(), other.encode()); | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + @override | ||
| 150 | + // ignore: avoid_equals_and_hash_code_on_mutable_classes | ||
| 151 | + int get hashCode => Object.hashAll(_toList()) | ||
| 152 | +; | ||
| 153 | +} | ||
| 154 | + | ||
| 94 | class WatchAppOtherInfo { | 155 | class WatchAppOtherInfo { |
| 95 | WatchAppOtherInfo({ | 156 | WatchAppOtherInfo({ |
| 96 | this.userId, | 157 | this.userId, |
| @@ -316,15 +377,18 @@ class _PigeonCodec extends StandardMessageCodec { | @@ -316,15 +377,18 @@ class _PigeonCodec extends StandardMessageCodec { | ||
| 316 | } else if (value is AppleSignInModel) { | 377 | } else if (value is AppleSignInModel) { |
| 317 | buffer.putUint8(130); | 378 | buffer.putUint8(130); |
| 318 | writeValue(buffer, value.encode()); | 379 | writeValue(buffer, value.encode()); |
| 319 | - } else if (value is WatchAppOtherInfo) { | 380 | + } else if (value is GoogleSignInModel) { |
| 320 | buffer.putUint8(131); | 381 | buffer.putUint8(131); |
| 321 | writeValue(buffer, value.encode()); | 382 | writeValue(buffer, value.encode()); |
| 322 | - } else if (value is AppleProductInfo) { | 383 | + } else if (value is WatchAppOtherInfo) { |
| 323 | buffer.putUint8(132); | 384 | buffer.putUint8(132); |
| 324 | writeValue(buffer, value.encode()); | 385 | writeValue(buffer, value.encode()); |
| 325 | - } else if (value is AppleProductPaymentResult) { | 386 | + } else if (value is AppleProductInfo) { |
| 326 | buffer.putUint8(133); | 387 | buffer.putUint8(133); |
| 327 | writeValue(buffer, value.encode()); | 388 | writeValue(buffer, value.encode()); |
| 389 | + } else if (value is AppleProductPaymentResult) { | ||
| 390 | + buffer.putUint8(134); | ||
| 391 | + writeValue(buffer, value.encode()); | ||
| 328 | } else { | 392 | } else { |
| 329 | super.writeValue(buffer, value); | 393 | super.writeValue(buffer, value); |
| 330 | } | 394 | } |
| @@ -339,10 +403,12 @@ class _PigeonCodec extends StandardMessageCodec { | @@ -339,10 +403,12 @@ class _PigeonCodec extends StandardMessageCodec { | ||
| 339 | case 130: | 403 | case 130: |
| 340 | return AppleSignInModel.decode(readValue(buffer)!); | 404 | return AppleSignInModel.decode(readValue(buffer)!); |
| 341 | case 131: | 405 | case 131: |
| 342 | - return WatchAppOtherInfo.decode(readValue(buffer)!); | 406 | + return GoogleSignInModel.decode(readValue(buffer)!); |
| 343 | case 132: | 407 | case 132: |
| 344 | - return AppleProductInfo.decode(readValue(buffer)!); | 408 | + return WatchAppOtherInfo.decode(readValue(buffer)!); |
| 345 | case 133: | 409 | case 133: |
| 410 | + return AppleProductInfo.decode(readValue(buffer)!); | ||
| 411 | + case 134: | ||
| 346 | return AppleProductPaymentResult.decode(readValue(buffer)!); | 412 | return AppleProductPaymentResult.decode(readValue(buffer)!); |
| 347 | default: | 413 | default: |
| 348 | return super.readValueOfType(type, buffer); | 414 | return super.readValueOfType(type, buffer); |
| @@ -770,6 +836,59 @@ class PlatformHostApi { | @@ -770,6 +836,59 @@ class PlatformHostApi { | ||
| 770 | } | 836 | } |
| 771 | } | 837 | } |
| 772 | 838 | ||
| 839 | + /// google登录 | ||
| 840 | + Future<GoogleSignInModel?> requestGoogleSignIn() async { | ||
| 841 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestGoogleSignIn$pigeonVar_messageChannelSuffix'; | ||
| 842 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 843 | + pigeonVar_channelName, | ||
| 844 | + pigeonChannelCodec, | ||
| 845 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 846 | + ); | ||
| 847 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null); | ||
| 848 | + final List<Object?>? pigeonVar_replyList = | ||
| 849 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 850 | + if (pigeonVar_replyList == null) { | ||
| 851 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 852 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 853 | + throw PlatformException( | ||
| 854 | + code: pigeonVar_replyList[0]! as String, | ||
| 855 | + message: pigeonVar_replyList[1] as String?, | ||
| 856 | + details: pigeonVar_replyList[2], | ||
| 857 | + ); | ||
| 858 | + } else { | ||
| 859 | + return (pigeonVar_replyList[0] as GoogleSignInModel?); | ||
| 860 | + } | ||
| 861 | + } | ||
| 862 | + | ||
| 863 | + /// 发送邮件 | ||
| 864 | + Future<bool> sendEmail(String mailTo, String title, String content) async { | ||
| 865 | + final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.sendEmail$pigeonVar_messageChannelSuffix'; | ||
| 866 | + final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>( | ||
| 867 | + pigeonVar_channelName, | ||
| 868 | + pigeonChannelCodec, | ||
| 869 | + binaryMessenger: pigeonVar_binaryMessenger, | ||
| 870 | + ); | ||
| 871 | + final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[mailTo, title, content]); | ||
| 872 | + final List<Object?>? pigeonVar_replyList = | ||
| 873 | + await pigeonVar_sendFuture as List<Object?>?; | ||
| 874 | + if (pigeonVar_replyList == null) { | ||
| 875 | + throw _createConnectionError(pigeonVar_channelName); | ||
| 876 | + } else if (pigeonVar_replyList.length > 1) { | ||
| 877 | + throw PlatformException( | ||
| 878 | + code: pigeonVar_replyList[0]! as String, | ||
| 879 | + message: pigeonVar_replyList[1] as String?, | ||
| 880 | + details: pigeonVar_replyList[2], | ||
| 881 | + ); | ||
| 882 | + } else if (pigeonVar_replyList[0] == null) { | ||
| 883 | + throw PlatformException( | ||
| 884 | + code: 'null-error', | ||
| 885 | + message: 'Host platform returned null value for non-null return value.', | ||
| 886 | + ); | ||
| 887 | + } else { | ||
| 888 | + return (pigeonVar_replyList[0] as bool?)!; | ||
| 889 | + } | ||
| 890 | + } | ||
| 891 | + | ||
| 773 | /// 查询指定id的苹果商品 | 892 | /// 查询指定id的苹果商品 |
| 774 | /// productId: 苹果商品id | 893 | /// productId: 苹果商品id |
| 775 | /// baseUnit: 除数基础单位(多少个天、周、月) -> | 894 | /// baseUnit: 除数基础单位(多少个天、周、月) -> |
| @@ -13,6 +13,13 @@ class AppleSignInModel { | @@ -13,6 +13,13 @@ class AppleSignInModel { | ||
| 13 | this.identityToken, | 13 | this.identityToken, |
| 14 | }); | 14 | }); |
| 15 | } | 15 | } |
| 16 | +class GoogleSignInModel{ | ||
| 17 | + final String? email; | ||
| 18 | + final String? clientID; | ||
| 19 | + final String idToken; | ||
| 20 | + final String? nickname; | ||
| 21 | + final String? avatarUrl; | ||
| 22 | +} | ||
| 16 | 23 | ||
| 17 | class WatchAppOtherInfo { | 24 | class WatchAppOtherInfo { |
| 18 | final int? userId; | 25 | final int? userId; |
| @@ -163,6 +170,13 @@ abstract class PlatformHostApi { | @@ -163,6 +170,13 @@ abstract class PlatformHostApi { | ||
| 163 | /// 请求苹果登录 | 170 | /// 请求苹果登录 |
| 164 | @async | 171 | @async |
| 165 | AppleSignInModel? requestAppleSignIn(); | 172 | AppleSignInModel? requestAppleSignIn(); |
| 173 | + /// google登录 | ||
| 174 | + @async | ||
| 175 | + GoogleSignInModel? requestGoogleSignIn(); | ||
| 176 | + | ||
| 177 | + /// 发送邮件 | ||
| 178 | + @async | ||
| 179 | + bool sendEmail(String mailTo, String title, String content); | ||
| 166 | 180 | ||
| 167 | /// 查询指定id的苹果商品 | 181 | /// 查询指定id的苹果商品 |
| 168 | /// productId: 苹果商品id | 182 | /// productId: 苹果商品id |
-
Please register or login to post a comment