Skip to content

Commit

Permalink
Merge pull request #120 from CovidTrackerFr/develop
Browse files Browse the repository at this point in the history
Release 1.3 - Merge develop into main
  • Loading branch information
victor-sarda authored May 19, 2021
2 parents 7a8d843 + eb0a97e commit a4ee79b
Show file tree
Hide file tree
Showing 65 changed files with 2,507 additions and 534 deletions.
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ disabled_rules:
- nesting
- identifier_name
line_length: 200
file_length: 800
type_body_length: 500
183 changes: 158 additions & 25 deletions ViteMaDose.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,31 @@
"version": "0.7.1"
}
},
{
"package": "BLTNBoard",
"repositoryURL": "https://github.com/alexisakers/BulletinBoard",
"state": {
"branch": null,
"revision": "e8ba81d25e41e5d24d6a4f404ed28443d728723b",
"version": "5.0.0"
}
},
{
"package": "Firebase",
"repositoryURL": "https://github.com/firebase/firebase-ios-sdk.git",
"state": {
"branch": null,
"revision": "e73fdb6b83b489d3b037085e04a8381f14d7f009",
"version": "7.10.0"
"revision": "b97bfcd2e5eecc7aa7e1aeb27d097d610876cf93",
"version": "7.11.1"
}
},
{
"package": "GoogleAppMeasurement",
"repositoryURL": "https://github.com/google/GoogleAppMeasurement.git",
"state": {
"branch": null,
"revision": "184d93b7790caa8b8368794c030c3aeb10d42e25",
"version": "7.10.0"
"revision": "22cb3ab5588200b210ae01e42665f52ac4e8ad28",
"version": "7.11.1"
}
},
{
Expand Down
134 changes: 125 additions & 9 deletions ViteMaDose/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,152 @@
//

import UIKit
import FirebaseCore
import Firebase
import SafariServices

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
FirebaseApp.configure()
Messaging.messaging().delegate = self
UNUserNotificationCenter.current().delegate = self
application.registerForRemoteNotifications()

#if DEBUG
resetUserDefaultsIfNeeded()
#endif
return true
}

// MARK: UISceneSession Lifecycle

func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}

func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
// MARK: - Register for remote notifications

func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let stringToken = deviceToken.map { data in String(format: "%02.2hhx", data) }.joined()
Log.i("APP APNS TOKEN: \(stringToken)")
Messaging.messaging().apnsToken = deviceToken
}

func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error
) {
Log.e("ERROR REGISTERING FOR NOTIFICATIONS \(error.localizedDescription)")
}
}

// MARK: - UNUserNotificationCenter Delegate

extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
if #available(iOS 14.0, *) {
completionHandler([.banner, .sound])
} else {
// Fallback on earlier versions
completionHandler([.alert, .sound])
}
// Analytics
let userInfo = notification.request.content.userInfo
Messaging.messaging().appDidReceiveMessage(userInfo)
}

func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
// Analytics
let userInfo = response.notification.request.content.userInfo
Messaging.messaging().appDidReceiveMessage(userInfo)

// If push contains an URL, open it in a SFSafariViewController
guard
let urlString = userInfo["url"] as? String,
let url = URL(string: urlString),
url.isValid
else {
completionHandler()
return
}

open(url: url, completion: completionHandler)
}

func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
// Analytics
Messaging.messaging().appDidReceiveMessage(userInfo)
completionHandler(UIBackgroundFetchResult.newData)
}

private func open(url: URL, completion: @escaping () -> Void) {
let window = UIApplication.shared.windows.filter(\.isKeyWindow)
guard let rootViewController = window.first?.rootViewController else {
return
}

let config = SFSafariViewController.Configuration()
let safariViewController = SFSafariViewController(url: url, configuration: config)
safariViewController.modalPresentationStyle = .pageSheet

DispatchQueue.main.async {
rootViewController.present(safariViewController, animated: true, completion: completion)
}
}
}

// MARK: - Messaging Delegate

extension AppDelegate: MessagingDelegate {
func messaging(
_ messaging: Messaging,
didReceiveRegistrationToken fcmToken: String?
) {
// Let FCM know about the new token
let dataDict: [String: String] = ["token": fcmToken.emptyIfNil]
NotificationCenter.default.post(
name: Notification.Name("FCMToken"),
object: nil,
userInfo: dataDict
)

Log.i("RECEIVED FCM TOKEN: \(fcmToken.emptyIfNil)")
}
}

// MARK: - DEBUG

#if DEBUG
extension AppDelegate {
private func resetUserDefaultsIfNeeded() {
#if DEBUG
if CommandLine.arguments.contains("-resetLocalStorage") {
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
}
#endif
}
}
#endif
152 changes: 152 additions & 0 deletions ViteMaDose/Helpers/Classes/Logger.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//
// Logger.swift
// ViteMaDose
//
// Created by Victor Sarda on 09/05/2021.
//

import Foundation
/// Enum which maps an appropiate symbol which added as prefix for each log message
///
/// - error: Log type error
/// - info: Log type info
/// - debug: Log type debug
/// - verbose: Log type verbose
/// - warning: Log type warning
/// - severe: Log type severe
enum LogEvent: String {
case e = "[🚨]" // error
case i = "[ℹ️]" // info
case d = "[💬]" // debug
case v = "[🔬]" // verbose
case w = "[⚠️]" // warning
case s = "[🔥]" // severe
}

/// Wrapping Swift.print() within DEBUG flag
///
/// - Note: *print()* might cause [security vulnerabilities](https://codifiedsecurity.com/mobile-app-security-testing-checklist-ios/)
///
/// - Parameter object: The object which is to be logged
///
func print(_ object: Any) {
// Only allowing in DEBUG mode
#if DEBUG
Swift.print(object)
#endif
}

final class Log {

static var dateFormat = "yyyy-MM-dd hh:mm:ss"
static var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = dateFormat
formatter.locale = Locale.current
formatter.timeZone = TimeZone.current
return formatter
}

private static var isLoggingEnabled: Bool {
#if DEBUG
return true
#else
return false
#endif
}

// MARK: - Loging methods

/// Logs error messages on console with prefix [🚨]
///
/// - Parameters:
/// - object: Object or message to be logged
/// - filename: File name from where loggin to be done
/// - line: Line number in file from where the logging is done
/// - column: Column number of the log message
/// - funcName: Name of the function from where the logging is done
class func e(_ object: Any, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {
guard isLoggingEnabled else { return }
print("\(Date().toString()) \(LogEvent.e.rawValue)[\(sourceFileName(filePath: filename))]:\(line) \(column) \(funcName) -> \(object)")
}

/// Logs info messages on console with prefix [ℹ️]
///
/// - Parameters:
/// - object: Object or message to be logged
/// - filename: File name from where loggin to be done
/// - line: Line number in file from where the logging is done
/// - column: Column number of the log message
/// - funcName: Name of the function from where the logging is done
class func i (_ object: Any, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {
guard isLoggingEnabled else { return }
print("\(Date().toString()) \(LogEvent.i.rawValue)[\(sourceFileName(filePath: filename))]:\(line) \(column) \(funcName) -> \(object)")
}

/// Logs debug messages on console with prefix [💬]
///
/// - Parameters:
/// - object: Object or message to be logged
/// - filename: File name from where loggin to be done
/// - line: Line number in file from where the logging is done
/// - column: Column number of the log message
/// - funcName: Name of the function from where the logging is done
class func d(_ object: Any, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {
guard isLoggingEnabled else { return }
print("\(Date().toString()) \(LogEvent.d.rawValue)[\(sourceFileName(filePath: filename))]:\(line) \(column) \(funcName) -> \(object)")
}

/// Logs messages verbosely on console with prefix [🔬]
///
/// - Parameters:
/// - object: Object or message to be logged
/// - filename: File name from where loggin to be done
/// - line: Line number in file from where the logging is done
/// - column: Column number of the log message
/// - funcName: Name of the function from where the logging is done
class func v(_ object: Any, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {
guard isLoggingEnabled else { return }
print("\(Date().toString()) \(LogEvent.v.rawValue)[\(sourceFileName(filePath: filename))]:\(line) \(column) \(funcName) -> \(object)")
}

/// Logs warnings verbosely on console with prefix [⚠️]
///
/// - Parameters:
/// - object: Object or message to be logged
/// - filename: File name from where loggin to be done
/// - line: Line number in file from where the logging is done
/// - column: Column number of the log message
/// - funcName: Name of the function from where the logging is done
class func w(_ object: Any, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {
guard isLoggingEnabled else { return }
print("\(Date().toString()) \(LogEvent.w.rawValue)[\(sourceFileName(filePath: filename))]:\(line) \(column) \(funcName) -> \(object)")
}

/// Logs severe events on console with prefix [🔥]
///
/// - Parameters:
/// - object: Object or message to be logged
/// - filename: File name from where loggin to be done
/// - line: Line number in file from where the logging is done
/// - column: Column number of the log message
/// - funcName: Name of the function from where the logging is done
class func s(_ object: Any, filename: String = #file, line: Int = #line, column: Int = #column, funcName: String = #function) {
guard isLoggingEnabled else { return }
print("\(Date().toString()) \(LogEvent.s.rawValue)[\(sourceFileName(filePath: filename))]:\(line) \(column) \(funcName) -> \(object)")
}

/// Extract the file name from the file path
///
/// - Parameter filePath: Full file path in bundle
/// - Returns: File Name with extension
private class func sourceFileName(filePath: String) -> String {
let components = filePath.components(separatedBy: "/")
return components.last ?? ""
}
}

internal extension Date {
func toString() -> String {
return Log.dateFormatter.string(from: self as Date)
}
}
22 changes: 22 additions & 0 deletions ViteMaDose/Helpers/Extensions/Array+Commons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,25 @@ extension RangeReplaceableCollection {
return filter { unique.insert($0[keyPath: keyPath]).inserted }
}
}

public protocol OptionalProtocol {
associatedtype Wrapped
var value: Wrapped? { get }
}

extension Sequence where Element: OptionalProtocol {
var compacted: [Element.Wrapped] {
return compactMap(\.value)
}
}

extension Optional: OptionalProtocol {
public var value: Wrapped? {
switch self {
case .none:
return nil
case let .some(value):
return value
}
}
}
1 change: 0 additions & 1 deletion ViteMaDose/Helpers/Extensions/Data+Commons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ extension Data {
/// - Parameter type: Object type
func decode<T: Codable>(_ type: T.Type) -> Result<T, Error> {
let jsonDecoder = JSONDecoder()

do {
let decoded = try jsonDecoder.decode(T.self, from: self)
return .success(decoded)
Expand Down
Loading

0 comments on commit a4ee79b

Please sign in to comment.