增加push功能。
This commit is contained in:
@ -26,6 +26,7 @@
|
||||
550B85A22C2BC624008834E5 /* InitAPP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550B85A12C2BC623008834E5 /* InitAPP.swift */; };
|
||||
551C8C342C79946700B1A88C /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 551C8C332C79946700B1A88C /* GoogleService-Info.plist */; };
|
||||
555027392C81C0ED00A05441 /* QCloudTTS.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 555027382C81C0ED00A05441 /* QCloudTTS.xcframework */; };
|
||||
5550273C2C8322F800A05441 /* PushHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5550273B2C8322F800A05441 /* PushHandler.swift */; };
|
||||
5586E0882C80AD2D00026733 /* TTSManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5586E0872C80AD2D00026733 /* TTSManager.swift */; };
|
||||
559E6D7C2C34EAE700C971B9 /* IapManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559E6D7B2C34EAE700C971B9 /* IapManager.swift */; };
|
||||
559E6D7E2C35355200C971B9 /* LogManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 559E6D7D2C35355200C971B9 /* LogManager.swift */; };
|
||||
@ -91,6 +92,8 @@
|
||||
550B85A12C2BC623008834E5 /* InitAPP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitAPP.swift; sourceTree = "<group>"; };
|
||||
551C8C332C79946700B1A88C /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
555027382C81C0ED00A05441 /* QCloudTTS.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = QCloudTTS.xcframework; sourceTree = "<group>"; };
|
||||
5550273A2C8311CB00A05441 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
5550273B2C8322F800A05441 /* PushHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushHandler.swift; sourceTree = "<group>"; };
|
||||
5586E0832C8092C400026733 /* AIGrammar-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AIGrammar-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
5586E0872C80AD2D00026733 /* TTSManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TTSManager.swift; sourceTree = "<group>"; };
|
||||
559E6D7B2C34EAE700C971B9 /* IapManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IapManager.swift; sourceTree = "<group>"; };
|
||||
@ -183,6 +186,7 @@
|
||||
5500A38E2BB3C7E80065A1D3 /* AIGrammar */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5550273A2C8311CB00A05441 /* Info.plist */,
|
||||
5586E0842C8093DF00026733 /* third-party */,
|
||||
5586E0832C8092C400026733 /* AIGrammar-Bridging-Header.h */,
|
||||
55BC47472C3A380C00120A7D /* CommView */,
|
||||
@ -249,6 +253,7 @@
|
||||
559E6D7D2C35355200C971B9 /* LogManager.swift */,
|
||||
55E4E8F42C60CFFC00988503 /* CommFunc.swift */,
|
||||
5586E0872C80AD2D00026733 /* TTSManager.swift */,
|
||||
5550273B2C8322F800A05441 /* PushHandler.swift */,
|
||||
);
|
||||
path = lib;
|
||||
sourceTree = "<group>";
|
||||
@ -559,6 +564,7 @@
|
||||
5500A3C42BB40AC40065A1D3 /* WordsView.swift in Sources */,
|
||||
5509CEF12BB54DD10056C5C2 /* Config.swift in Sources */,
|
||||
55BB127B2BBD653100D2BEA4 /* ShareSheet.swift in Sources */,
|
||||
5550273C2C8322F800A05441 /* PushHandler.swift in Sources */,
|
||||
5500A3C82BB40ADE0065A1D3 /* SettingsView.swift in Sources */,
|
||||
55E4E8F52C60CFFC00988503 /* CommFunc.swift in Sources */,
|
||||
55DAC6552BBA959500BDD4C8 /* ResultView.swift in Sources */,
|
||||
@ -750,6 +756,7 @@
|
||||
"$(PROJECT_DIR)/AIGrammar/third-party/ios-arm64_i386_x86_64-simulator",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = AIGrammar/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = EasyGrammar;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera access is required to capture text for grammar and spelling correction.";
|
||||
@ -873,6 +880,7 @@
|
||||
"$(PROJECT_DIR)/AIGrammar/third-party/ios-arm64_i386_x86_64-simulator",
|
||||
);
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = AIGrammar/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = EasyGrammar;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.education";
|
||||
INFOPLIST_KEY_NSCameraUsageDescription = "Camera access is required to capture text for grammar and spelling correction.";
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -8,15 +8,24 @@
|
||||
import SwiftUI
|
||||
import TrustDecision
|
||||
|
||||
import Combine
|
||||
import Firebase
|
||||
import FirebaseAnalytics
|
||||
import FirebaseCrashlytics
|
||||
|
||||
@main
|
||||
struct AIGrammarApp: App {
|
||||
let persistenceController = PersistenceController.shared
|
||||
|
||||
// 获取 AppDelegate 的实例
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
let persistenceController = PersistenceController.shared
|
||||
@StateObject private var appState = NofifyState()
|
||||
@State private var urlToOpen: String?
|
||||
|
||||
init() {
|
||||
// 将当前实例传递给 AppDelegate
|
||||
appDelegate.app = self
|
||||
|
||||
// 初始化部分
|
||||
setupLogging()
|
||||
_ = InitApp.shared
|
||||
@ -33,12 +42,68 @@ struct AIGrammarApp: App {
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
AllTabView()
|
||||
AllTabView(selectedTab: $appState.selectedTab, showPromotion: $appState.showPromotion, promotionMode: $appState.promotionMode)
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
.environmentObject(IAPManager()) // 这里添加 IAPManager
|
||||
.environmentObject(globalEnvironment) // 这里添加 IAPManager
|
||||
.environmentObject(appState)
|
||||
.preferredColorScheme(.light) // 强制整个应用使用亮色模式
|
||||
.sheet(isPresented: $appState.showPromotion) {
|
||||
switch appState.promotionMode {
|
||||
case .halfScreen:
|
||||
PromotionView() // 半屏模式
|
||||
.presentationDetents([.medium])
|
||||
case .fullScreen:
|
||||
PromotionView() // 全屏模式
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.onChange(of: urlToOpen) { url in
|
||||
if let url = url, let link = URL(string: url) {
|
||||
UIApplication.shared.open(link)
|
||||
}
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
||||
handlePushNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 这个是在app外打开链接,实际上不应该用到。
|
||||
func openURL(urlString: String) {
|
||||
urlToOpen = urlString
|
||||
}
|
||||
|
||||
func handlePushNotification() {
|
||||
// 确保在主线程中更新视图
|
||||
DispatchQueue.main.async {
|
||||
let pushInfo = globalEnvironment.pushSettings
|
||||
// 切换到对应的tab
|
||||
if pushInfo.gotoTab >= 0 && pushInfo.gotoTab < 4 {
|
||||
self.appState.selectedTab = pushInfo.gotoTab
|
||||
}else {
|
||||
logger.info("invalid goto Tab: \(pushInfo.gotoTab)")
|
||||
}
|
||||
// 是否显示本地页面
|
||||
if pushInfo.showPage && pushInfo.page != "" {
|
||||
self.appState.showPromotion = true
|
||||
switch pushInfo.showMode {
|
||||
case "halfScreen" :
|
||||
self.appState.promotionMode = PromotionDisplayType.halfScreen
|
||||
case "fullScreen" :
|
||||
self.appState.promotionMode = PromotionDisplayType.fullScreen
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
// 是否显示远程页面
|
||||
if pushInfo.showPage && pushInfo.openURL != "" {
|
||||
urlToOpen = pushInfo.openURL
|
||||
}
|
||||
|
||||
logger.info("Push Values. gotoTab: \(pushInfo.gotoTab), showPage: \(pushInfo.showPage), page: \(pushInfo.page), showMode: \(pushInfo.showMode), url: \(pushInfo.openURL)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,36 +9,58 @@ import SwiftUI
|
||||
|
||||
|
||||
struct AllTabView: View {
|
||||
// 当前正在展示的tab
|
||||
@Binding var selectedTab: Int
|
||||
@Binding var showPromotion: Bool
|
||||
@Binding var promotionMode: PromotionDisplayType
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
TabView(selection: $selectedTab) {
|
||||
GrammarCheckView()
|
||||
.tabItem {
|
||||
Image(systemName: "book.fill")
|
||||
Text("Grammar Check")
|
||||
}
|
||||
.tag(0)
|
||||
|
||||
WordsView()
|
||||
.tabItem {
|
||||
Image(systemName: "text.bubble")
|
||||
Text("Words")
|
||||
}
|
||||
.tag(1)
|
||||
|
||||
TranslateView()
|
||||
.tabItem {
|
||||
Image(systemName: "globe")
|
||||
Text("Translate")
|
||||
}
|
||||
.tag(2)
|
||||
|
||||
SettingsView()
|
||||
.tabItem {
|
||||
Image(systemName: "gear")
|
||||
Text("Settings")
|
||||
}
|
||||
.tag(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AllTabView_Preview: View{
|
||||
@State private var selectedTab = 2
|
||||
@State private var showPromotion = false
|
||||
@State private var promotionMode: PromotionDisplayType = .halfScreen
|
||||
@State private var urlToOpen: String?
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
AllTabView(selectedTab: $selectedTab, showPromotion: $showPromotion, promotionMode: $promotionMode)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AllTabView()
|
||||
AllTabView_Preview()
|
||||
}
|
||||
|
||||
@ -83,7 +83,16 @@ class GlobalEnvironment: ObservableObject {
|
||||
logger.info("baseHost: \(self.baseHost)")
|
||||
// 以后定义SandBox的功能,主要是商品列表的区分。
|
||||
}
|
||||
|
||||
// 使用结构体组织相关的设置
|
||||
@Published var pushSettings: PushInfo = PushInfo()
|
||||
struct PushInfo{
|
||||
var gotoTab: Int = 0
|
||||
var showPage: Bool = false
|
||||
var page: String = ""
|
||||
var showMode: String = ""
|
||||
var openURL: String = ""
|
||||
var appAtFront : Bool = false
|
||||
}
|
||||
}
|
||||
|
||||
// 全局实例
|
||||
|
||||
118
AIGrammar/lib/PushHandler.swift
Normal file
118
AIGrammar/lib/PushHandler.swift
Normal file
@ -0,0 +1,118 @@
|
||||
//
|
||||
// PushHandler.swift
|
||||
// AIGrammar
|
||||
//
|
||||
// Created by oscar on 2024/8/31.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
|
||||
enum PromotionDisplayType {
|
||||
case halfScreen
|
||||
case fullScreen
|
||||
case urlLink(String) // URL 链接,可以传递 URL 字符串
|
||||
}
|
||||
|
||||
class NofifyState: ObservableObject {
|
||||
@Published var selectedTab: Int = 0
|
||||
@Published var showPromotion: Bool = false
|
||||
@Published var promotionMode: PromotionDisplayType = .halfScreen
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
// 保存 AIGrammarApp 的实例
|
||||
var app: AIGrammarApp?
|
||||
|
||||
var window: UIWindow?
|
||||
var rootView: ContentView?
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
// 设置 UNUserNotificationCenter 的 delegate
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
|
||||
// 注册推送通知
|
||||
registerForPushNotifications()
|
||||
return true
|
||||
}
|
||||
|
||||
func registerForPushNotifications() {
|
||||
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
||||
logger.info("Permission granted: \(granted)")
|
||||
guard granted else { return }
|
||||
self.getNotificationSettings()
|
||||
}
|
||||
}
|
||||
|
||||
func getNotificationSettings() {
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
logger.info("Notification settings: \(settings)")
|
||||
guard settings.authorizationStatus == .authorized else { return }
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.registerForRemoteNotifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
|
||||
let token = tokenParts.joined()
|
||||
logger.info("Device Token: \(token)")
|
||||
// 你可以在此处将 token 发送到你的服务器
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||
logger.error("Failed to register: \(error)")
|
||||
}
|
||||
|
||||
/*
|
||||
// 处理前台接收推送通知
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
logger.info("get push msg in willPresent")
|
||||
globalEnvironment.pushSettings.appAtFront = true
|
||||
//completionHandler([.sound, .banner])
|
||||
}
|
||||
*/
|
||||
|
||||
// 处理用户响应推送通知
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
logger.info("get push msg in didReceive")
|
||||
let userInfo = response.notification.request.content.userInfo
|
||||
|
||||
// 解析相关的参数,保存到全局变量中。等用户唤起主界面后执行
|
||||
globalEnvironment.pushSettings.gotoTab = extractValue(from: userInfo, key: "gotoTab", defaultValue: 0, logMessage: "gotoTab not found or invalid in userInfo")
|
||||
globalEnvironment.pushSettings.showPage = extractValue(from: userInfo, key: "showPage", defaultValue: 0, logMessage: "showPage not found or invalid in userInfo") != 0
|
||||
globalEnvironment.pushSettings.page = extractValue(from: userInfo, key: "page", defaultValue: "", logMessage: "page not found or invalid in userInfo")
|
||||
globalEnvironment.pushSettings.showMode = extractValue(from: userInfo, key: "showMode", defaultValue: "", logMessage: "showMode not found or invalid in userInfo")
|
||||
globalEnvironment.pushSettings.openURL = extractValue(from: userInfo, key: "url", defaultValue: "", logMessage: "url not found or invalid in userInfo")
|
||||
|
||||
// 如果推送过来的时候,已经在前台了,需要触发一下
|
||||
if globalEnvironment.pushSettings.appAtFront {
|
||||
DispatchQueue.main.async {
|
||||
self.app?.handlePushNotification()
|
||||
}
|
||||
globalEnvironment.pushSettings.appAtFront = false
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
// 解析 userInfo 的通用函数
|
||||
func extractValue<T>(from userInfo: [AnyHashable: Any], key: String, defaultValue: T, logMessage: String) -> T {
|
||||
if let value = userInfo[key] as? T {
|
||||
return value
|
||||
} else {
|
||||
logger.info(logMessage)
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct PromotionView: View {
|
||||
var body: some View {
|
||||
Text("This is the Promotion View")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user