增加push功能。

This commit is contained in:
oscarz
2024-09-10 16:39:30 +08:00
parent 5b3b2f4a5f
commit 434df13a41
6 changed files with 232 additions and 7 deletions
+4 -1
View File
@@ -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>
+68 -3
View File
@@ -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)")
}
}
}
+24 -2
View File
@@ -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()
}
+10 -1
View File
@@ -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
View 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")
}
}