diff --git a/app/ExposureHistoryContext.tsx b/app/ExposureHistoryContext.tsx index 77ac60db46..32e409d85b 100644 --- a/app/ExposureHistoryContext.tsx +++ b/app/ExposureHistoryContext.tsx @@ -18,6 +18,7 @@ interface ExposureHistoryState { userHasNewExposure: boolean; observeExposures: () => void; resetExposures: () => void; + getCurrentExposures: () => void; } const initialState = { @@ -26,6 +27,7 @@ const initialState = { userHasNewExposure: true, observeExposures: () => {}, resetExposures: () => {}, + getCurrentExposures: () => {}, }; const ExposureHistoryContext = createContext( @@ -42,6 +44,7 @@ export interface ExposureEventsStrategy { exposureInfo: ExposureInfo, calendarOptions: ExposureCalendarOptions, ) => ExposureHistory; + getCurrentExposures: (cb: (exposureInfo: ExposureInfo) => void) => void; } interface ExposureHistoryProps { @@ -84,6 +87,10 @@ const ExposureHistoryProvider: FunctionComponent = ({ return subscription.remove; }, [exposureInfoSubscription, toExposureHistory]); + useEffect(() => { + getCurrentExposures(); + }, [toExposureHistory]); + const observeExposures = () => { setUserHasNewExposure(false); }; @@ -92,6 +99,17 @@ const ExposureHistoryProvider: FunctionComponent = ({ setUserHasNewExposure(true); }; + const getCurrentExposures = async () => { + const cb = (exposureInfo: ExposureInfo) => { + const exposureHistory = toExposureHistory( + exposureInfo, + blankHistoryConfig, + ); + setExposureHistory(exposureHistory); + }; + exposureEventsStrategy.getCurrentExposures(cb); + }; + const hasBeenExposed = false; return ( = ({ userHasNewExposure, observeExposures, resetExposures, + getCurrentExposures, }}> {children} diff --git a/app/bt/PermissionsContext.tsx b/app/bt/PermissionsContext.tsx index c1768e6969..c83239e35c 100644 --- a/app/bt/PermissionsContext.tsx +++ b/app/bt/PermissionsContext.tsx @@ -84,7 +84,6 @@ const PermissionsProvider = ({ const checkENPermission = () => { const handleNativeResponse = (status: ENPermissionStatus) => { - console.log('checking ', status); setExposureNotificationsPermission(status); }; BTNativeModule.getCurrentENPermissionsStatus(handleNativeResponse); diff --git a/app/bt/index.ts b/app/bt/index.ts index 4bc44ebe78..935a9cb415 100644 --- a/app/bt/index.ts +++ b/app/bt/index.ts @@ -10,6 +10,7 @@ import { ExposureEventsStrategy } from '../ExposureHistoryContext'; const btExposureEventContext: ExposureEventsStrategy = { exposureInfoSubscription: BTNativeModule.subscribeToExposureEvents, toExposureHistory: toExposureHistory, + getCurrentExposures: BTNativeModule.getCurrentExposures, }; const btStrategy: TracingStrategy = { diff --git a/app/bt/nativeModule.ts b/app/bt/nativeModule.ts index 75403b545b..3381548b45 100644 --- a/app/bt/nativeModule.ts +++ b/app/bt/nativeModule.ts @@ -72,6 +72,17 @@ export const getCurrentENPermissionsStatus = async ( }); }; +// Exposure History Module +const exposureHistoryModule = NativeModules.ExposureHistoryModule; +export const getCurrentExposures = async ( + cb: (exposureInfo: ExposureInfo) => void, +): Promise => { + exposureHistoryModule.getCurrentExposures((rawExposure: string) => { + const rawExposures: RawExposure[] = JSON.parse(rawExposure); + cb(toExposureInfo(rawExposures)); + }); +}; + // Exposure Key Module const exposureKeyModule = NativeModules.ExposureKeyModule; diff --git a/app/factories/tracingStrategy.tsx b/app/factories/tracingStrategy.tsx index 60735c4be7..00d4c1f528 100644 --- a/app/factories/tracingStrategy.tsx +++ b/app/factories/tracingStrategy.tsx @@ -17,6 +17,7 @@ export default Factory.define(() => ({ return { remove: () => {} }; }, toExposureHistory: () => [], + getCurrentExposures: () => {}, }, permissionsProvider: PermissionsProvider, homeScreenComponent: HomeScreen, diff --git a/app/gps/exposureInfo.ts b/app/gps/exposureInfo.ts index 750615e219..57bfcad2fc 100644 --- a/app/gps/exposureInfo.ts +++ b/app/gps/exposureInfo.ts @@ -42,6 +42,10 @@ const ExposureEvents: GPSExposureHistoryEventEmitter = { }, }; +export const getCurrentExposures = async ( + _cb: (exposureInfo: ExposureInfo) => void, +): Promise => {}; + export const subscribeToExposureEvents = ( cb: (exposureHistory: ExposureInfo) => void, ): { remove: () => void } => { diff --git a/app/gps/index.ts b/app/gps/index.ts index 520f1a5c90..8e6253b1a8 100644 --- a/app/gps/index.ts +++ b/app/gps/index.ts @@ -2,7 +2,7 @@ import { TracingStrategy } from '../tracingStrategy'; import { PermissionsProvider } from './PermissionsContext'; import Home from './Home'; import ExportStack from './ExportStack'; -import { subscribeToExposureEvents } from './exposureInfo'; +import { subscribeToExposureEvents, getCurrentExposures } from './exposureInfo'; import { useGPSCopyContent, gpsAssets } from './content'; import { ExposureEventsStrategy } from '../ExposureHistoryContext'; import { toExposureHistory } from './intersect/exposureHistory'; @@ -10,6 +10,7 @@ import { toExposureHistory } from './intersect/exposureHistory'; const gpsExposureEventContext: ExposureEventsStrategy = { exposureInfoSubscription: subscribeToExposureEvents, toExposureHistory: toExposureHistory, + getCurrentExposures: getCurrentExposures, }; const gpsStrategy: TracingStrategy = { diff --git a/ios/BT/API/Model/Exposure.swift b/ios/BT/API/Model/Exposure.swift index 09633f703b..f79346637d 100644 --- a/ios/BT/API/Model/Exposure.swift +++ b/ios/BT/API/Model/Exposure.swift @@ -4,11 +4,11 @@ import RealmSwift @objcMembers class Exposure: Object, Codable { - var id: String! - var date: Int! - var duration: TimeInterval! - var totalRiskScore: ENRiskScore! - var transmissionRiskLevel: ENRiskLevel! + @objc dynamic var id: String = .default + @objc dynamic var date: Int = 0 + @objc dynamic var duration: Double = 0.0 + @objc dynamic var totalRiskScore: Int = 0 + @objc dynamic var transmissionRiskLevel: Int = 0 init(id: String, date: Int, @@ -18,8 +18,8 @@ class Exposure: Object, Codable { self.id = id self.date = date self.duration = duration - self.totalRiskScore = totalRiskScore - self.transmissionRiskLevel = transmissionRiskLevel + self.totalRiskScore = Int(totalRiskScore) + self.transmissionRiskLevel = Int(transmissionRiskLevel) super.init() } @@ -31,4 +31,14 @@ class Exposure: Object, Codable { "id" } + var asDictionary : [String: Any] { + return [ + "id": id, + "date": date, + "duration": duration, + "totalRiskScore": totalRiskScore, + "transmissionRiskLevel": transmissionRiskLevel + ] + } + } diff --git a/ios/BT/API/Model/UserState.swift b/ios/BT/API/Model/UserState.swift index f6666aae15..435137b3ae 100644 --- a/ios/BT/API/Model/UserState.swift +++ b/ios/BT/API/Model/UserState.swift @@ -10,7 +10,7 @@ class UserState: Object { @objc dynamic var remainingDailyFileProcessingCapacity: Int = Constants.dailyFileProcessingCapacity @objc dynamic var exposureDetectionErrorLocalizedDescription: String = .default @objc dynamic var urlOfMostRecentlyDetectedKeyFile: String = .default - dynamic var exposures: List = List() + let exposures: List = List() override class func primaryKey() -> String? { "id" diff --git a/ios/BT/ExposureManager.swift b/ios/BT/ExposureManager.swift index 54274145b8..2d50db9af2 100644 --- a/ios/BT/ExposureManager.swift +++ b/ios/BT/ExposureManager.swift @@ -180,9 +180,6 @@ final class ExposureManager: NSObject { totalRiskScore: exposure.totalRiskScore, transmissionRiskLevel: exposure.transmissionRiskLevel) } - if !newExposures.isEmpty { - self.postNewExposureNotification() - } self.finish(.success(newExposures), processedFileCount: processedFileCount, lastProcessedUrlPath: lastProcessedUrlPath, @@ -345,23 +342,6 @@ final class ExposureManager: NSObject { #endif } - func postNewExposureNotification() { - let identifier = String.exposureDetectionErrorNotificationIdentifier - - let content = UNMutableNotificationContent() - content.title = String.newExposureNotificationTitle.localized - content.body = String.newExposureNotificationBody.localized - content.sound = .default - let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil) - UNUserNotificationCenter.current().add(request) { error in - DispatchQueue.main.async { - if let error = error { - print("Error showing error user notification: \(error)") - } - } - } - } - func urlPathsToProcess(_ urlPaths: [String]) -> [String] { let startIdx = startIndex(for: urlPaths) let endIdx = min(startIdx + BTSecureStorage.shared.userState.remainingDailyFileProcessingCapacity, urlPaths.count) @@ -379,6 +359,11 @@ final class ExposureManager: NSObject { } } + @objc var currentExposures: String { + let exposures = Array(BTSecureStorage.shared.userState.exposures) + return exposures.jsonStringRepresentation() + } + func updateRemainingFileCapacity() { guard let lastResetDate = BTSecureStorage.shared.userState.dateLastPerformedFileCapacityReset else { BTSecureStorage.shared.dateLastPerformedFileCapacityReset = Date() diff --git a/ios/BT/Extensions/Foundation/Encodable+Extensions.swift b/ios/BT/Extensions/Foundation/Encodable+Extensions.swift index ecb08390b6..d6839d74d4 100644 --- a/ios/BT/Extensions/Foundation/Encodable+Extensions.swift +++ b/ios/BT/Extensions/Foundation/Encodable+Extensions.swift @@ -1,8 +1,7 @@ -import RealmSwift - -extension List where Element: Encodable { - public func encode(to coder: Encoder) throws { - var container = coder.unkeyedContainer() - try container.encode(contentsOf: self) +extension Encodable { + func jsonStringRepresentation() -> String { + let encodedValue = try! JSONEncoder().encode(self) + let string = String(data: encodedValue, encoding: .utf8)! + return string } } diff --git a/ios/BT/Extensions/Foundation/List+Extensions.swift b/ios/BT/Extensions/Foundation/List+Extensions.swift new file mode 100644 index 0000000000..ecb08390b6 --- /dev/null +++ b/ios/BT/Extensions/Foundation/List+Extensions.swift @@ -0,0 +1,8 @@ +import RealmSwift + +extension List where Element: Encodable { + public func encode(to coder: Encoder) throws { + var container = coder.unkeyedContainer() + try container.encode(contentsOf: self) + } +} diff --git a/ios/BT/Extensions/Other/ExposureManager+Extensions.swift b/ios/BT/Extensions/Other/ExposureManager+Extensions.swift index baf8ffe247..be615464aa 100644 --- a/ios/BT/Extensions/Other/ExposureManager+Extensions.swift +++ b/ios/BT/Extensions/Other/ExposureManager+Extensions.swift @@ -33,7 +33,6 @@ extension ExposureManager { ExposureManager.shared.postExposureDetectionErrorNotification() callback([NSNull(), "Exposure deteaction error message: \(BTSecureStorage.shared.exposureDetectionErrorLocalizedDescription)"]) case .simulateExposure: - ExposureManager.shared.postNewExposureNotification() let exposure = Exposure(id: UUID().uuidString, date: Date().posixRepresentation - Int(TimeInterval.random(in: 0...13)) * 24 * 60 * 60 * 1000, duration: TimeInterval(Int.random(in: 1...10) * 60 * 5 * 1000), diff --git a/ios/BT/Storage/BTSecureStorage.swift b/ios/BT/Storage/BTSecureStorage.swift index 69f255a7c0..3d0688183c 100644 --- a/ios/BT/Storage/BTSecureStorage.swift +++ b/ios/BT/Storage/BTSecureStorage.swift @@ -4,7 +4,7 @@ import RealmSwift final class BTSecureStorage: SafePathsSecureStorage { - static let shared = BTSecureStorage() + static let shared = BTSecureStorage(inMemory: false) override var keychainIdentifier: String { "\(Bundle.main.bundleIdentifier!).realm" @@ -17,6 +17,13 @@ final class BTSecureStorage: SafePathsSecureStorage { return realmConfig }() + override init(inMemory: Bool = false) { + super.init(inMemory: inMemory) + if !userStateExists { + resetUserState({ _ in }) + } + } + override func getRealmConfig() -> Realm.Configuration? { if let key = getEncryptionKey() { if (inMemory) { @@ -36,13 +43,17 @@ final class BTSecureStorage: SafePathsSecureStorage { return realm.object(ofType: UserState.self, forPrimaryKey: 0) ?? UserState() } + var userStateExists: Bool { + let realm = try! Realm(configuration: realmConfig) + return realm.object(ofType: UserState.self, forPrimaryKey: 0) != nil + } + func setUserValue(value: Value, keyPath: String, notificationName: Notification.Name) { let realm = try! Realm(configuration: realmConfig) try! realm.write { realm.create(UserState.self, value: [keyPath: value], update: .modified) - let encodedValue = try JSONEncoder().encode(value) - let stringRepresentation = String(data: encodedValue, encoding: .utf8)! - NotificationCenter.default.post(name: notificationName, object: stringRepresentation) + let jsonString = value.jsonStringRepresentation() + NotificationCenter.default.post(name: notificationName, object: jsonString) } } @@ -61,7 +72,9 @@ final class BTSecureStorage: SafePathsSecureStorage { func storeExposures(_ exposures: [Exposure]) { let realm = try! Realm(configuration: realmConfig) try! realm.write { - exposures.forEach { userState.exposures.append($0) } + userState.exposures.append(objectsIn: exposures) + let jsonString = userState.exposures.jsonStringRepresentation() + NotificationCenter.default.post(name: .ExposuresDidChange, object: jsonString) } } diff --git a/ios/BT/bridge/ExposureHistoryModule.m b/ios/BT/bridge/ExposureHistoryModule.m new file mode 100644 index 0000000000..0c5b98038f --- /dev/null +++ b/ios/BT/bridge/ExposureHistoryModule.m @@ -0,0 +1,17 @@ +#import +#import +#import +#import "BT-Swift.h" + +@interface ExposureHistoryModule: NSObject +@end + +@implementation ExposureHistoryModule + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(getCurrentExposures: (RCTResponseSenderBlock)callback) { + callback(@[[[ExposureManager shared] currentExposures]]); +} + +@end diff --git a/ios/BT/bridge/ExposureKeyModule.m b/ios/BT/bridge/ExposureKeyModule.m index 1158c3ad6d..ad834f4492 100644 --- a/ios/BT/bridge/ExposureKeyModule.m +++ b/ios/BT/bridge/ExposureKeyModule.m @@ -23,7 +23,7 @@ @implementation ExposureKeyModule resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { -// [[ExposureManager shared] getAndPostDiagnosisKeysWithCertificate:certificate HMACKey:HMACKey com] + [[ExposureManager shared] getAndPostDiagnosisKeysWithCertificate:certificate HMACKey:HMACKey resolve:resolve reject:reject]; } diff --git a/ios/COVIDSafePaths.xcodeproj/project.pbxproj b/ios/COVIDSafePaths.xcodeproj/project.pbxproj index 770231077f..ce432b032c 100644 --- a/ios/COVIDSafePaths.xcodeproj/project.pbxproj +++ b/ios/COVIDSafePaths.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ B576CC4324993F5200CDD9D9 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B576CC3D24993F4C00CDD9D9 /* Date+Extensions.swift */; }; B576CC4424993F5200CDD9D9 /* Encodable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B576CC4024993F4C00CDD9D9 /* Encodable+Extensions.swift */; }; B596C0722488127D00943B79 /* ENPermissionsModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B596C0712488127D00943B79 /* ENPermissionsModule.m */; }; + B59F4C3524BDF879007B09D5 /* ExposureHistoryModule.m in Sources */ = {isa = PBXBuildFile; fileRef = B59F4C3424BDF879007B09D5 /* ExposureHistoryModule.m */; }; + B59F4C3724BE0658007B09D5 /* List+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59F4C3624BE0658007B09D5 /* List+Extensions.swift */; }; B5A6FD6E24BC9E69007D328C /* ExposureManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A6FD6A24BC9E54007D328C /* ExposureManagerTests.swift */; }; B5C9723024B8A909007F4C0B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C9722F24B8A909007F4C0B /* Constants.swift */; }; B5E29D54249E3BE100E686DC /* ExposureManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E29D53249E3BE100E686DC /* ExposureManager+Extensions.swift */; }; @@ -328,6 +330,8 @@ B576CC4024993F4C00CDD9D9 /* Encodable+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Encodable+Extensions.swift"; sourceTree = ""; }; B57BA02624A3CD3F00FBAA05 /* KeySubmissionModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KeySubmissionModule.m; sourceTree = ""; }; B596C0712488127D00943B79 /* ENPermissionsModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ENPermissionsModule.m; sourceTree = ""; }; + B59F4C3424BDF879007B09D5 /* ExposureHistoryModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ExposureHistoryModule.m; sourceTree = ""; }; + B59F4C3624BE0658007B09D5 /* List+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "List+Extensions.swift"; sourceTree = ""; }; B5A6FD6A24BC9E54007D328C /* ExposureManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExposureManagerTests.swift; sourceTree = ""; }; B5C490A72498F84000588A5F /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = ""; }; B5C9722F24B8A909007F4C0B /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; @@ -749,6 +753,7 @@ B5E79436249E666B00BD8596 /* Array+Extensions.swift */, B576CC3D24993F4C00CDD9D9 /* Date+Extensions.swift */, B576CC4024993F4C00CDD9D9 /* Encodable+Extensions.swift */, + B59F4C3624BE0658007B09D5 /* List+Extensions.swift */, B5FBB0D624916DE100433980 /* Notification+Extensions.swift */, ); path = Foundation; @@ -878,6 +883,7 @@ B596C0712488127D00943B79 /* ENPermissionsModule.m */, C550A66524B7906F002CA7F2 /* ExposureKeyModule.m */, B57BA02624A3CD3F00FBAA05 /* KeySubmissionModule.m */, + B59F4C3424BDF879007B09D5 /* ExposureHistoryModule.m */, B5FB3B42248BD61A001DB1D5 /* DebugMenuModule.m */, B5582D43249943DE001458A9 /* ExposureEventEmitter.m */, ); @@ -1393,8 +1399,10 @@ B5FB3B43248BD61A001DB1D5 /* DebugMenuModule.m in Sources */, B5582D47249943E5001458A9 /* ExposureEventEmitter.m in Sources */, C5C850C7248014F700A494CA /* main.m in Sources */, + B59F4C3524BDF879007B09D5 /* ExposureHistoryModule.m in Sources */, B596C0722488127D00943B79 /* ENPermissionsModule.m in Sources */, B5FC37D8248A784F006474EB /* DiagnosisKeyRequests.swift in Sources */, + B59F4C3724BE0658007B09D5 /* List+Extensions.swift in Sources */, B5FBB0D324916B3600433980 /* String+Extensions.swift in Sources */, B507A9EE24A197FF00E039D5 /* DownloadedPackage.swift in Sources */, B5FC37F6248A9968006474EB /* ENTemporaryExposureKey+Extensions.swift in Sources */, diff --git a/ios/COVIDSafePaths.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/COVIDSafePaths.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index 9d4c43be97..0000000000 --- a/ios/COVIDSafePaths.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,16 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "Scrypt", - "repositoryURL": "https://github.com/MichaelNeas/swift-scrypt.git", - "state": { - "branch": "master", - "revision": "ef765c45ee568f662e2111ba59f51badfde56da2", - "version": null - } - } - ] - }, - "version": 1 -} diff --git a/pathcheck-mobile-resources b/pathcheck-mobile-resources index 5127372863..3c98ee0eb0 160000 --- a/pathcheck-mobile-resources +++ b/pathcheck-mobile-resources @@ -1 +1 @@ -Subproject commit 5127372863183c2b6d1ab7d03f0364db60ef7bd8 +Subproject commit 3c98ee0eb0c01f17c23b417e67828152b8d68662