Commit 4044a3452f53d1acdc8ab68a4045c8de1ddf6e11

Authored by 权海
1 parent cb34ffd6

feat(ui):增肌google sign in、发送邮件的支持

... ... @@ -128,6 +128,46 @@ data class AppleSignInModel (
}
/** Generated class from Pigeon that represents data sent in messages. */
data class GoogleSignInModel (
val email: String? = null,
val clientID: String? = null,
val idToken: String,
val nickname: String? = null,
val avatarUrl: String? = null
)
{
companion object {
fun fromList(pigeonVar_list: List<Any?>): GoogleSignInModel {
val email = pigeonVar_list[0] as String?
val clientID = pigeonVar_list[1] as String?
val idToken = pigeonVar_list[2] as String
val nickname = pigeonVar_list[3] as String?
val avatarUrl = pigeonVar_list[4] as String?
return GoogleSignInModel(email, clientID, idToken, nickname, avatarUrl)
}
}
fun toList(): List<Any?> {
return listOf(
email,
clientID,
idToken,
nickname,
avatarUrl,
)
}
override fun equals(other: Any?): Boolean {
if (other !is GoogleSignInModel) {
return false
}
if (this === other) {
return true
}
return PlatformApiPigeonUtils.deepEquals(toList(), other.toList()) }
override fun hashCode(): Int = toList().hashCode()
}
/** Generated class from Pigeon that represents data sent in messages. */
data class WatchAppOtherInfo (
val userId: Long? = null,
val nickname: String? = null,
... ... @@ -287,16 +327,21 @@ private open class PlatformApiPigeonCodec : StandardMessageCodec() {
}
131.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
WatchAppOtherInfo.fromList(it)
GoogleSignInModel.fromList(it)
}
}
132.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AppleProductInfo.fromList(it)
WatchAppOtherInfo.fromList(it)
}
}
133.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AppleProductInfo.fromList(it)
}
}
134.toByte() -> {
return (readValue(buffer) as? List<Any?>)?.let {
AppleProductPaymentResult.fromList(it)
}
}
... ... @@ -313,18 +358,22 @@ private open class PlatformApiPigeonCodec : StandardMessageCodec() {
stream.write(130)
writeValue(stream, value.toList())
}
is WatchAppOtherInfo -> {
is GoogleSignInModel -> {
stream.write(131)
writeValue(stream, value.toList())
}
is AppleProductInfo -> {
is WatchAppOtherInfo -> {
stream.write(132)
writeValue(stream, value.toList())
}
is AppleProductPaymentResult -> {
is AppleProductInfo -> {
stream.write(133)
writeValue(stream, value.toList())
}
is AppleProductPaymentResult -> {
stream.write(134)
writeValue(stream, value.toList())
}
else -> super.writeValue(stream, value)
}
}
... ... @@ -371,6 +420,10 @@ interface PlatformHostApi {
fun requestUnhandedUrl(): String?
/** 请求苹果登录 */
fun requestAppleSignIn(callback: (Result<AppleSignInModel?>) -> Unit)
/** google登录 */
fun requestGoogleSignIn(callback: (Result<GoogleSignInModel?>) -> Unit)
/** 发送邮件 */
fun sendEmail(mailTo: String, title: String, content: String, callback: (Result<Boolean>) -> Unit)
/**
* 查询指定id的苹果商品
* productId: 苹果商品id
... ... @@ -666,6 +719,46 @@ interface PlatformHostApi {
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestGoogleSignIn$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
api.requestGoogleSignIn{ result: Result<GoogleSignInModel?> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PlatformApiPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(PlatformApiPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.sendEmail$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val mailToArg = args[0] as String
val titleArg = args[1] as String
val contentArg = args[2] as String
api.sendEmail(mailToArg, titleArg, contentArg) { result: Result<Boolean> ->
val error = result.exceptionOrNull()
if (error != null) {
reply.reply(PlatformApiPigeonUtils.wrapError(error))
} else {
val data = result.getOrNull()
reply.reply(PlatformApiPigeonUtils.wrapResult(data))
}
}
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestAppleProductInfo$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
... ...
... ... @@ -29,6 +29,7 @@ flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
pod 'OBS', :path => './Runner/Library'
pod 'GoogleSignIn'
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
... ...
PODS:
- AppAuth (2.1.0):
- AppAuth/Core (= 2.1.0)
- AppAuth/ExternalUserAgent (= 2.1.0)
- AppAuth/Core (2.1.0)
- AppAuth/ExternalUserAgent (2.1.0):
- AppAuth/Core
- AppCheckCore (11.3.1):
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- PromisesSwift (~> 2.4)
- RecaptchaInterop (~> 101.0)
- Flutter (1.0.0)
- fluttertoast (0.0.2):
- Flutter
- GoogleSignIn (9.2.0):
- AppAuth (~> 2.1)
- AppCheckCore (~> 11.0)
- GTMAppAuth (~> 5.0)
- GTMSessionFetcher/Core (~> 3.3)
- GoogleUtilities/Environment (8.1.2):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.2):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.2)
- GoogleUtilities/UserDefaults (8.1.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GTMAppAuth (5.0.0):
- AppAuth/Core (~> 2.0)
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
- GTMSessionFetcher/Core (3.5.0)
- image_cropper (0.0.4):
- Flutter
- TOCropViewController (~> 2.7.4)
... ... @@ -25,6 +55,10 @@ PODS:
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- PromisesObjC (2.4.1)
- PromisesSwift (2.4.1):
- PromisesObjC (= 2.4.1)
- RecaptchaInterop (101.0.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
... ... @@ -65,6 +99,7 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- GoogleSignIn
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- OBS (from `./Runner/Library`)
... ... @@ -79,7 +114,16 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- AppAuth
- AppCheckCore
- GoogleSignIn
- GoogleUtilities
- GTMAppAuth
- GTMSessionFetcher
- libwebp
- PromisesObjC
- PromisesSwift
- RecaptchaInterop
- TAThirdParty
- ThinkingDataCore
- ThinkingSDK
... ... @@ -114,14 +158,23 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
AppAuth: ef4da5a3fc2e10b90c09a0a94a9baeaedc0341d5
AppCheckCore: e215d35177a9cf469927863e69c13e220df32a8b
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
GoogleSignIn: e449a40a92e9f2eea56e98b5214d13725dc5a00a
GoogleUtilities: 766ace00c6b10d8148408f329d10c4f051931850
GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
image_cropper: c4326ea50132b1e1564499e5d32a84f01fb03537
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
OBS: 4cf1e6db5515aefd6c3c6ae3e90e85ea5f028e83
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
PromisesObjC: 752c3227f599e3467650e47ea36f433eeb10c273
PromisesSwift: 217dea0fd5d2ad65222a109c48698add13cc1c5b
RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
... ... @@ -133,6 +186,6 @@ SPEC CHECKSUMS:
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
PODFILE CHECKSUM: 9846d4e0a8198893058beb0df941f3d2ecdcca2c
PODFILE CHECKSUM: fe3137a8e1c8e115fcac0a685d21e651b2d2788b
COCOAPODS: 1.16.2
... ...
... ... @@ -11,6 +11,7 @@ import Flutter
import UserNotifications
import AppTrackingTransparency
import AdSupport
import GoogleSignIn
typealias FlutterBridgeMethod = ((_ params: Any?, _ result: FlutterResult) -> Void)
... ... @@ -48,6 +49,9 @@ class AppDelegate: NSObject, UIApplicationDelegate {
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
if GIDSignIn.sharedInstance.handle(url) {
return true
}
handleFlutterUrl(url.absoluteString)
return true
}
... ...
... ... @@ -11,16 +11,25 @@ import AuthenticationServices
enum PlatformHostApiError: LocalizedError {
case deallocated
case invalidAppleCredential
case invalidGoogleCredential
case missingPresentationAnchor
case missingGoogleIDToken
case mailComposerAlreadyPresented
var errorDescription: String? {
switch self {
case .deallocated:
return "PlatformHostApiImpl was released before Apple sign-in started."
return "PlatformHostApiImpl was released before the native request started."
case .invalidAppleCredential:
return "Apple sign-in did not return an Apple ID credential."
case .invalidGoogleCredential:
return "Google sign-in did not return a Google user."
case .missingPresentationAnchor:
return "Unable to find a window for Apple sign-in presentation."
return "Unable to find a window for native presentation."
case .missingGoogleIDToken:
return "Google sign-in did not return an ID token."
case .mailComposerAlreadyPresented:
return "A mail composer is already being presented."
}
}
}
... ...
... ... @@ -11,6 +11,14 @@
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6</string>
</array>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
... ... @@ -26,8 +34,18 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlegmail</string>
<string>ms-outlook</string>
<string>ymail</string>
<string>readdle-spark</string>
<string>mqqapi</string>
</array>
<key>NSHealthShareUsageDescription</key>
<string>需要读取 Apple Health 中的心率、HRV、睡眠、步数、活动能量等数据,用于展示健康状态并同步到 Apple Watch。</string>
<key>GIDClientID</key>
<string>518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6.apps.googleusercontent.com</string>
<key>NSHealthUpdateUsageDescription</key>
<string>需要写入少量健康数据权限以保持与旧版 Apple Health 同步流程兼容。</string>
<key>NSPhotoLibraryUsageDescription</key>
... ...
... ... @@ -5,6 +5,12 @@
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
... ... @@ -20,6 +26,14 @@
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlegmail</string>
<string>ms-outlook</string>
<string>ymail</string>
<string>readdle-spark</string>
<string>mqqapi</string>
</array>
<key>NSHealthShareUsageDescription</key>
<string>需要读取 Apple Health 中的心率、HRV、睡眠、步数、活动能量等数据,用于展示健康状态并同步到 Apple Watch。</string>
<key>NSHealthUpdateUsageDescription</key>
... ... @@ -32,9 +46,11 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>需要相机拍摄用户头像</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要相机拍摄用户头像</string>
<key>NSCameraUsageDescription</key>
<string>需要相机拍摄用户头像</string>
<key>GIDClientID</key>
<string>518385058395-ja7eugmdu7ar8m8juddn7qjkqvjh5hp6.apps.googleusercontent.com</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要相机拍摄用户头像</string>
</dict>
</plist>
... ...
... ... @@ -155,6 +155,47 @@ struct AppleSignInModel: Hashable {
}
/// Generated class from Pigeon that represents data sent in messages.
struct GoogleSignInModel: Hashable {
var email: String? = nil
var clientID: String? = nil
var idToken: String
var nickname: String? = nil
var avatarUrl: String? = nil
// swift-format-ignore: AlwaysUseLowerCamelCase
static func fromList(_ pigeonVar_list: [Any?]) -> GoogleSignInModel? {
let email: String? = nilOrValue(pigeonVar_list[0])
let clientID: String? = nilOrValue(pigeonVar_list[1])
let idToken = pigeonVar_list[2] as! String
let nickname: String? = nilOrValue(pigeonVar_list[3])
let avatarUrl: String? = nilOrValue(pigeonVar_list[4])
return GoogleSignInModel(
email: email,
clientID: clientID,
idToken: idToken,
nickname: nickname,
avatarUrl: avatarUrl
)
}
func toList() -> [Any?] {
return [
email,
clientID,
idToken,
nickname,
avatarUrl,
]
}
static func == (lhs: GoogleSignInModel, rhs: GoogleSignInModel) -> Bool {
return deepEqualsPlatformApi(lhs.toList(), rhs.toList()) }
func hash(into hasher: inout Hasher) {
deepHashPlatformApi(value: toList(), hasher: &hasher)
}
}
/// Generated class from Pigeon that represents data sent in messages.
struct WatchAppOtherInfo: Hashable {
var userId: Int64? = nil
var nickname: String? = nil
... ... @@ -314,10 +355,12 @@ private class PlatformApiPigeonCodecReader: FlutterStandardReader {
case 130:
return AppleSignInModel.fromList(self.readValue() as! [Any?])
case 131:
return WatchAppOtherInfo.fromList(self.readValue() as! [Any?])
return GoogleSignInModel.fromList(self.readValue() as! [Any?])
case 132:
return AppleProductInfo.fromList(self.readValue() as! [Any?])
return WatchAppOtherInfo.fromList(self.readValue() as! [Any?])
case 133:
return AppleProductInfo.fromList(self.readValue() as! [Any?])
case 134:
return AppleProductPaymentResult.fromList(self.readValue() as! [Any?])
default:
return super.readValue(ofType: type)
... ... @@ -333,15 +376,18 @@ private class PlatformApiPigeonCodecWriter: FlutterStandardWriter {
} else if let value = value as? AppleSignInModel {
super.writeByte(130)
super.writeValue(value.toList())
} else if let value = value as? WatchAppOtherInfo {
} else if let value = value as? GoogleSignInModel {
super.writeByte(131)
super.writeValue(value.toList())
} else if let value = value as? AppleProductInfo {
} else if let value = value as? WatchAppOtherInfo {
super.writeByte(132)
super.writeValue(value.toList())
} else if let value = value as? AppleProductPaymentResult {
} else if let value = value as? AppleProductInfo {
super.writeByte(133)
super.writeValue(value.toList())
} else if let value = value as? AppleProductPaymentResult {
super.writeByte(134)
super.writeValue(value.toList())
} else {
super.writeValue(value)
}
... ... @@ -397,6 +443,10 @@ protocol PlatformHostApi {
func requestUnhandedUrl() throws -> String?
/// 请求苹果登录
func requestAppleSignIn(completion: @escaping (Result<AppleSignInModel?, Error>) -> Void)
/// google登录
func requestGoogleSignIn(completion: @escaping (Result<GoogleSignInModel?, Error>) -> Void)
/// 发送邮件
func sendEmail(mailTo: String, title: String, content: String, completion: @escaping (Result<Bool, Error>) -> Void)
/// 查询指定id的苹果商品
/// productId: 苹果商品id
/// baseUnit: 除数基础单位(多少个天、周、月) ->
... ... @@ -658,6 +708,42 @@ class PlatformHostApiSetup {
} else {
requestAppleSignInChannel.setMessageHandler(nil)
}
/// google登录
let requestGoogleSignInChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestGoogleSignIn\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
requestGoogleSignInChannel.setMessageHandler { _, reply in
api.requestGoogleSignIn { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
requestGoogleSignInChannel.setMessageHandler(nil)
}
/// 发送邮件
let sendEmailChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.sendEmail\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
if let api = api {
sendEmailChannel.setMessageHandler { message, reply in
let args = message as! [Any?]
let mailToArg = args[0] as! String
let titleArg = args[1] as! String
let contentArg = args[2] as! String
api.sendEmail(mailTo: mailToArg, title: titleArg, content: contentArg) { result in
switch result {
case .success(let res):
reply(wrapResult(res))
case .failure(let error):
reply(wrapError(error))
}
}
}
} else {
sendEmailChannel.setMessageHandler(nil)
}
/// 查询指定id的苹果商品
/// productId: 苹果商品id
/// baseUnit: 除数基础单位(多少个天、周、月) ->
... ...
... ... @@ -3,6 +3,8 @@ import AuthenticationServices
import StoreKit
import UIKit
import WebKit
import GoogleSignIn
import MessageUI
class PriceFormatter{
static func formatPrice(_ price: Decimal, currencyCode: String) -> String {
... ... @@ -46,7 +48,9 @@ extension WatchAppOtherInfo{
* 组装完整 User-Agent格式与 Android 端保持一致
* `{systemWebViewUA} doublefeel/{versionCode}({versionName})(Apple##Apple##{model}; iOS{osVersion}; {height}x{width})(huawei)`
*/
final class PlatformHostApiImpl: PlatformHostApi {
final class PlatformHostApiImpl: NSObject, PlatformHostApi {
private var sendEmailCompletion: ((Result<Bool, any Error>) -> Void)?
func isDebugEnvoriment() throws -> Bool {
#if DEBUG || CI_ENV
return true
... ... @@ -348,6 +352,124 @@ final class PlatformHostApiImpl: PlatformHostApi {
}
}
func requestGoogleSignIn(completion: @escaping (Result<GoogleSignInModel?, any Error>) -> Void) {
DispatchQueue.main.async {
guard let viewController = Self.topViewController() else {
print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(PlatformHostApiError.missingPresentationAnchor.localizedDescription)")
completion(.failure(PlatformHostApiError.missingPresentationAnchor))
return
}
GIDSignIn.sharedInstance.signIn(withPresenting: viewController) { signInResult, error in
if let error {
let nsError = error as NSError
if nsError.domain == kGIDSignInErrorDomain, nsError.code == -5 {
print("[PlatformHostApiImpl.requestGoogleSignIn] return: nil")
completion(.success(nil))
} else {
print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(error.localizedDescription)")
completion(.failure(error))
}
return
}
guard let user = signInResult?.user else {
print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(PlatformHostApiError.invalidGoogleCredential.localizedDescription)")
completion(.failure(PlatformHostApiError.invalidGoogleCredential))
return
}
guard let token = user.idToken?.tokenString else {
print("[PlatformHostApiImpl.requestGoogleSignIn] error: \(PlatformHostApiError.missingGoogleIDToken.localizedDescription)")
completion(.failure(PlatformHostApiError.missingGoogleIDToken))
return
}
let profilePicUrl = user.profile?.imageURL(withDimension: 240)
let model = GoogleSignInModel(
email: user.profile?.email,
clientID: user.configuration.clientID,
idToken: token,
nickname: user.profile?.name,
avatarUrl: profilePicUrl?.absoluteString
)
print("[PlatformHostApiImpl.requestGoogleSignIn] return: \(model)")
completion(.success(model))
}
}
}
func sendEmail(mailTo: String, title: String, content: String, completion: @escaping (Result<Bool, any Error>) -> Void) {
DispatchQueue.main.async { [weak self] in
guard let self else {
print("[PlatformHostApiImpl.sendEmail] error: \(PlatformHostApiError.deallocated.localizedDescription)")
completion(.failure(PlatformHostApiError.deallocated))
return
}
guard MFMailComposeViewController.canSendMail() else {
UIApplication.openEmailWithSchema(to: mailTo, subject: title, body: content) { opened in
print("[PlatformHostApiImpl.sendEmail] return: \(opened)")
completion(.success(opened))
}
return
}
guard sendEmailCompletion == nil else {
print("[PlatformHostApiImpl.sendEmail] error: \(PlatformHostApiError.mailComposerAlreadyPresented.localizedDescription)")
completion(.failure(PlatformHostApiError.mailComposerAlreadyPresented))
return
}
guard let viewController = Self.topViewController() else {
print("[PlatformHostApiImpl.sendEmail] error: \(PlatformHostApiError.missingPresentationAnchor.localizedDescription)")
completion(.failure(PlatformHostApiError.missingPresentationAnchor))
return
}
sendEmailCompletion = completion
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = self
mailVC.setToRecipients([mailTo])
mailVC.setSubject(title)
mailVC.setMessageBody(content, isHTML: false)
viewController.present(mailVC, animated: true)
}
}
}
extension PlatformHostApiImpl: MFMailComposeViewControllerDelegate {
func mailComposeController(
_ controller: MFMailComposeViewController,
didFinishWith result: MFMailComposeResult,
error: Error?
) {
controller.dismiss(animated: true) { [weak self] in
guard let self else { return }
let completion = self.sendEmailCompletion
self.sendEmailCompletion = nil
if let error {
print("[PlatformHostApiImpl.sendEmail] error: \(error.localizedDescription)")
completion?(.failure(error))
return
}
let success: Bool
switch result {
case .cancelled:
success = false
case .saved, .sent:
success = true
case .failed:
success = false
@unknown default:
success = false
}
print("[PlatformHostApiImpl.sendEmail] return: \(success)")
completion?(.success(success))
}
}
}
//MARK: - 分享
... ... @@ -419,3 +541,48 @@ extension PlatformHostApiImpl{
return rootViewController
}
}
extension UIApplication{
static func openEmailWithSchema(
to: String,
subject: String,
body: String,
completion: ((Bool) -> Void)? = nil
) {
if let url = createEmailUrl(to: to, subject: subject, body: body),
UIApplication.shared.canOpenURL(url){
UIApplication.shared.open(url, completionHandler: completion)
}else{
let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")!
UIApplication.shared.open(defaultUrl, completionHandler: completion)
}
}
static func createEmailUrl(to: String, subject: String, body: String) -> URL? {
let subjectEncoded = subject.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let bodyEncoded = body.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let gmailUrl = URL(string: "googlegmail://co?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let outlookUrl = URL(string: "ms-outlook://compose?to=\(to)&subject=\(subjectEncoded)")
let yahooMail = URL(string: "ymail://mail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let sparkUrl = URL(string: "readdle-spark://compose?recipient=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let qqEmail = URL(string: "mqqapi://composeemail/compose?to=\(to)&subject=\(subjectEncoded)&body=\(bodyEncoded)")
let defaultUrl = URL(string: "mailto:\(to)?subject=\(subjectEncoded)&body=\(bodyEncoded)")
if let gmailUrl = gmailUrl, UIApplication.shared.canOpenURL(gmailUrl) {
return gmailUrl
} else if let outlookUrl = outlookUrl, UIApplication.shared.canOpenURL(outlookUrl) {
return outlookUrl
} else if let yahooMail = yahooMail,UIApplication.shared.canOpenURL(yahooMail) {
return yahooMail
} else if let sparkUrl = sparkUrl, UIApplication.shared.canOpenURL(sparkUrl) {
return sparkUrl
}else if let qqEmail, UIApplication.shared.canOpenURL(qqEmail) {
return qqEmail
}
return defaultUrl
}
}
... ...
... ... @@ -91,6 +91,67 @@ class AppleSignInModel {
;
}
class GoogleSignInModel {
GoogleSignInModel({
this.email,
this.clientID,
required this.idToken,
this.nickname,
this.avatarUrl,
});
String? email;
String? clientID;
String idToken;
String? nickname;
String? avatarUrl;
List<Object?> _toList() {
return <Object?>[
email,
clientID,
idToken,
nickname,
avatarUrl,
];
}
Object encode() {
return _toList(); }
static GoogleSignInModel decode(Object result) {
result as List<Object?>;
return GoogleSignInModel(
email: result[0] as String?,
clientID: result[1] as String?,
idToken: result[2]! as String,
nickname: result[3] as String?,
avatarUrl: result[4] as String?,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (other is! GoogleSignInModel || other.runtimeType != runtimeType) {
return false;
}
if (identical(this, other)) {
return true;
}
return _deepEquals(encode(), other.encode());
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
int get hashCode => Object.hashAll(_toList())
;
}
class WatchAppOtherInfo {
WatchAppOtherInfo({
this.userId,
... ... @@ -316,15 +377,18 @@ class _PigeonCodec extends StandardMessageCodec {
} else if (value is AppleSignInModel) {
buffer.putUint8(130);
writeValue(buffer, value.encode());
} else if (value is WatchAppOtherInfo) {
} else if (value is GoogleSignInModel) {
buffer.putUint8(131);
writeValue(buffer, value.encode());
} else if (value is AppleProductInfo) {
} else if (value is WatchAppOtherInfo) {
buffer.putUint8(132);
writeValue(buffer, value.encode());
} else if (value is AppleProductPaymentResult) {
} else if (value is AppleProductInfo) {
buffer.putUint8(133);
writeValue(buffer, value.encode());
} else if (value is AppleProductPaymentResult) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else {
super.writeValue(buffer, value);
}
... ... @@ -339,10 +403,12 @@ class _PigeonCodec extends StandardMessageCodec {
case 130:
return AppleSignInModel.decode(readValue(buffer)!);
case 131:
return WatchAppOtherInfo.decode(readValue(buffer)!);
return GoogleSignInModel.decode(readValue(buffer)!);
case 132:
return AppleProductInfo.decode(readValue(buffer)!);
return WatchAppOtherInfo.decode(readValue(buffer)!);
case 133:
return AppleProductInfo.decode(readValue(buffer)!);
case 134:
return AppleProductPaymentResult.decode(readValue(buffer)!);
default:
return super.readValueOfType(type, buffer);
... ... @@ -770,6 +836,59 @@ class PlatformHostApi {
}
}
/// google登录
Future<GoogleSignInModel?> requestGoogleSignIn() async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.requestGoogleSignIn$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else {
return (pigeonVar_replyList[0] as GoogleSignInModel?);
}
}
/// 发送邮件
Future<bool> sendEmail(String mailTo, String title, String content) async {
final String pigeonVar_channelName = 'dev.flutter.pigeon.doublefeel_flutter.PlatformHostApi.sendEmail$pigeonVar_messageChannelSuffix';
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
pigeonVar_channelName,
pigeonChannelCodec,
binaryMessenger: pigeonVar_binaryMessenger,
);
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[mailTo, title, content]);
final List<Object?>? pigeonVar_replyList =
await pigeonVar_sendFuture as List<Object?>?;
if (pigeonVar_replyList == null) {
throw _createConnectionError(pigeonVar_channelName);
} else if (pigeonVar_replyList.length > 1) {
throw PlatformException(
code: pigeonVar_replyList[0]! as String,
message: pigeonVar_replyList[1] as String?,
details: pigeonVar_replyList[2],
);
} else if (pigeonVar_replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (pigeonVar_replyList[0] as bool?)!;
}
}
/// 查询指定id的苹果商品
/// productId: 苹果商品id
/// baseUnit: 除数基础单位(多少个天、周、月) ->
... ...
... ... @@ -13,6 +13,13 @@ class AppleSignInModel {
this.identityToken,
});
}
class GoogleSignInModel{
final String? email;
final String? clientID;
final String idToken;
final String? nickname;
final String? avatarUrl;
}
class WatchAppOtherInfo {
final int? userId;
... ... @@ -163,6 +170,13 @@ abstract class PlatformHostApi {
/// 请求苹果登录
@async
AppleSignInModel? requestAppleSignIn();
/// google登录
@async
GoogleSignInModel? requestGoogleSignIn();
/// 发送邮件
@async
bool sendEmail(String mailTo, String title, String content);
/// 查询指定id的苹果商品
/// productId: 苹果商品id
... ...