diff --git a/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj b/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj index 408343a..de97d7e 100644 --- a/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj +++ b/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj @@ -12,6 +12,18 @@ AC8826F32E6F5A6700ECEF0D /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = AC8826F22E6F5A6700ECEF0D /* CropViewController */; }; AC8826F52E6F5A6700ECEF0D /* TOCropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = AC8826F42E6F5A6700ECEF0D /* TOCropViewController */; }; ACAC2DEB2D9F2C1900869E5C /* CalendarKit in Frameworks */ = {isa = PBXBuildFile; productRef = ACAC2DEA2D9F2C1900869E5C /* CalendarKit */; }; + ACF3DEC52EC490D000E72587 /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DEC42EC490D000E72587 /* FirebaseAuth */; }; + ACF3DEC72EC490D000E72587 /* FirebaseCore in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DEC62EC490D000E72587 /* FirebaseCore */; }; + ACF3DEC92EC490D000E72587 /* FirebaseInAppMessaging-Beta in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DEC82EC490D000E72587 /* FirebaseInAppMessaging-Beta */; }; + ACF3DECB2EC490D000E72587 /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DECA2EC490D000E72587 /* FirebaseMessaging */; }; + ACF3DECD2EC4917600E72587 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DECC2EC4917600E72587 /* FirebaseAnalytics */; }; + ACF3DECF2EC4917600E72587 /* FirebaseAnalyticsCore in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DECE2EC4917600E72587 /* FirebaseAnalyticsCore */; }; + ACF3DED12EC4917600E72587 /* FirebaseAuthCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DED02EC4917600E72587 /* FirebaseAuthCombine-Community */; }; + ACF3DED32EC4917600E72587 /* FirebaseDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DED22EC4917600E72587 /* FirebaseDatabase */; }; + ACF3DED52EC4917600E72587 /* FirebaseFirestoreCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DED42EC4917600E72587 /* FirebaseFirestoreCombine-Community */; }; + ACF3DED72EC4917600E72587 /* FirebaseFunctionsCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DED62EC4917600E72587 /* FirebaseFunctionsCombine-Community */; }; + ACF3DED92EC4917600E72587 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DED82EC4917600E72587 /* FirebaseStorage */; }; + ACF3DEDB2EC4917600E72587 /* FirebaseStorageCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = ACF3DEDA2EC4917600E72587 /* FirebaseStorageCombine-Community */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -31,11 +43,23 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + ACF3DED32EC4917600E72587 /* FirebaseDatabase in Frameworks */, + ACF3DECB2EC490D000E72587 /* FirebaseMessaging in Frameworks */, AC136A092DB96847009D478F /* ObjectMapper in Frameworks */, + ACF3DEC52EC490D000E72587 /* FirebaseAuth in Frameworks */, AC8826F52E6F5A6700ECEF0D /* TOCropViewController in Frameworks */, + ACF3DED92EC4917600E72587 /* FirebaseStorage in Frameworks */, + ACF3DEDB2EC4917600E72587 /* FirebaseStorageCombine-Community in Frameworks */, + ACF3DEC92EC490D000E72587 /* FirebaseInAppMessaging-Beta in Frameworks */, AC8826F32E6F5A6700ECEF0D /* CropViewController in Frameworks */, + ACF3DEC72EC490D000E72587 /* FirebaseCore in Frameworks */, + ACF3DED72EC4917600E72587 /* FirebaseFunctionsCombine-Community in Frameworks */, + ACF3DED12EC4917600E72587 /* FirebaseAuthCombine-Community in Frameworks */, + ACF3DECF2EC4917600E72587 /* FirebaseAnalyticsCore in Frameworks */, ACAC2DEB2D9F2C1900869E5C /* CalendarKit in Frameworks */, + ACF3DECD2EC4917600E72587 /* FirebaseAnalytics in Frameworks */, AC53DEAA2DB9476D003445AD /* Alamofire in Frameworks */, + ACF3DED52EC4917600E72587 /* FirebaseFirestoreCombine-Community in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -46,6 +70,7 @@ isa = PBXGroup; children = ( ACAC2DD12D9F271300869E5C /* Club Portal */, + ACF3DEDC2EC493E800E72587 /* Frameworks */, ACAC2DD02D9F271300869E5C /* Products */, ); sourceTree = ""; @@ -58,6 +83,13 @@ name = Products; sourceTree = ""; }; + ACF3DEDC2EC493E800E72587 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -83,6 +115,18 @@ AC136A082DB96847009D478F /* ObjectMapper */, AC8826F22E6F5A6700ECEF0D /* CropViewController */, AC8826F42E6F5A6700ECEF0D /* TOCropViewController */, + ACF3DEC42EC490D000E72587 /* FirebaseAuth */, + ACF3DEC62EC490D000E72587 /* FirebaseCore */, + ACF3DEC82EC490D000E72587 /* FirebaseInAppMessaging-Beta */, + ACF3DECA2EC490D000E72587 /* FirebaseMessaging */, + ACF3DECC2EC4917600E72587 /* FirebaseAnalytics */, + ACF3DECE2EC4917600E72587 /* FirebaseAnalyticsCore */, + ACF3DED02EC4917600E72587 /* FirebaseAuthCombine-Community */, + ACF3DED22EC4917600E72587 /* FirebaseDatabase */, + ACF3DED42EC4917600E72587 /* FirebaseFirestoreCombine-Community */, + ACF3DED62EC4917600E72587 /* FirebaseFunctionsCombine-Community */, + ACF3DED82EC4917600E72587 /* FirebaseStorage */, + ACF3DEDA2EC4917600E72587 /* FirebaseStorageCombine-Community */, ); productName = "Club Portal"; productReference = ACAC2DCF2D9F271300869E5C /* Club Portal.app */; @@ -117,6 +161,7 @@ AC53DEA82DB9476D003445AD /* XCRemoteSwiftPackageReference "Alamofire" */, AC136A072DB96847009D478F /* XCRemoteSwiftPackageReference "ObjectMapper" */, AC8826F12E6F5A6700ECEF0D /* XCRemoteSwiftPackageReference "TOCropViewController" */, + ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); preferredProjectObjectVersion = 77; productRefGroup = ACAC2DD02D9F271300869E5C /* Products */; @@ -273,6 +318,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Club Portal/Club Portal.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\""; @@ -308,6 +354,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "Club Portal/Club Portal.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\""; @@ -394,6 +441,14 @@ minimumVersion = 1.1.11; }; }; + ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 12.5.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -422,6 +477,66 @@ package = ACAC2DE92D9F2C1900869E5C /* XCRemoteSwiftPackageReference "CalendarKit" */; productName = CalendarKit; }; + ACF3DEC42EC490D000E72587 /* FirebaseAuth */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAuth; + }; + ACF3DEC62EC490D000E72587 /* FirebaseCore */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCore; + }; + ACF3DEC82EC490D000E72587 /* FirebaseInAppMessaging-Beta */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = "FirebaseInAppMessaging-Beta"; + }; + ACF3DECA2EC490D000E72587 /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; + ACF3DECC2EC4917600E72587 /* FirebaseAnalytics */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalytics; + }; + ACF3DECE2EC4917600E72587 /* FirebaseAnalyticsCore */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalyticsCore; + }; + ACF3DED02EC4917600E72587 /* FirebaseAuthCombine-Community */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = "FirebaseAuthCombine-Community"; + }; + ACF3DED22EC4917600E72587 /* FirebaseDatabase */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseDatabase; + }; + ACF3DED42EC4917600E72587 /* FirebaseFirestoreCombine-Community */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = "FirebaseFirestoreCombine-Community"; + }; + ACF3DED62EC4917600E72587 /* FirebaseFunctionsCombine-Community */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = "FirebaseFunctionsCombine-Community"; + }; + ACF3DED82EC4917600E72587 /* FirebaseStorage */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseStorage; + }; + ACF3DEDA2EC4917600E72587 /* FirebaseStorageCombine-Community */ = { + isa = XCSwiftPackageProductDependency; + package = ACF3DEC32EC48ECD00E72587 /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = "FirebaseStorageCombine-Community"; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = ACAC2DC72D9F271300869E5C /* Project object */; diff --git a/Club_portal/Club Portal/Club Portal.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Club_portal/Club Portal/Club Portal.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1b06acb..7dfe610 100644 --- a/Club_portal/Club Portal/Club Portal.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Club_portal/Club Portal/Club Portal.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "b1b0353358422b4fa2ea090c1b3ed8788cceebbcf36fc271f832e564b41e6da4", + "originHash" : "28827aa8b1b4853bac5668c904753f1251153146a70f33dbdbae6b7398bba144", "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5", + "version" : "1.2024072200.0" + } + }, { "identity" : "alamofire", "kind" : "remoteSourceControl", @@ -10,6 +19,15 @@ "version" : "5.10.2" } }, + { + "identity" : "app-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/app-check.git", + "state" : { + "revision" : "61b85103a1aeed8218f17c794687781505fbbef5", + "version" : "11.2.0" + } + }, { "identity" : "calendarkit", "kind" : "remoteSourceControl", @@ -19,6 +37,96 @@ "version" : "1.1.11" } }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk.git", + "state" : { + "revision" : "793b67f4652e1a39d03fab6650033768afe6d15e", + "version" : "12.5.0" + } + }, + { + "identity" : "google-ads-on-device-conversion-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/googleads/google-ads-on-device-conversion-ios-sdk", + "state" : { + "revision" : "35b601a60fbbea2de3ea461f604deaaa4d8bbd0c", + "version" : "3.2.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "c2d59acf17a8ba7ed80a763593c67c9c7c006ad1", + "version" : "12.5.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "617af071af9aa1d6a091d59a202910ac482128f9", + "version" : "10.1.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "60da361632d0de02786f709bdc0c4df340f7613e", + "version" : "8.1.0" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "75b31c842f664a0f46a2e590a570e370249fd8f6", + "version" : "1.69.1" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "fb7f2740b1570d2f7599c6bb9531bf4fad6974b7", + "version" : "5.0.0" + } + }, + { + "identity" : "interop-ios-for-google-sdks", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/interop-ios-for-google-sdks.git", + "state" : { + "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe", + "version" : "101.0.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", + "version" : "1.22.5" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", + "version" : "2.30910.0" + } + }, { "identity" : "objectmapper", "kind" : "remoteSourceControl", @@ -28,6 +136,24 @@ "version" : "4.4.3" } }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", + "version" : "2.4.0" + } + }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "c169a5744230951031770e27e475ff6eefe51f9d", + "version" : "1.33.3" + } + }, { "identity" : "tocropviewcontroller", "kind" : "remoteSourceControl", diff --git a/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 39e3c49..4f062a3 100644 --- a/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -50,6 +50,36 @@ endingLineNumber = "74" landmarkName = "body" landmarkType = "24"> + + + + + + + + + + + + + + + + + + + + + + diff --git a/Club_portal/Club Portal/Club Portal/Club Portal.entitlements b/Club_portal/Club Portal/Club Portal/Club Portal.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/Club_portal/Club Portal/Club Portal/Club Portal.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/Club_portal/Club Portal/Club Portal/GoogleService-Info.plist b/Club_portal/Club Portal/Club Portal/GoogleService-Info.plist new file mode 100644 index 0000000..6cbed47 --- /dev/null +++ b/Club_portal/Club Portal/Club Portal/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyBdzkpF0c0ZFyh3Js-K8wEOPR5sHnNV--8 + GCM_SENDER_ID + 1016302979918 + PLIST_VERSION + 1 + BUNDLE_ID + com.app.Club-Portal + PROJECT_ID + court-matchup-aa3e3 + STORAGE_BUCKET + court-matchup-aa3e3.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:1016302979918:ios:10e9be076f05b83c80ac1f + + \ No newline at end of file diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/PushNotificationsEndpoints.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/PushNotificationsEndpoints.swift new file mode 100644 index 0000000..0649ebd --- /dev/null +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/PushNotificationsEndpoints.swift @@ -0,0 +1,64 @@ +import Foundation + +// MARK: - Register Device Endpoint +struct RegisterDeviceEndpoint: Endpoint { + var urlQueryItems: [URLQueryItem] = [] + var customHeaders: [String: String]? { nil } + var isMultipart: Bool + + + let deviceToken: String + let deviceId: String + let deviceName: String + let appVersion: String + + var path: String { + return "/v3/api/custom/courtmatchup/push/register-device" + } + + var method: HTTPMethod { + return .post + } + + var headers: [String: String]? { + return ["Content-Type": "application/json"] + } + + var body: Data? { + let params: [String: Any] = [ + "device_token": deviceToken, + "platform": "ios", + "device_id": deviceId, + "device_name": deviceName, + "app_version": appVersion + ] + return try? JSONSerialization.data(withJSONObject: params) + } +} + +// MARK: - Unregister Device Endpoint +struct UnregisterDeviceEndpoint: Endpoint { + var urlQueryItems: [URLQueryItem] = [] + var customHeaders: [String: String]? { nil } + var isMultipart: Bool + + + let deviceToken: String + + var path: String { + return "/v3/api/custom/courtmatchup/push/unregister-device" + } + + var method: HTTPMethod { + return .post + } + + var headers: [String: String]? { + return ["Content-Type": "application/json"] + } + + var body: Data? { + let params: [String: String] = ["device_token": deviceToken] + return try? JSONSerialization.data(withJSONObject: params) + } +} diff --git a/Club_portal/Club Portal/Club Portal/Services/NotificationService.swift b/Club_portal/Club Portal/Club Portal/Services/NotificationService.swift new file mode 100644 index 0000000..ec73eeb --- /dev/null +++ b/Club_portal/Club Portal/Club Portal/Services/NotificationService.swift @@ -0,0 +1,148 @@ +import Foundation +import SwiftUI +import FirebaseCore +import FirebaseMessaging +import UserNotifications + +// MARK: - Empty Response Model +struct EmptyResponse: Codable { + // This is a placeholder for API responses that don't return any data +} + +class NotificationService: NSObject, ObservableObject { + static let shared = NotificationService() + @Published var fcmToken: String = "" + + func requestNotificationPermission() { + UNUserNotificationCenter.current().delegate = self + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + + UNUserNotificationCenter.current().getNotificationSettings { settings in + if settings.authorizationStatus == .notDetermined { + // Request permission if not determined + UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { granted, error in + if let error = error { + print("Error requesting notification permission: \(error.localizedDescription)") + return + } + + if granted { + print("Notification permission granted") + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } else { + print("Notification permission denied") + } + } + } else if settings.authorizationStatus == .authorized { + // Already authorized, register for remote notifications + DispatchQueue.main.async { + UIApplication.shared.registerForRemoteNotifications() + } + } + } + } + + func updateFCMToken() { + Messaging.messaging().token { [weak self] token, error in + if let error = error { + print("Error fetching FCM token: \(error.localizedDescription)") + return + } + + if let token = token { + print("FCM Token: \(token)") + self?.fcmToken = token + AppSettings.fcm = token + // TODO: Send this token to your backend + self?.sendTokenToServer(token: token) + } + } + } + + private func sendTokenToServer(token: String) { + let deviceId = UIDevice.current.identifierForVendor?.uuidString ?? "" + let deviceName = UIDevice.current.name + let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0" + + let endpoint = RegisterDeviceEndpoint( + isMultipart: false, + deviceToken: token, + deviceId: deviceId, + deviceName: deviceName, + appVersion: appVersion + ) + + Task { + do { + _ = try await APIService.shared.request(endpoint, responseType: EmptyResponse.self) + print("Successfully registered FCM token with server") + } catch { + print("Failed to register FCM token: \(error.localizedDescription)") + } + } + } + + func unregisterDevice(completion: (() -> Void)? = nil) { + guard !fcmToken.isEmpty else { + completion?() + return + } + + let endpoint = UnregisterDeviceEndpoint(isMultipart: false, deviceToken: fcmToken) + + Task { + do { + _ = try await APIService.shared.request(endpoint, responseType: EmptyResponse.self) + print("Successfully unregistered FCM token from server") + } catch { + print("Failed to unregister FCM token: \(error.localizedDescription)") + } + completion?() + } + } +} + +// MARK: - UNUserNotificationCenterDelegate +extension NotificationService: UNUserNotificationCenterDelegate { + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + let userInfo = notification.request.content.userInfo + print("Received notification: \(userInfo)") + + // Show the notification even when the app is in the foreground + completionHandler([.banner, .sound, .badge]) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + print("User tapped on notification: \(userInfo)") + + // Handle the notification tap here + handleNotification(userInfo: userInfo) + + completionHandler() + } + + private func handleNotification(userInfo: [AnyHashable: Any]) { + // Handle the notification data here + // You can navigate to specific screens based on the notification data + print("Handling notification with data: \(userInfo)") + } +} + +// MARK: - MessagingDelegate +extension NotificationService: MessagingDelegate { + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + print("Firebase registration token: \(String(describing: fcmToken))") + + if let token = fcmToken { + self.fcmToken = token + sendTokenToServer(token: token) + } + } +} diff --git a/Club_portal/Club Portal/Club Portal/UI/Main/Club_PortalApp.swift b/Club_portal/Club Portal/Club Portal/UI/Main/Club_PortalApp.swift index e7de98a..23762c4 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Main/Club_PortalApp.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Main/Club_PortalApp.swift @@ -6,10 +6,15 @@ // import SwiftUI - +import FirebaseCore +import FirebaseMessaging +import UIKit @main struct Club_PortalApp: App { - + @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate + + // Initialize notification service + @StateObject var notificationService = NotificationService() var body: some Scene { WindowGroup { WelcomeView() @@ -21,3 +26,46 @@ struct Club_PortalApp: App { } } + + + +class AppDelegate: NSObject, UIApplicationDelegate { + let gcmMessageIDKey = "gcm.message_id" + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + // Configure Firebase + FirebaseApp.configure() + + // Set up notifications + UNUserNotificationCenter.current().delegate = NotificationService.shared + Messaging.messaging().delegate = NotificationService.shared + + // Request notification permissions + NotificationService.shared.requestNotificationPermission() + + // Register for remote notifications + application.registerForRemoteNotifications() + + return true + } + + // Handle remote notification registration + func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + Messaging.messaging().apnsToken = deviceToken + NotificationService.shared.updateFCMToken() + } + + // Handle notification tap when app is in background or terminated + func application(_ application: UIApplication, + didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + + print(userInfo) + completionHandler(UIBackgroundFetchResult.newData) + } +} diff --git a/Club_portal/Club Portal/Club Portal/Utliz/AppSettings.swift b/Club_portal/Club Portal/Club Portal/Utliz/AppSettings.swift index e601324..2c447e6 100644 --- a/Club_portal/Club Portal/Club Portal/Utliz/AppSettings.swift +++ b/Club_portal/Club Portal/Club Portal/Utliz/AppSettings.swift @@ -41,6 +41,7 @@ final class AppSettings { case lastReserveTime case lastReservationTotal case isLoggedin + case fcm @@ -81,6 +82,14 @@ final class AppSettings { USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.plan_name.rawValue) } } + static var fcm: String { + get { + return USERDEFAULTS_GET_STRING_KEY(key: SettingKey.fcm.rawValue) + } + set { + USERDEFAULTS_SET_STRING_KEY(object: newValue, key: SettingKey.fcm.rawValue) + } + } static var price: Double { get { return USERDEFAULTS_GET_DOUBLE_KEY(key: SettingKey.price.rawValue)