Skip to content

Commit

Permalink
Merge pull request covidwatchorg#71 from covid19risk/updateGlobalState
Browse files Browse the repository at this point in the history
Global state handling updates
  • Loading branch information
ibeckermayer authored Apr 17, 2020
2 parents dfac664 + 06c1d9a commit 5293966
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 61 deletions.
2 changes: 0 additions & 2 deletions COVIDWatch iOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var isUserSickObservation: NSKeyValueObservation?

var localContactEventsUploader: LocalContactEventsUploader?
var currentUserExposureNotifier: CurrentUserExposureNotifier?

// swiftlint:disable:next function_body_length
func application(
Expand All @@ -42,7 +41,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
self.requestUserNotificationAuthorization(provisional: true)
self.configureIsCurrentUserSickObserver()
self.localContactEventsUploader = LocalContactEventsUploader()
self.currentUserExposureNotifier = CurrentUserExposureNotifier()
self.bluetoothController = BluetoothController()
self.configureIsContactEventLoggingEnabledObserver()
}
Expand Down
59 changes: 35 additions & 24 deletions COVIDWatch iOS/Data/UserDefaults+Shared.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ extension UserDefaults {

public struct Key {
public static let isUserSick = "isUserSick"
public static let didUserMakeContactWithSickUser = "didUserMakeContactWithSickUser"
public static let wasCurrentUserNotifiedOfExposure = "wasCurrentUserNotifiedOfExposure"
public static let mostRecentExposureDate = "mostRecentExposureDate"
public static let isContactEventLoggingEnabled = "isContactEventLoggingEnabled"
public static let lastContactEventsDownloadDate = "lastContactEventsDownloadDate"
public static let isFirstTimeUser = "isFirstTimeUser"
public static let lastTestedDate = "lastTestedDate"
public static let testLastSubmittedDate = "testLastSubmittedDate"

public static let registration: [String: Any] = [
isUserSick: false,
wasCurrentUserNotifiedOfExposure: false,
isContactEventLoggingEnabled: false,
isFirstTimeUser: true
]
Expand All @@ -34,58 +32,71 @@ extension UserDefaults {
}
}

@objc dynamic public var didUserMakeContactWithSickUser: Bool {
@objc dynamic public var isContactEventLoggingEnabled: Bool {
get {
return bool(forKey: Key.didUserMakeContactWithSickUser)
return bool(forKey: Key.isContactEventLoggingEnabled)
}
set {
setValue(newValue, forKey: Key.didUserMakeContactWithSickUser)
setValue(newValue, forKey: Key.isContactEventLoggingEnabled)
}
}

@objc dynamic public var wasCurrentUserNotifiedOfExposure: Bool {
@objc dynamic public var lastContactEventsDownloadDate: Date? {
get {
return bool(forKey: Key.wasCurrentUserNotifiedOfExposure)
return object(forKey: Key.lastContactEventsDownloadDate) as? Date
}
set {
setValue(newValue, forKey: Key.wasCurrentUserNotifiedOfExposure)
setValue(newValue, forKey: Key.lastContactEventsDownloadDate)
}
}

@objc dynamic public var isContactEventLoggingEnabled: Bool {
@objc dynamic public var isFirstTimeUser: Bool {
get {
return bool(forKey: Key.isContactEventLoggingEnabled)
return bool(forKey: Key.isFirstTimeUser)
}
set {
setValue(newValue, forKey: Key.isContactEventLoggingEnabled)
setValue(newValue, forKey: Key.isFirstTimeUser)
}
}

@objc dynamic public var lastContactEventsDownloadDate: Date? {
@objc dynamic public var testLastSubmittedDate: Date? {
get {
return object(forKey: Key.lastContactEventsDownloadDate) as? Date
return object(forKey: Key.testLastSubmittedDate) as? Date
}
set {
setValue(newValue, forKey: Key.lastContactEventsDownloadDate)
setValue(newValue, forKey: Key.testLastSubmittedDate)
}
}

@objc dynamic public var isFirstTimeUser: Bool {
// most recent date we've detected that this user was in contact with a reportedly sick user
@objc dynamic public var mostRecentExposureDate: Date? {
get {
return bool(forKey: Key.isFirstTimeUser)
return object(forKey: Key.mostRecentExposureDate) as? Date
}
set {
setValue(newValue, forKey: Key.isFirstTimeUser)
setValue(newValue, forKey: Key.mostRecentExposureDate)
}
}

@objc dynamic public var lastTestedDate: Date? {
// Helper function for determining whether user is at risk for COVID
// NOTE: Do not watch this property. Watch mostRecentExposureDate and use this in the callback
public var isUserAtRiskForCovid: Bool {
get {
return object(forKey: Key.lastTestedDate) as? Date
}
set {
setValue(newValue, forKey: Key.lastTestedDate)
if let mostRecentExposureDate = UserDefaults.shared.mostRecentExposureDate {
return isDWithinXDaysOfToday(D: mostRecentExposureDate, X: 14)
}
return false
}
}

// Helper function for determining whether user is eligible to submit (another) test
// NOTE: Do not watch this property. Watch testLastSubmittedDate and use this in the callback
public var isEligibleToSubmitTest: Bool {
get {
if let testLastSubmittedDate = UserDefaults.shared.testLastSubmittedDate {
return !isDWithinXDaysOfToday(D: testLastSubmittedDate, X: 14)
}
return true
}
}
}
10 changes: 9 additions & 1 deletion COVIDWatch iOS/UIComponents/InfoBanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,18 @@ class InfoBanner: UITextView {
self.font = UIFont(name: "Montserrat-Bold", size: fontSize)
self.textColor = .white
self.frame.size.width = screenWidth
self.frame.size.height = 100 * figmaToiOSVerticalScalingFactor
self.frame.size.height = 130 * figmaToiOSVerticalScalingFactor
self.isEditable = false
self.backgroundColor = UIColor.Secondary.Tangerine
self.isSelectable = false
let verticalInset = 44 * figmaToiOSVerticalScalingFactor
let horizontalInset = 43 * figmaToiOSHorizontalScalingFactor
self.textContainerInset = UIEdgeInsets(
top: verticalInset,
left: horizontalInset,
bottom: verticalInset,
right: horizontalInset
)
}

func draw(parentVC: UIViewController, centerX: CGFloat, originY: CGFloat) {
Expand Down
43 changes: 43 additions & 0 deletions COVIDWatch iOS/UIComponents/Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import UIKit

let MANUAL_STATE_TEST = true

class Menu: UIView {
var xIcon = UIImageView(image: UIImage(named: "x-icon"))
var menuItems: [MenuItem] = [
Expand Down Expand Up @@ -104,6 +106,47 @@ class Menu: UIView {

init() {
super.init(frame: CGRect())

if MANUAL_STATE_TEST {
self.menuItems.removeAll()
let globalState = UserDefaults.shared
let now = Date()
let threeDaysAgo = Calendar.current.date(byAdding: .day, value: -3, to: now)
let thirtyDaysAgo = Calendar.current.date(byAdding: .day, value: -30, to: now)
self.menuItems.append(contentsOf: [
MenuItem(text: "mostRecentExposure-30d", addLinkImg: false, onClick: {
globalState.mostRecentExposureDate = thirtyDaysAgo
print("Set mostRecentExposureDate to 30 days ago")
}),
MenuItem(text: "mostRecentExposure-3d", addLinkImg: false, onClick: {
globalState.mostRecentExposureDate = threeDaysAgo
print("Set mostRecentExposureDate to 3 days ago")
}),
MenuItem(text: "mostRecentExposure-nil", addLinkImg: false, onClick: {
globalState.mostRecentExposureDate = nil
print("Set mostRecentExposureDate to nil")
}),
MenuItem(text: "testLastSubmittedDate-30d", addLinkImg: false, onClick: {
globalState.testLastSubmittedDate = thirtyDaysAgo
print("Set testLastSubmittedDate to 30 days ago")
}),
MenuItem(text: "testLastSubmittedDate-3d", addLinkImg: false, onClick: {
globalState.testLastSubmittedDate = threeDaysAgo
print("Set testLastSubmittedDate to 3 days ago")
}),
MenuItem(text: "testLastSubmittedDate-nil", addLinkImg: false, onClick: {
globalState.testLastSubmittedDate = nil
print("Set testLastSubmittedDate to nil")
}),
MenuItem(text: "Toggle isUserSick", addLinkImg: false, onClick: {
globalState.isUserSick = !globalState.isUserSick
print("isUserSick set to \(globalState.isUserSick)")
// Enforce that global state is realistic
globalState.testLastSubmittedDate = nil
print("Set testLastSubmittedDate to nil")
})
])
}
}

required init?(coder: NSCoder) {
Expand Down
24 changes: 24 additions & 0 deletions COVIDWatch iOS/Utility/IsWithinXDaysOfToday.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// IsWithinXDaysOfToday.swift
// COVIDWatch iOS
//
// Created by Isaiah Becker-Mayer on 4/14/20.
// Copyright © 2020 IZE. All rights reserved.
//

import Foundation

func isDWithinXDaysOfToday(D: Date, X: Int) -> Bool {
let calendar = Calendar.current
let now = Date()

let components = calendar.dateComponents([.day], from: D, to: now)
if let numDays = components.day {
if numDays <= X {
return true
} else {
return false
}
}
return false // Should not reach here
}
2 changes: 1 addition & 1 deletion COVIDWatch iOS/ViewControllers/Confirm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class Confirm: UIViewController {

func onConfirm() {
UserDefaults.shared.isUserSick = true
UserDefaults.shared.lastTestedDate = Date()
UserDefaults.shared.testLastSubmittedDate = Date()
performSegue(withIdentifier: "confirmToHome", sender: self)
}
}
Expand Down
58 changes: 30 additions & 28 deletions COVIDWatch iOS/ViewControllers/Home.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,31 @@ class Home: BaseViewController {
var testedButton = Button(text: "Tested for COVID-19?",
subtext: "Share your result anonymously to help keep your community stay safe.")
var infoBanner = InfoBanner(text: "You may have been in contact with COVID-19")
var lastTestedDateObserver: NSKeyValueObservation?
var didUserMakeContactWithSickUserObserver: NSKeyValueObservation?
var testLastSubmittedDateObserver: NSKeyValueObservation?
var mostRecentExposureDateObserver: NSKeyValueObservation?
var isUserSickObserver: NSKeyValueObservation?
var observer: NSObjectProtocol?
var bluetoothPermission: BluetoothPermission?
let globalState = UserDefaults.shared

override func viewDidLoad() {
super.viewDidLoad()
lastTestedDateObserver = UserDefaults.shared.observe(
\.lastTestedDate,
testLastSubmittedDateObserver = globalState.observe(
\.testLastSubmittedDate,
options: [.initial, .new],
changeHandler: { (_, _) in
self.drawScreen()
})

didUserMakeContactWithSickUserObserver = UserDefaults.shared.observe(
\.didUserMakeContactWithSickUser,
mostRecentExposureDateObserver = globalState.observe(
\.mostRecentExposureDate,
options: [],
changeHandler: { (_, _) in
self.drawScreen()
})

isUserSickObserver = globalState.observe(
\.isUserSick,
options: [],
changeHandler: { (_, _) in
self.drawScreen()
Expand Down Expand Up @@ -147,8 +156,13 @@ class Home: BaseViewController {
private func drawScreen() {
// optionally draw the info banner and determine the coordinate for the top of the image
var imgTop: CGFloat
if UserDefaults.shared.didUserMakeContactWithSickUser {
if globalState.isUserAtRiskForCovid || globalState.isUserSick {
infoBanner.isHidden = false
if globalState.isUserSick {
infoBanner.text = "You reported that you tested positive for COVID-19"
} else {
infoBanner.text = "You may have been in contact with COVID-19"
}
infoBanner.draw(parentVC: self, centerX: view.center.x, originY: header.frame.maxY)
imgTop = infoBanner.frame.maxY + 21.0 * figmaToiOSVerticalScalingFactor
} else {
Expand All @@ -158,7 +172,7 @@ class Home: BaseViewController {
// determine image size
img.frame.size.width = 253 * figmaToiOSHorizontalScalingFactor
img.frame.size.height = 259 * figmaToiOSVerticalScalingFactor
if UserDefaults.shared.isFirstTimeUser && screenHeight <= 667 {
if globalState.isFirstTimeUser && screenHeight <= 667 {
img.frame.size.width /= 1.5
img.frame.size.height /= 1.5
}
Expand All @@ -167,14 +181,14 @@ class Home: BaseViewController {
self.view.addSubview(img)

var mainTextTop: CGFloat
if UserDefaults.shared.isFirstTimeUser {
if globalState.isFirstTimeUser {
largeText.isHidden = false
largeText.text = "You're all set!"
largeText.draw(parentVC: self,
centerX: view.center.x,
originY: img.frame.maxY + (22.0 * figmaToiOSVerticalScalingFactor))
mainTextTop = largeText.frame.maxY
} else if !UserDefaults.shared.didUserMakeContactWithSickUser {
} else if !globalState.isUserAtRiskForCovid {
largeText.isHidden = false
largeText.text = "Welcome Back!"
largeText.draw(parentVC: self,
Expand All @@ -188,11 +202,11 @@ class Home: BaseViewController {
}

// draw mainText with respect to largeText or img or not at all
if UserDefaults.shared.isFirstTimeUser {
if globalState.isFirstTimeUser {
// swiftlint:disable:next line_length
mainText.text = "Thank you for helping protect your communities. You will be notified of potential contact with COVID-19."
mainText.draw(parentVC: self, centerX: view.center.x, originY: mainTextTop)
} else if !UserDefaults.shared.didUserMakeContactWithSickUser {
} else if !globalState.isUserAtRiskForCovid {
// swiftlint:disable:next line_length
mainText.text = "Covid Watch has not detected exposure to COVID-19. Share the app with family and friends to help your community stay safe."
mainText.draw(parentVC: self, centerX: view.center.x, originY: mainTextTop)
Expand All @@ -203,7 +217,7 @@ class Home: BaseViewController {
mainText.textAlignment = .center
}

if UserDefaults.shared.didUserMakeContactWithSickUser || screenHeight <= 568 {
if globalState.isUserAtRiskForCovid || screenHeight <= 568 {
// Necessary to fit on screen
spreadButton.subtext?.removeFromSuperview()
spreadButton.subtext = nil
Expand All @@ -215,21 +229,9 @@ class Home: BaseViewController {
spreadButton = Button(text: "Share the app", subtext: "It works best when everyone uses it.")
}
// spreadButton drawn below because its position depends on whether testedButton is drawn

let calendar = Calendar.current

var hasBeenTestedInLast14Days = false
if let lastTestedDate = UserDefaults.shared.lastTestedDate {
let date1 = calendar.startOfDay(for: lastTestedDate)
let date2 = calendar.startOfDay(for: Date())

let components = calendar.dateComponents([.day], from: date1, to: date2)
if let numDays = components.day {
hasBeenTestedInLast14Days = numDays <= 14 ? true : false
}
}
spreadButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.share)))
if !hasBeenTestedInLast14Days {

if globalState.isEligibleToSubmitTest {
self.testedButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.test)))
let testedButtonTop: CGFloat = 668.0 * figmaToiOSVerticalScalingFactor
testedButton.draw(parentVC: self, centerX: view.center.x, originY: testedButtonTop)
Expand All @@ -254,7 +256,7 @@ class Home: BaseViewController {
centerX: view.center.x)
}

UserDefaults.shared.isFirstTimeUser = false
globalState.isFirstTimeUser = false

}

Expand Down
2 changes: 1 addition & 1 deletion COVIDWatch iOS/ViewControllers/Test.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class Test: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
}

@objc func submitNegative() {
UserDefaults.shared.lastTestedDate = Date()
UserDefaults.shared.testLastSubmittedDate = Date()
performSegue(withIdentifier: "testToHome", sender: self)
}

Expand Down
Loading

0 comments on commit 5293966

Please sign in to comment.