|
...
|
...
|
@@ -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
|
|
|
|
}
|
|
|
|
} |
...
|
...
|
|