Skip to content

Commit a2b6a8b

Browse files
COVIDSafe code from version 1.11 (#22)
1 parent 746841a commit a2b6a8b

File tree

65 files changed

+4396
-2212
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+4396
-2212
lines changed

CovidSafe.xcodeproj/project.pbxproj

+97-23
Large diffs are not rendered by default.

CovidSafe/API/MessageAPI.swift

+82-46
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,45 @@ class MessageAPI {
1313

1414
static let keyLastApiUpdate = "keyLastApiUpdate"
1515
static let keyLastVersionChecked = "keyLastVersionChecked"
16-
17-
static func getMessagesIfNeeded(completion: @escaping (MessageResponse?, Swift.Error?) -> Void) {
16+
17+
static func getMessagesIfNeeded(completion: @escaping (MessageResponse?, MessageAPIError?) -> Void) {
1818
if shouldGetMessages() {
19-
guard let token = UserDefaults.standard.string(forKey: "deviceTokenForAPN") else {
20-
return
21-
}
22-
//Get relevat encounter data
23-
guard let persistentContainer =
24-
EncounterDB.shared.persistentContainer else {
25-
return
26-
}
27-
let managedContext = persistentContainer.newBackgroundContext()
28-
guard let encounterLastWeekRequest = Encounter.fetchEncountersInLast(days: 7) else {
19+
getMessages(completion: completion)
20+
}
21+
}
22+
23+
static func getMessages(completion: @escaping (MessageResponse?, MessageAPIError?) -> Void) {
24+
guard let token = UserDefaults.standard.string(forKey: "deviceTokenForAPN") else {
25+
completion(nil, .RequestError)
26+
return
27+
}
28+
//Get relevat encounter data
29+
guard let persistentContainer =
30+
EncounterDB.shared.persistentContainer else {
31+
completion(nil, .RequestError)
2932
return
30-
}
33+
}
34+
let managedContext = persistentContainer.newBackgroundContext()
35+
guard let encounterLastWeekRequest = Encounter.fetchEncountersInLast(days: 7) else {
36+
completion(nil, .RequestError)
37+
return
38+
}
39+
40+
do {
41+
//fetch last week encounters count
42+
let weekEncounters = try managedContext.count(for: encounterLastWeekRequest)
43+
let healthcheck = BluetraceManager.shared.isBluetoothOn() && BluetraceManager.shared.isBluetoothAuthorized() ?
44+
healthCheckParamValue.OK :
45+
healthCheckParamValue.POSSIBLE_ERROR
46+
let encounterCheck = weekEncounters > 0 ? healthCheckParamValue.OK : healthCheckParamValue.POSSIBLE_ERROR
3147

32-
do {
33-
//fetch last week encounters count
34-
let weekEncounters = try managedContext.count(for: encounterLastWeekRequest)
35-
let healthcheck = (BluetraceManager.shared.isBluetoothOn() &&
36-
BluetraceManager.shared.isBluetoothAuthorized() &&
37-
weekEncounters > 0 ? healthCheckParamValue.OK : healthCheckParamValue.POSSIBLE_ERROR)
38-
39-
// Make API call to get messages
40-
let messageRequest = MessageRequest(remotePushToken: token, healthcheck: healthcheck)
41-
getMessages(msgRequest: messageRequest, completion: completion)
42-
43-
} catch let error as NSError {
44-
DLog("Could not fetch encounter(s) from db. \(error), \(error.userInfo)")
45-
}
48+
// Make API call to get messages
49+
let messageRequest = MessageRequest(remotePushToken: token, healthcheck: healthcheck, encountershealth: encounterCheck)
50+
getMessages(msgRequest: messageRequest, completion: completion)
51+
52+
} catch let error as NSError {
53+
completion(nil, .RequestError)
54+
DLog("Could not fetch encounter(s) from db. \(error), \(error.userInfo)")
4655
}
4756
}
4857

@@ -62,36 +71,40 @@ class MessageAPI {
6271

6372
if lastChecked > 0 {
6473
let lastCheckedDate = Date(timeIntervalSince1970: lastChecked)
65-
let components = calendar.dateComponents([.day], from: lastCheckedDate, to: currentDate)
74+
let components = calendar.dateComponents([.hour], from: lastCheckedDate, to: currentDate)
6675

67-
if let numDays = components.day {
68-
shouldGetMessages = numDays > 0
76+
if let numHours = components.hour {
77+
shouldGetMessages = numHours > 4
6978
}
7079
}
7180

7281
return shouldGetMessages
7382
}
7483

7584
private static func getMessages(msgRequest: MessageRequest,
76-
completion: @escaping (MessageResponse?, Swift.Error?) -> Void) {
85+
completion: @escaping (MessageResponse?, MessageAPIError?) -> Void) {
7786
let keychain = KeychainSwift()
7887
guard let apiHost = PlistHelper.getvalueFromInfoPlist(withKey: "API_Host", plistName: "CovidSafe-config") else {
88+
completion(nil, .RequestError)
7989
return
8090
}
8191

8292
guard let token = keychain.get("JWT_TOKEN") else {
83-
completion(nil, nil)
93+
completion(nil, .RequestError)
8494
return
8595
}
8696
let headers: HTTPHeaders = [
8797
"Authorization": "Bearer \(token)"
8898
]
8999

100+
let preferredLanguages = Locale.preferredLanguages.count > 5 ? Locale.preferredLanguages[0...5].joined(separator: ",") : Locale.preferredLanguages.joined(separator: ",")
101+
90102
var params: [String : Any] = [
91103
"os" : "ios-\(UIDevice.current.systemVersion)",
92104
"healthcheck" : msgRequest.healthcheck.rawValue,
93-
"preferredlanguages": Locale.preferredLanguages
94-
]
105+
"encountershealth" : msgRequest.encountershealth.rawValue,
106+
"preferredlanguages": preferredLanguages
107+
]
95108

96109
if let buildString = Bundle.main.version {
97110
params["appversion"] = "\(buildString)"
@@ -104,20 +117,35 @@ class MessageAPI {
104117
parameters: params,
105118
headers: headers
106119
).validate().responseDecodable(of: MessageResponse.self) { (response) in
107-
switch response.result {
108-
case .success:
109-
guard let messageResponse = response.value else { return }
110-
111-
// save successful timestamp
112-
let calendar = NSCalendar.current
113-
let currentDate = calendar.startOfDay(for: Date())
120+
switch response.result {
121+
case .success:
122+
guard let messageResponse = response.value else { return }
123+
124+
// save successful timestamp
125+
let minutesToDefer = Int.random(in: 0..<10)
126+
let calendar = NSCalendar.current
127+
let currentDate = Date()
128+
if let deferredDate = calendar.date(byAdding: .minute, value: minutesToDefer, to: currentDate) {
129+
UserDefaults.standard.set(deferredDate.timeIntervalSince1970, forKey: keyLastApiUpdate)
130+
} else {
114131
UserDefaults.standard.set(currentDate.timeIntervalSince1970, forKey: keyLastApiUpdate)
115-
UserDefaults.standard.set(Bundle.main.version, forKey: keyLastVersionChecked)
116-
117-
completion(messageResponse, nil)
118-
case let .failure(error):
119-
completion(nil, error)
120132
}
133+
UserDefaults.standard.set(Bundle.main.version, forKey: keyLastVersionChecked)
134+
135+
completion(messageResponse, nil)
136+
case .failure(_):
137+
guard let statusCode = response.response?.statusCode else {
138+
completion(nil, .UnknownError)
139+
return
140+
}
141+
if (statusCode == 200) {
142+
completion(nil, .ResponseError)
143+
}
144+
if (statusCode >= 400 && statusCode < 500) {
145+
completion(nil, .RequestError)
146+
}
147+
completion(nil, .ServerError)
148+
}
121149
}
122150
}
123151
}
@@ -131,6 +159,7 @@ enum healthCheckParamValue: String {
131159
struct MessageRequest {
132160
var remotePushToken: String?
133161
var healthcheck: healthCheckParamValue
162+
var encountershealth: healthCheckParamValue
134163
}
135164

136165
struct MessageResponse: Decodable {
@@ -154,3 +183,10 @@ struct Message: Decodable {
154183
case destination
155184
}
156185
}
186+
187+
enum MessageAPIError: Error {
188+
case RequestError
189+
case ResponseError
190+
case ServerError
191+
case UnknownError
192+
}

CovidSafe/API/StatisticsAPI.swift

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//
2+
// MessageAPI.swift
3+
// CovidSafe
4+
//
5+
// Copyright © 2020 Australian Government. All rights reserved.
6+
//
7+
8+
import Foundation
9+
import Alamofire
10+
import KeychainSwift
11+
12+
class StatisticsAPI {
13+
14+
static let keyCovidStatistics = "keyCovidStatistics"
15+
16+
static func getStatistics(completion: @escaping (StatisticsResponse?, MessageAPIError?) -> Void) {
17+
let keychain = KeychainSwift()
18+
guard let apiHost = PlistHelper.getvalueFromInfoPlist(withKey: "API_Host", plistName: "CovidSafe-config") else {
19+
completion(nil, .RequestError)
20+
return
21+
}
22+
23+
guard let token = keychain.get("JWT_TOKEN") else {
24+
completion(nil, .RequestError)
25+
return
26+
}
27+
let headers: HTTPHeaders = [
28+
"Authorization": "Bearer \(token)"
29+
]
30+
31+
CovidNetworking.shared.session.request("\(apiHost)/statistics",
32+
method: .get,
33+
headers: headers
34+
).validate().responseDecodable(of: StatisticsResponse.self) { (response) in
35+
switch response.result {
36+
case .success:
37+
guard let statisticsResponse = response.value else { return }
38+
let statsData = try? PropertyListEncoder().encode(statisticsResponse)
39+
UserDefaults.standard.set(statsData, forKey: keyCovidStatistics)
40+
41+
completion(statisticsResponse, nil)
42+
case .failure(_):
43+
var lastStats: StatisticsResponse? = nil
44+
if let savedStats = UserDefaults.standard.data(forKey: keyCovidStatistics) {
45+
lastStats = try? PropertyListDecoder().decode(StatisticsResponse.self, from: savedStats)
46+
}
47+
guard let statusCode = response.response?.statusCode else {
48+
completion(lastStats, .UnknownError)
49+
return
50+
}
51+
if (statusCode == 200) {
52+
completion(lastStats, .ResponseError)
53+
}
54+
if (statusCode >= 400 && statusCode < 500) {
55+
completion(lastStats, .RequestError)
56+
}
57+
completion(lastStats, .ServerError)
58+
}
59+
}
60+
}
61+
}
62+
63+
struct StatisticsResponse: Codable {
64+
65+
let updatedDate: String?
66+
67+
let national: StateTerritoryStatistics?
68+
let act: StateTerritoryStatistics?
69+
let nsw: StateTerritoryStatistics?
70+
let nt: StateTerritoryStatistics?
71+
let qld: StateTerritoryStatistics?
72+
let sa: StateTerritoryStatistics?
73+
let tas: StateTerritoryStatistics?
74+
let vic: StateTerritoryStatistics?
75+
let wa: StateTerritoryStatistics?
76+
77+
enum CodingKeys: String, CodingKey {
78+
case updatedDate = "updated_date"
79+
case national
80+
case act
81+
case nsw
82+
case nt
83+
case qld
84+
case sa
85+
case tas
86+
case vic
87+
case wa
88+
}
89+
}
90+
91+
struct StateTerritoryStatistics: Codable {
92+
let totalCases: Int?
93+
let activeCases: Int?
94+
let newCases: Int?
95+
let recoveredCases: Int?
96+
let deaths: Int?
97+
98+
enum CodingKeys: String, CodingKey {
99+
case totalCases = "total_cases"
100+
case activeCases = "active_cases"
101+
case newCases = "new_cases"
102+
case recoveredCases = "recovered_cases"
103+
case deaths
104+
}
105+
}

CovidSafe/AppDelegate.swift

+16-23
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
4343
UNUserNotificationCenter.current().delegate = self
4444
NotificationCenter.default.addObserver(self, selector:#selector(jwtExpired(_:)),name: .jwtExpired, object: nil)
4545

46-
setupBluetoothPNStatusCallback()
47-
4846
motionManager = CMMotionManager()
4947
startAccelerometerUpdates()
5048

@@ -91,25 +89,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
9189

9290
// - Local Notifications
9391

94-
fileprivate func setupBluetoothPNStatusCallback() {
95-
96-
let btStatusMagicNumber = Int.random(in: 0 ... PushNotificationConstants.btStatusPushNotifContents.count - 1)
97-
98-
BluetraceManager.shared.bluetoothDidUpdateStateCallback = { [unowned self] state in
99-
guard state != .resetting else {
100-
// If the bt is just resetting no need to prompt the user here
101-
return
102-
}
103-
if UserDefaults.standard.bool(forKey: "turnedOnBluetooth") && !BluetraceManager.shared.isBluetoothOn() {
104-
if !UserDefaults.standard.bool(forKey: "sentBluetoothStatusNotif") {
105-
UserDefaults.standard.set(true, forKey: "sentBluetoothStatusNotif")
106-
self.triggerIntervalLocalPushNotifications(pnContent: PushNotificationConstants.btStatusPushNotifContents[btStatusMagicNumber], identifier: "bluetoothStatusNotifId")
107-
return
108-
}
109-
}
110-
}
111-
}
112-
11392
fileprivate func getReminderNotificationsIdentifiers() -> [String] {
11493
var identifiers: [String] = []
11594
for interval in intervals {
@@ -380,7 +359,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
380359
}
381360
#endif
382361

383-
if payload["content-available"] as? Int == 1 && payload["category"] as? String == "UPDATE_STATS" {
362+
if payload["content-available"] as? Int == 1 && payload["category"] as? String == PushNotificationCategory.UPDATE_STATS {
384363
DLog("Notification is category: UPDATE_STATS")
385364
MessageAPI.getMessagesIfNeeded() { (messageResponse, error) in
386365
if let error = error {
@@ -405,6 +384,15 @@ extension Notification.Name {
405384
static let enableUserInteraction = Notification.Name("enableUserInteraction")
406385
static let jwtExpired = Notification.Name("jwtExpired")
407386
static let encounterRecorded = Notification.Name("encounterRecorded")
387+
static let shouldUpdateAppFromMessages = Notification.Name("shouldUpdateAppFromMessages")
388+
}
389+
390+
struct PushNotificationCategory {
391+
static let UPDATE_STATS = "UPDATE_STATS"
392+
static let UPDATE_APP = "UPDATE_APP"
393+
static let POSSIBLE_ISSUE = "POSSIBLE_ISSUE"
394+
static let NO_CHECKIN = "NO_CHECKIN"
395+
static let POSSIBLE_ENCOUNTER_ERROR = "POSSIBLE_ENCOUNTER_ERROR"
408396
}
409397

410398
extension AppDelegate : UNUserNotificationCenterDelegate {
@@ -429,11 +417,16 @@ extension AppDelegate : UNUserNotificationCenterDelegate {
429417
if let payload = userInfo["aps"] as? [String: AnyObject],
430418
let notificationType = payload["category"] as? String {
431419

432-
if notificationType == "UPDATE_APP",
420+
if notificationType == PushNotificationCategory.UPDATE_APP,
433421
let url = URL(string: "itms-apps://itunes.apple.com/app/id1509242894"),
434422
UIApplication.shared.canOpenURL(url) {
435423
UIApplication.shared.open(url, options: [:], completionHandler: nil)
436424
}
425+
else if notificationType == PushNotificationCategory.POSSIBLE_ISSUE ||
426+
notificationType == PushNotificationCategory.NO_CHECKIN ||
427+
notificationType == PushNotificationCategory.POSSIBLE_ENCOUNTER_ERROR {
428+
UserDefaults.standard.set(true, forKey: "PerformHealthChecks")
429+
}
437430
}
438431
completionHandler()
439432
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "chevron-right-white.pdf",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "Group 123.pdf",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
}
12+
}
Binary file not shown.

0 commit comments

Comments
 (0)