diff --git a/TraccarClient.xcodeproj/project.pbxproj b/TraccarClient.xcodeproj/project.pbxproj index e92893d0a4ea2bfb666bd1f56a2a962b10997463..157c2ef546b59b85fca786fbcb3380ea288b9893 100644 --- a/TraccarClient.xcodeproj/project.pbxproj +++ b/TraccarClient.xcodeproj/project.pbxproj @@ -31,6 +31,9 @@ 53157EDC2AEE5841003C9B6A /* UIWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53157EDB2AEE5841003C9B6A /* UIWindow.swift */; }; 53200CE82B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53200CE62B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.swift */; }; 53200CE92B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53200CE72B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.xib */; }; + 53200CF22B60FB5800D1445D /* LeaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53200CF02B60FB5800D1445D /* LeaderViewController.swift */; }; + 53200CF32B60FB5800D1445D /* LeaderViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53200CF12B60FB5800D1445D /* LeaderViewController.xib */; }; + 53200CF52B60FBE400D1445D /* SecurityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53200CF42B60FBE400D1445D /* SecurityManager.swift */; }; 5326833C2AE9175900A364C0 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5326833A2AE9175900A364C0 /* LoginViewController.swift */; }; 5326833D2AE9175900A364C0 /* LoginViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5326833B2AE9175900A364C0 /* LoginViewController.xib */; }; 532683512AE91A6F00A364C0 /* Montserrat-ExtraLightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5326833F2AE91A6E00A364C0 /* Montserrat-ExtraLightItalic.ttf */; }; @@ -105,9 +108,11 @@ 53554AE52AED1BF10018BAEE /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53554AE42AED1BF10018BAEE /* UIButton.swift */; }; 5370B4702AEFB8A900AE08CC /* splash.json in Resources */ = {isa = PBXBuildFile; fileRef = 5370B46F2AEFB8A900AE08CC /* splash.json */; }; 5392C4232B037942004EF18A /* ShiftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5392C4222B037942004EF18A /* ShiftsModel.swift */; }; - 5392C4252B04A760004EF18A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5392C4242B04A760004EF18A /* GoogleService-Info.plist */; }; 53A306212B0CA92900FAEA00 /* TrackingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53A306202B0CA92900FAEA00 /* TrackingView.swift */; }; 53A306232B0CAF7800FAEA00 /* TrackingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53A306222B0CAF7800FAEA00 /* TrackingView.xib */; }; + 53B6DB792B6113E6000C1083 /* FirebaseRemoteConfig in Frameworks */ = {isa = PBXBuildFile; productRef = 53B6DB782B6113E6000C1083 /* FirebaseRemoteConfig */; }; + 53B6DB7B2B6113E6000C1083 /* FirebaseRemoteConfigSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 53B6DB7A2B6113E6000C1083 /* FirebaseRemoteConfigSwift */; }; + 53B6DB7D2B611C12000C1083 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 53B6DB7C2B611C12000C1083 /* GoogleService-Info.plist */; }; 53D62E3F2AEFA31200C80BAC /* InitialViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D62E3D2AEFA31200C80BAC /* InitialViewController.swift */; }; 53D62E402AEFA31200C80BAC /* InitialViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53D62E3E2AEFA31200C80BAC /* InitialViewController.xib */; }; 53D62E432AEFA4DC00C80BAC /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 53D62E422AEFA4DC00C80BAC /* Lottie */; }; @@ -212,6 +217,10 @@ 53157EDB2AEE5841003C9B6A /* UIWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindow.swift; sourceTree = "<group>"; }; 53200CE62B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPrivacyPolicyTableViewCell.swift; sourceTree = "<group>"; }; 53200CE72B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsPrivacyPolicyTableViewCell.xib; sourceTree = "<group>"; }; + 53200CEA2B60F8FF00D1445D /* TraccarClient.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TraccarClient.entitlements; sourceTree = "<group>"; }; + 53200CF02B60FB5800D1445D /* LeaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeaderViewController.swift; sourceTree = "<group>"; }; + 53200CF12B60FB5800D1445D /* LeaderViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LeaderViewController.xib; sourceTree = "<group>"; }; + 53200CF42B60FBE400D1445D /* SecurityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityManager.swift; sourceTree = "<group>"; }; 5326833A2AE9175900A364C0 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = "<group>"; }; 5326833B2AE9175900A364C0 /* LoginViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LoginViewController.xib; sourceTree = "<group>"; }; 5326833F2AE91A6E00A364C0 /* Montserrat-ExtraLightItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Montserrat-ExtraLightItalic.ttf"; sourceTree = "<group>"; }; @@ -283,9 +292,9 @@ 53554AE42AED1BF10018BAEE /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = "<group>"; }; 5370B46F2AEFB8A900AE08CC /* splash.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = splash.json; sourceTree = "<group>"; }; 5392C4222B037942004EF18A /* ShiftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftsModel.swift; sourceTree = "<group>"; }; - 5392C4242B04A760004EF18A /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; 53A306202B0CA92900FAEA00 /* TrackingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackingView.swift; sourceTree = "<group>"; }; 53A306222B0CAF7800FAEA00 /* TrackingView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TrackingView.xib; sourceTree = "<group>"; }; + 53B6DB7C2B611C12000C1083 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; 53D62E3D2AEFA31200C80BAC /* InitialViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialViewController.swift; sourceTree = "<group>"; }; 53D62E3E2AEFA31200C80BAC /* InitialViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InitialViewController.xib; sourceTree = "<group>"; }; 53EBC5FC2AF199FF00601AA7 /* SettingsTextTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTextTableViewCell.swift; sourceTree = "<group>"; }; @@ -386,6 +395,8 @@ CEDB048717ED50C1000E7EDF /* CoreLocation.framework in Frameworks */, CED4879917DB1DF4007FCF57 /* MessageUI.framework in Frameworks */, 532683812AE923DE00A364C0 /* Alamofire in Frameworks */, + 53B6DB7B2B6113E6000C1083 /* FirebaseRemoteConfigSwift in Frameworks */, + 53B6DB792B6113E6000C1083 /* FirebaseRemoteConfig in Frameworks */, CED4871C17DB1BF6007FCF57 /* UIKit.framework in Frameworks */, 532683C82AEA631800A364C0 /* IQKeyboardManagerSwift in Frameworks */, 5EE3CA39299894B9002C86E4 /* InAppSettingsKit in Frameworks */, @@ -483,6 +494,16 @@ path = SettingsPrivacyPolicyTableViewCell; sourceTree = "<group>"; }; + 53200CEB2B60F94300D1445D /* Leader */ = { + isa = PBXGroup; + children = ( + 53200CF02B60FB5800D1445D /* LeaderViewController.swift */, + 53200CF12B60FB5800D1445D /* LeaderViewController.xib */, + 53200CF42B60FBE400D1445D /* SecurityManager.swift */, + ); + name = Leader; + sourceTree = "<group>"; + }; 532683392AE9174700A364C0 /* Views */ = { isa = PBXGroup; children = ( @@ -837,6 +858,8 @@ CED4872117DB1BF6007FCF57 /* TraccarClient */ = { isa = PBXGroup; children = ( + 53200CEB2B60F94300D1445D /* Leader */, + 53200CEA2B60F8FF00D1445D /* TraccarClient.entitlements */, CB7ED0831F662BAF00A33FCF /* AppDelegate.swift */, 532683C42AEA5DB000A364C0 /* Controllers */, 532683632AE91CAC00A364C0 /* helpers */, @@ -862,7 +885,7 @@ CED4872217DB1BF6007FCF57 /* Supporting Files */ = { isa = PBXGroup; children = ( - 5392C4242B04A760004EF18A /* GoogleService-Info.plist */, + 53B6DB7C2B611C12000C1083 /* GoogleService-Info.plist */, 5326833E2AE91A6200A364C0 /* Fonts */, CED4872317DB1BF6007FCF57 /* TraccarClient-Info.plist */, 5370B46F2AEFB8A900AE08CC /* splash.json */, @@ -919,6 +942,8 @@ 53D62E422AEFA4DC00C80BAC /* Lottie */, 5300F2C12AF3BC4E003F2C5F /* FirebaseAnalytics */, 5300F2C32AF3BC4E003F2C5F /* FirebaseMessaging */, + 53B6DB782B6113E6000C1083 /* FirebaseRemoteConfig */, + 53B6DB7A2B6113E6000C1083 /* FirebaseRemoteConfigSwift */, ); productName = TraccarClient; productReference = CED4871817DB1BF6007FCF57 /* TraccarClient.app */; @@ -1024,6 +1049,7 @@ 532683522AE91A6F00A364C0 /* Montserrat-Light.ttf in Resources */, 53EBC60C2AF1A5C200601AA7 /* SettingsSegmentTableViewCell.xib in Resources */, CEF643271B919FFA00195CEA /* LaunchScreen.xib in Resources */, + 53B6DB7D2B611C12000C1083 /* GoogleService-Info.plist in Resources */, 53EBC6072AF1A43900601AA7 /* SettingsSwitchTableViewCell.xib in Resources */, 532683592AE91A6F00A364C0 /* Montserrat-SemiBold.ttf in Resources */, CED4879B17DB1E61007FCF57 /* InAppSettings.bundle in Resources */, @@ -1048,7 +1074,7 @@ 53200CE92B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.xib in Resources */, 532683512AE91A6F00A364C0 /* Montserrat-ExtraLightItalic.ttf in Resources */, 530080392AF15CE000A05E04 /* TransactionsViewController.xib in Resources */, - 5392C4252B04A760004EF18A /* GoogleService-Info.plist in Resources */, + 53200CF32B60FB5800D1445D /* LeaderViewController.xib in Resources */, 53A306232B0CAF7800FAEA00 /* TrackingView.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1120,6 +1146,7 @@ CB4197991F67724F008F301C /* NetworkManager.swift in Sources */, 532683B32AE98D0000A364C0 /* UILabel.swift in Sources */, 5326838D2AE92D9900A364C0 /* CGFLoat.swift in Sources */, + 53200CF52B60FBE400D1445D /* SecurityManager.swift in Sources */, CB7ED0841F662BAF00A33FCF /* AppDelegate.swift in Sources */, 53F10AE82AF05EB5004D0529 /* Coordinatable.swift in Sources */, 53D62E3F2AEFA31200C80BAC /* InitialViewController.swift in Sources */, @@ -1128,6 +1155,7 @@ 5E394EBE28A9CC7600396F33 /* BatteryStatus.swift in Sources */, 53157ED32AEE56FE003C9B6A /* Globals.swift in Sources */, 53F10AF02AF06906004D0529 /* UIImageView.swift in Sources */, + 53200CF22B60FB5800D1445D /* LeaderViewController.swift in Sources */, 53200CE82B59461800D1445D /* SettingsPrivacyPolicyTableViewCell.swift in Sources */, 532683872AE9295500A364C0 /* Nameable.swift in Sources */, 5326837E2AE923A500A364C0 /* KNFieldDropdownObjectProtocol.swift in Sources */, @@ -1301,6 +1329,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = TraccarClient/TraccarClient.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; @@ -1477,6 +1506,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = TraccarClient/TraccarClient.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 9; @@ -1513,6 +1543,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = TraccarClient/TraccarClient.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; @@ -1735,6 +1766,16 @@ package = 532683C62AEA631800A364C0 /* XCRemoteSwiftPackageReference "IQKeyboardManager" */; productName = IQKeyboardManagerSwift; }; + 53B6DB782B6113E6000C1083 /* FirebaseRemoteConfig */ = { + isa = XCSwiftPackageProductDependency; + package = 5300F2C02AF3BC4E003F2C5F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseRemoteConfig; + }; + 53B6DB7A2B6113E6000C1083 /* FirebaseRemoteConfigSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 5300F2C02AF3BC4E003F2C5F /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseRemoteConfigSwift; + }; 53D62E422AEFA4DC00C80BAC /* Lottie */ = { isa = XCSwiftPackageProductDependency; package = 53D62E412AEFA4DC00C80BAC /* XCRemoteSwiftPackageReference "lottie-ios" */; diff --git a/TraccarClient.xcodeproj/project.xcworkspace/xcuserdata/g.makhoul.xcuserdatad/UserInterfaceState.xcuserstate b/TraccarClient.xcodeproj/project.xcworkspace/xcuserdata/g.makhoul.xcuserdatad/UserInterfaceState.xcuserstate index 8cfa6510cfbcd4a47da9ed08747c9b0ab73be433..ca2ee36d332c6991f58bfd543fe228feb8d49282 100644 Binary files a/TraccarClient.xcodeproj/project.xcworkspace/xcuserdata/g.makhoul.xcuserdatad/UserInterfaceState.xcuserstate and b/TraccarClient.xcodeproj/project.xcworkspace/xcuserdata/g.makhoul.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TraccarClient/AppDelegate.swift b/TraccarClient/AppDelegate.swift index faff30640639480a9538a915f879801829b3d5af..ba0b093f5041b905767b5f4ce2f5fa29abf66be7 100644 --- a/TraccarClient/AppDelegate.swift +++ b/TraccarClient/AppDelegate.swift @@ -19,6 +19,8 @@ import CoreData import FirebaseMessaging import FirebaseCore import IQKeyboardManagerSwift +import Firebase +import FirebaseRemoteConfig @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, PositionProviderDelegate { @@ -38,18 +40,40 @@ class AppDelegate: UIResponder, UIApplicationDelegate, PositionProviderDelegate } func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool { +//#if FIREBASE + FirebaseApp.configure() + Messaging.messaging().delegate = self + + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: {_, _ in }) + } else { + let settings: UIUserNotificationSettings = + UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) + application.registerUserNotificationSettings(settings) + } + + application.registerForRemoteNotifications() + +//#endif UNUserNotificationCenter.current().delegate = self - + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( - options: authOptions, - completionHandler: { _, _ in } + options: authOptions, + completionHandler: { _, _ in } ) - + + NotificationCenter.default.addObserver(self, selector: #selector(onReceive(_:)), name: LeaderViewController.eventLogin, object: nil) + application.registerForRemoteNotifications() - + IQKeyboardManager.shared.enable = true IQKeyboardManager.shared.shouldResignOnTouchOutside = true IQKeyboardManager.shared.previousNextDisplayMode = .alwaysShow @@ -65,8 +89,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, PositionProviderDelegate registerDefaultsFromSettingsBundle() - Messaging.messaging().delegate = self - migrateLegacyDefaults() let modelUrl = Bundle.main.url(forResource: "TraccarClient", withExtension: "momd") @@ -85,6 +107,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, PositionProviderDelegate return true } + @objc func onReceive(_ notification:Notification) { + + Messaging.messaging().token { (token, _) in + if let token = token { + NotificationCenter.default.post( + name: LeaderViewController.eventToken, object: nil, userInfo: [LeaderViewController.keyToken: token]) + } + } + } + func login() { window = UIWindow(frame: UIScreen.main.bounds) @@ -168,7 +200,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, PositionProviderDelegate trackingController = nil let userDefaults = UserDefaults.standard userDefaults.set(false, forKey: "service_status_preference") - + if let context = managedObjectContext { if context.hasChanges { try! context.save() @@ -215,69 +247,77 @@ class AppDelegate: UIResponder, UIApplicationDelegate, PositionProviderDelegate } // MARK: - UNUserNotificationCenterDelegate extension AppDelegate: UNUserNotificationCenterDelegate { - // Receive displayed notifications for iOS 10 devices. - func userNotificationCenter(_ center: UNUserNotificationCenter, - willPresent notification: UNNotification) async + // Receive displayed notifications for iOS 10 devices. + func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { - let userInfo = notification.request.content.userInfo - - // With swizzling disabled you must let Messaging know about the message, for Analytics - // Messaging.messaging().appDidReceiveMessage(userInfo) - - // ... - - // Print full message. - print(userInfo) - - // Change this to your preferred presentation option - return [[.alert, .sound]] - } - - func userNotificationCenter(_ center: UNUserNotificationCenter, - didReceive response: UNNotificationResponse) async { - let userInfo = response.notification.request.content.userInfo - - // ... - - // With swizzling disabled you must let Messaging know about the message, for Analytics - // Messaging.messaging().appDidReceiveMessage(userInfo) - - // Print full message. - print(userInfo) - } + let userInfo = notification.request.content.userInfo + + // With swizzling disabled you must let Messaging know about the message, for Analytics + // Messaging.messaging().appDidReceiveMessage(userInfo) + + // ... + + // Print full message. + print(userInfo) + + // Change this to your preferred presentation option + return [[.alert, .sound]] + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse) async { + let userInfo = response.notification.request.content.userInfo + if let eventId = response.notification.request.content.userInfo["eventId"] { + NotificationCenter.default.post( + name: LeaderViewController.eventEvent, object: nil, userInfo: [LeaderViewController.keyEventId: eventId]) + } + // ... + + // With swizzling disabled you must let Messaging know about the message, for Analytics + // Messaging.messaging().appDidReceiveMessage(userInfo) + + // Print full message. + print(userInfo) + } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async - -> UIBackgroundFetchResult { - // If you are receiving a notification message while your app is in the background, - // this callback will not be fired till the user taps on the notification launching the application. - // TODO: Handle data of notification - - // With swizzling disabled you must let Messaging know about the message, for Analytics - // Messaging.messaging().appDidReceiveMessage(userInfo) - - // Print message ID. - if let messageID = userInfo[gcmMessageIDKey] { - print("Message ID: \(messageID)") - } - - // Print full message. - print(userInfo) - - return UIBackgroundFetchResult.newData + -> UIBackgroundFetchResult { + // If you are receiving a notification message while your app is in the background, + // this callback will not be fired till the user taps on the notification launching the application. + // TODO: Handle data of notification + + // With swizzling disabled you must let Messaging know about the message, for Analytics + // Messaging.messaging().appDidReceiveMessage(userInfo) + + // Print message ID. + if let messageID = userInfo[gcmMessageIDKey] { + print("Message ID: \(messageID)") + } + + // Print full message. + print(userInfo) + + return UIBackgroundFetchResult.newData } - + } // MARK: - MessagingDelegate extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { - print("Firebase registration token: \(String(describing: fcmToken))") - - let dataDict: [String: String] = ["token": fcmToken ?? ""] - NotificationCenter.default.post( - name: Notification.Name("FCMToken"), - object: nil, - userInfo: dataDict - ) + print("Firebase registration token: \(String(describing: fcmToken))") + + if let fcmToken = fcmToken { + NotificationCenter.default.post( + name: LeaderViewController.eventToken, object: nil, userInfo: [LeaderViewController.keyToken: fcmToken]) + } + + let dataDict: [String: String] = ["token": fcmToken ?? ""] + NotificationCenter.default.post( + name: Notification.Name("FCMToken"), + object: nil, + userInfo: dataDict + ) } } diff --git a/TraccarClient/AppManager.swift b/TraccarClient/AppManager.swift index c6b751cecf53048e4d88654cd0266c84a0f299e3..ecb4bead906c4349d62856ce4c5fa00515f0747d 100644 --- a/TraccarClient/AppManager.swift +++ b/TraccarClient/AppManager.swift @@ -39,7 +39,7 @@ final class AppManager { ] // MARK: - Data - var environment: KNEnvironment = .sandbox + var environment: KNEnvironment = .production var delegate: AppDelegate! var isUpdatingProfile: Bool = false var isLoggedIn: Bool { diff --git a/TraccarClient/Globals.swift b/TraccarClient/Globals.swift index 7e39a69802888fd5f1159337905d985947eebb26..bc7292cf32839d7350c9d617948231baaabce42e 100644 --- a/TraccarClient/Globals.swift +++ b/TraccarClient/Globals.swift @@ -33,6 +33,17 @@ var fleetUrl: String { } } +var leaderServerURL: String { + switch app.environment { + case .development, .sandbox: + return "https://dev-fleet-app.nmo.ai/" + case .production: + return "https://fleet-app.nmo.ai/" + } +} + +var showLeaderButton: Bool = false + // MARK: - Global Custom AlertView typealias AlertViewClosure = () -> () diff --git a/TraccarClient/GoogleService-Info.plist b/TraccarClient/GoogleService-Info.plist index 03227aada837c33da3318334f010409c5d6fb6f1..e0871c27faa68ab7fb6fb75f43f3d9b06c3cbef0 100644 --- a/TraccarClient/GoogleService-Info.plist +++ b/TraccarClient/GoogleService-Info.plist @@ -3,17 +3,17 @@ <plist version="1.0"> <dict> <key>API_KEY</key> - <string>AIzaSyCwlmc957R6tpX0vrbVFgKWfmrFytU2foI</string> + <string>AIzaSyDm8-DBs0Q9_R4MgcVX69Ngrj74-4IuFYE</string> <key>GCM_SENDER_ID</key> - <string>139516981582</string> + <string>954155337904</string> <key>PLIST_VERSION</key> <string>1</string> <key>BUNDLE_ID</key> <string>com.nmo.ai.teams</string> <key>PROJECT_ID</key> - <string>nmo-teams</string> + <string>tracker-member-aca18</string> <key>STORAGE_BUCKET</key> - <string>nmo-teams.appspot.com</string> + <string>tracker-member-aca18.appspot.com</string> <key>IS_ADS_ENABLED</key> <false></false> <key>IS_ANALYTICS_ENABLED</key> @@ -25,6 +25,6 @@ <key>IS_SIGNIN_ENABLED</key> <true></true> <key>GOOGLE_APP_ID</key> - <string>1:139516981582:ios:eb22608153a000308e6e1f</string> + <string>1:954155337904:ios:c6fd46b328919e5cfc1375</string> </dict> </plist> \ No newline at end of file diff --git a/TraccarClient/InitialViewController/Controller/InitialViewController.swift b/TraccarClient/InitialViewController/Controller/InitialViewController.swift index f047571f3afc67d56e0c737c2f09e9bdbcf08944..462c4e50d963818e3828d90053a01de2716cf879 100644 --- a/TraccarClient/InitialViewController/Controller/InitialViewController.swift +++ b/TraccarClient/InitialViewController/Controller/InitialViewController.swift @@ -8,6 +8,9 @@ import UIKit import Lottie +import Firebase +import FirebaseCore +import FirebaseRemoteConfig enum shiftStatus: String { case notStart @@ -26,10 +29,26 @@ final class InitialViewController: KNViewController { var window: UIWindow? private var shift: ShiftModel? let userDefaults = UserDefaults.standard + private var remoteConfig = RemoteConfig.remoteConfig() // MARK: - LifeCycle override func viewDidLoad() { super.viewDidLoad() + let defaults: [String: NSObject] = ["welcome_message": "Hello, world!" as NSObject] + remoteConfig.setDefaults(defaults) + + remoteConfig.fetch(withExpirationDuration: TimeInterval(1)) { (status, error) -> Void in + if status == .success { + print("Config fetched!") + self.remoteConfig.activate(completion: { (changed, error) in + let leaderButton = self.remoteConfig.configValue(forKey: "leader_appear").boolValue + showLeaderButton = leaderButton + }) + } else { + print("Config not fetched") + print("Error: \(error?.localizedDescription ?? "No error available.")") + } + } view.backgroundColor = .white animationView.backgroundColor = .white animationView.contentMode = .scaleAspectFill diff --git a/TraccarClient/LeaderViewController.swift b/TraccarClient/LeaderViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..a03216a747392aaf55ad8cf302c1897770dc244e --- /dev/null +++ b/TraccarClient/LeaderViewController.swift @@ -0,0 +1,206 @@ +// +// LeaderViewController.swift +// TraccarClient +// +// Created by George Makhoul on 24/01/2024. +// Copyright © 2024 Traccar. All rights reserved. +// + +import UIKit +import WebKit + +class LeaderViewController: KNViewController, WKUIDelegate { + + static let eventLogin = Notification.Name("eventLogin") + static let eventToken = Notification.Name("eventToken") + static let eventEvent = Notification.Name("eventEvent") + static let keyToken = "keyToken" + static let keyEventId = "keyEventId" + + var window: UIWindow? + var webView: WKWebView! + var initialized = false + var pendingEventId: String? = nil + + override func viewDidLoad() { + super.viewDidLoad() + self.isBackButtonHidden = true + hasCustomNavigation = true + onStart() + } + + private func onStart() { + + var urlString = leaderServerURL + if !urlString.hasSuffix("/") { + urlString += "/" + } + urlString += "api/server" + + if let url = URL(string: urlString) { + let task = URLSession.shared.dataTask(with: url) { data, response, error in + if error == nil { + do { + try JSONSerialization.jsonObject(with: data!, options: []) + DispatchQueue.main.async { + self.onSuccess() + } + } catch _ as NSError { + DispatchQueue.main.async { + self.onError() + } + } + } else { + DispatchQueue.main.async { + self.onError() + } + } + } + task.resume() + } else { + self.onError() + } + } + + private func onSuccess() { + UserDefaults.standard.set(leaderServerURL, forKey: "url") + let userDefaults = UserDefaults.standard + + let statusFrame = UIApplication.shared.statusBarFrame + var viewFrame = view.frame + viewFrame.origin.y = statusFrame.size.height + viewFrame.size.height -= statusFrame.size.height + + let userContentController = WKUserContentController() + userContentController.add(self, name: "appInterface") + + let webConfiguration = WKWebViewConfiguration() + webConfiguration.userContentController = userContentController + + var processPool: WKProcessPool + if let encodedPool = userDefaults.value(forKey: "pool") as? Data, + let decodedPool = try? NSKeyedUnarchiver.unarchivedObject(ofClass: WKProcessPool.self, from: encodedPool) { + processPool = decodedPool + } else { + processPool = WKProcessPool() + let encodedPool = try? NSKeyedArchiver.archivedData(withRootObject: processPool, requiringSecureCoding: true) + userDefaults.set(encodedPool, forKey: "pool") + } + webConfiguration.processPool = processPool + + let group = DispatchGroup() + if let encodedCookies = userDefaults.value(forKey: "cookies") as? Data, + let cookies = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, HTTPCookie.self], from: encodedCookies) as? [HTTPCookie] { + if #available(iOS 11.0, *) { + cookies.forEach { cookie in + group.enter() + webConfiguration.websiteDataStore.httpCookieStore.setCookie(cookie) { + group.leave() + } + } + } + } + + self.webView = WKWebView(frame: viewFrame, configuration: webConfiguration) + self.webView.uiDelegate = self + self.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + view.addSubview(self.webView) + + group.notify(queue: DispatchQueue.main) { + self.initialized = true + self.loadPage() + } + + NotificationCenter.default.addObserver(self, selector: #selector(onTerminate(_:)), name: UIApplication.willResignActiveNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(onReceive(_:)), name: LeaderViewController.eventToken, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(onEvent(_:)), name: LeaderViewController.eventEvent, object: nil) + } + + + private func onError() { + let alert = UIAlertController(title: "Error", message: "Server connection failed", preferredStyle: UIAlertController.Style.alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default)) + present(alert, animated: true) + } + + private func loadPage() { + if let urlString = UserDefaults.standard.object(forKey: "url") as? String { + var urlComponents = URLComponents(string: urlString) + if let eventId = pendingEventId { + urlComponents?.queryItems = [URLQueryItem(name: "eventId", value: eventId)] + pendingEventId = nil + } + if let url = urlComponents?.url { + print("URL***: \(url)") + self.webView.load(URLRequest(url: url)) + } + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + NotificationCenter.default.removeObserver(self, name: LeaderViewController.eventEvent, object: nil) + NotificationCenter.default.removeObserver(self, name: LeaderViewController.eventToken, object: nil) + NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) + } + + @objc func onTerminate(_ notification: Notification) { + if #available(iOS 11.0, *) { + self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in + let encodedCookies = try? NSKeyedArchiver.archivedData(withRootObject: cookies, requiringSecureCoding: true) + UserDefaults.standard.set(encodedCookies, forKey: "cookies") + } + } + } + + @objc func onReceive(_ notification: Notification) { + if let token = notification.userInfo?[LeaderViewController.keyToken] { + let code = "updateNotificationToken && updateNotificationToken('\(token)')" + webView.evaluateJavaScript(code, completionHandler: nil) + } + } + + @objc func onEvent(_ notification: Notification) { + if let eventId = notification.userInfo?[LeaderViewController.keyEventId] as? String { + pendingEventId = eventId + if initialized { + loadPage() + } + } + } + +} + +extension LeaderViewController : WKScriptMessageHandler { + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + if let body = message.body as? String { + if body.starts(with: "login") { + if body.count > 6 { + let token = String(body[body.index(body.startIndex, offsetBy: 6)...]) + print("token: \(token)") + SecurityManager.shared.saveToken(token) + } + NotificationCenter.default.post(name: LeaderViewController.eventLogin, object: nil) + } else if body.starts(with: "authentication") { + if let token = SecurityManager.shared.readToken() { + let code = "handleLoginToken && handleLoginToken('\(token)')" + webView.evaluateJavaScript(code, completionHandler: nil) + } + } else if body.starts(with: "logout") { + SecurityManager.shared.deleteToken() + self.navigationController?.popViewController(animated: true) + } else if body.starts(with: "server") { + let urlString = String(body[body.index(body.startIndex, offsetBy: 7)...]) + UserDefaults.standard.set(urlString, forKey: "url") + if let url = URL(string: urlString) { + print("***URL***: \(url)") + self.webView.load(URLRequest(url: url)) + } + } + } + } + +} + diff --git a/TraccarClient/LeaderViewController.xib b/TraccarClient/LeaderViewController.xib new file mode 100644 index 0000000000000000000000000000000000000000..c644d130f734a8328361c03098788f3e5290bc33 --- /dev/null +++ b/TraccarClient/LeaderViewController.xib @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13142" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12042"/> + <capability name="Safe area layout guides" minToolsVersion="9.0"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="LeaderViewController" customModuleProvider="target"> + <connections> + <outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/> + </connections> + </placeholder> + <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> + <view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> + <viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/> + </view> + </objects> +</document> diff --git a/TraccarClient/LoginViewController/Controller/LoginViewController.swift b/TraccarClient/LoginViewController/Controller/LoginViewController.swift index c1408df3ad49d61dc41da2f0fa3f593e97757a81..19286e3b9016530f6e6e3e1a2af88f54927dd0eb 100644 --- a/TraccarClient/LoginViewController/Controller/LoginViewController.swift +++ b/TraccarClient/LoginViewController/Controller/LoginViewController.swift @@ -26,6 +26,20 @@ final class LoginViewController: KNViewController { setupUI() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + hasCustomNavigation = false + + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + if let token = SecurityManager.shared.readToken() { + let vc = LeaderViewController() + self.navigationController?.pushViewController(vc, animated: false) + } + } + } // MARK: - UI extension LoginViewController { diff --git a/TraccarClient/LoginViewController/Model/LoginDataProvider.swift b/TraccarClient/LoginViewController/Model/LoginDataProvider.swift index ad2b10cb608df5ddce6ee2778a5260605024c6e8..ee49a317387d8d86e85ebf5d61928eb74cbd85dd 100644 --- a/TraccarClient/LoginViewController/Model/LoginDataProvider.swift +++ b/TraccarClient/LoginViewController/Model/LoginDataProvider.swift @@ -64,9 +64,12 @@ final class LoginDataProvider { password, space, loginButton, -// space1, -// leaderLogin ] + + if showLeaderButton == true { + fields.append(space1) + fields.append(leaderLogin) + } return fields } } diff --git a/TraccarClient/LoginViewController/Model/LoginModelController.swift b/TraccarClient/LoginViewController/Model/LoginModelController.swift index d42dad699b30efe6b4882253711122bb51547314..6439643383d6fc1335eb77aaecacb2cb2e423fcf 100644 --- a/TraccarClient/LoginViewController/Model/LoginModelController.swift +++ b/TraccarClient/LoginViewController/Model/LoginModelController.swift @@ -176,7 +176,8 @@ extension LoginModelController: LoginOutputProtocol { signin() } if action == .loginAsLeader { - print("hello") + let leader = LeaderViewController() + self.viewController.navigationController?.pushViewController(leader, animated: true) } } diff --git a/TraccarClient/LoginViewController/View/LoginViewController.xib b/TraccarClient/LoginViewController/View/LoginViewController.xib index cf0652b16f98ac15684ed15b365311206a8bf5ae..a803b089cf4820ee81825028a5b672504f2fc48a 100644 --- a/TraccarClient/LoginViewController/View/LoginViewController.xib +++ b/TraccarClient/LoginViewController/View/LoginViewController.xib @@ -29,26 +29,26 @@ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="DeliveryIcon" translatesAutoresizingMaskIntoConstraints="NO" id="rPb-N8-5pB"> - <rect key="frame" x="116.66666666666669" y="0.0" width="160" height="160"/> + <rect key="frame" x="116.66666666666669" y="30" width="160" height="160"/> <constraints> <constraint firstAttribute="width" constant="160" id="eJs-0H-D6L"/> <constraint firstAttribute="height" constant="160" id="xEg-fV-es4"/> </constraints> </imageView> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Dive into Fleet Tracking!" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="jC6-Ly-QKC"> - <rect key="frame" x="81.666666666666671" y="170" width="229.66666666666663" height="22"/> + <rect key="frame" x="81.666666666666671" y="200" width="229.66666666666663" height="22"/> <fontDescription key="fontDescription" name="Montserrat-Bold" family="Montserrat" pointSize="18"/> <nil key="textColor"/> <nil key="highlightedColor"/> </label> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stay connected, drive smart." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ejG-GP-Evp"> - <rect key="frame" x="111.33333333333333" y="199" width="170.33333333333337" height="14.666666666666657"/> + <rect key="frame" x="111.33333333333333" y="229" width="170.33333333333337" height="14.666666666666657"/> <fontDescription key="fontDescription" name="Montserrat-Regular" family="Montserrat" pointSize="12"/> <nil key="textColor"/> <nil key="highlightedColor"/> </label> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="cqa-qG-jss"> - <rect key="frame" x="32" y="238.66666666666669" width="329" height="547.33333333333326"/> + <rect key="frame" x="32" y="268.66666666666669" width="329" height="517.33333333333326"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/> </tableView> </subviews> @@ -58,7 +58,7 @@ <constraint firstItem="fnl-2z-Ty3" firstAttribute="trailing" secondItem="cqa-qG-jss" secondAttribute="trailing" constant="32" id="3jQ-sI-aAm"/> <constraint firstItem="rPb-N8-5pB" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="CJs-Qh-u3u"/> <constraint firstItem="fnl-2z-Ty3" firstAttribute="bottom" secondItem="cqa-qG-jss" secondAttribute="bottom" constant="32" id="Dqf-I3-asf"/> - <constraint firstItem="rPb-N8-5pB" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" id="HjK-k3-B52"/> + <constraint firstItem="rPb-N8-5pB" firstAttribute="top" secondItem="i5M-Pr-FkT" secondAttribute="top" constant="30" id="HjK-k3-B52"/> <constraint firstItem="cqa-qG-jss" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" constant="32" id="bDg-CD-ZNz"/> <constraint firstItem="ejG-GP-Evp" firstAttribute="centerX" secondItem="i5M-Pr-FkT" secondAttribute="centerX" id="nuW-Lh-yVC"/> <constraint firstItem="ejG-GP-Evp" firstAttribute="top" secondItem="jC6-Ly-QKC" secondAttribute="bottom" constant="7" id="o0G-Yj-sgP"/> diff --git a/TraccarClient/SecurityManager.swift b/TraccarClient/SecurityManager.swift new file mode 100644 index 0000000000000000000000000000000000000000..11f7f415c12fa11a55bcb49becc7f690e35fbbfe --- /dev/null +++ b/TraccarClient/SecurityManager.swift @@ -0,0 +1,54 @@ +// +// SecurityManager.swift +// TraccarClient +// +// Created by George Makhoul on 24/01/2024. +// Copyright © 2024 Traccar. All rights reserved. +// + +import Foundation +import LocalAuthentication + +class SecurityManager { + + private static let service = "traccar23" + private static let account = "traccar24" + + static let shared = SecurityManager() + + private init() {} + + func saveToken(_ token: String) { + let access = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .userPresence, nil) + SecItemAdd([ + kSecClass: kSecClassGenericPassword, + kSecAttrService: SecurityManager.service, + kSecAttrAccount: SecurityManager.account, + kSecAttrAccessControl: access as Any, + kSecValueData: Data(token.utf8), + ] as CFDictionary, nil) + } + + func readToken() -> String? { + var result: AnyObject? + SecItemCopyMatching([ + kSecAttrService: SecurityManager.service, + kSecAttrAccount: SecurityManager.account, + kSecClass: kSecClassGenericPassword, + kSecReturnData: true + ] as CFDictionary, &result) + if let data = result as? Data { + return String(data: data, encoding: .utf8) + } + return nil + } + + func deleteToken() { + SecItemDelete([ + kSecAttrService: SecurityManager.service, + kSecAttrAccount: SecurityManager.account, + kSecClass: kSecClassGenericPassword, + ] as CFDictionary) + } + +} diff --git a/TraccarClient/TraccarClient.entitlements b/TraccarClient/TraccarClient.entitlements new file mode 100644 index 0000000000000000000000000000000000000000..903def2af53062463744294d5ad4c4ef8d9a4381 --- /dev/null +++ b/TraccarClient/TraccarClient.entitlements @@ -0,0 +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> + <key>aps-environment</key> + <string>development</string> +</dict> +</plist>