COVIDSafe code from version 2.6 (#51)

This commit is contained in:
COVIDSafe Support 2021-05-12 17:39:38 -07:00 committed by GitHub
parent 195798ddd5
commit 4d98b6c5e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 910 additions and 144 deletions

View file

@ -16,10 +16,15 @@ class AuthenticationAPI: CovidSafeAuthenticatedAPI {
return
}
guard let authHeaders = try? authenticatedHeaders() else {
completion(nil, .RequestError)
return
}
CovidNetworking.shared.session.request("\(apiHost)/issueInitialRefreshToken",
method: .post,
encoding: JSONEncoding.default,
headers: authenticatedHeaders
headers: authHeaders
).validate().responseDecodable(of: ChallengeResponse.self) { (response) in
switch response.result {
case .success:
@ -55,7 +60,7 @@ class AuthenticationAPI: CovidSafeAuthenticatedAPI {
return
}
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
guard let token = keychain.get("JWT_TOKEN"),
let refreshToken = keychain.get("REFRESH_TOKEN"),
@ -105,7 +110,7 @@ class AuthenticationAPI: CovidSafeAuthenticatedAPI {
}
static func issueTokensAPI(completion: @escaping (ChallengeResponse?, CovidSafeAPIError?) -> Void) {
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
// block api call only if refresh token exists, if it doesn't it means the app should get it for the first time
if UserDefaults.standard.bool(forKey: "ReauthenticationNeededKey") && keychain.get("REFRESH_TOKEN") != nil {
@ -132,13 +137,20 @@ class AuthenticationAPI: CovidSafeAuthenticatedAPI {
completion(response, nil)
}
} else {
let refreshTokenBeforeRefresh = keychain.get("REFRESH_TOKEN")
AuthenticationAPI.issueJWTTokenAPI { (response, error) in
guard let jwt = response?.token,
let refresh = response?.refreshToken,
error == nil else {
// set corrupted
// if the token in the system before the call is different than the one in keychain, this is a concurrency issue, the token was refresh already and that is why this failed. We should not start re-authentication
guard refreshTokenBeforeRefresh == keychain.get("REFRESH_TOKEN") else {
completion(response, .TokenAlreadyRefreshedError)
return
}
// set corrupted only when it has not been changed
UserDefaults.standard.set(true, forKey: "ReauthenticationNeededKey")
completion(response, .TokenExpiredError)
return

View file

@ -20,11 +20,17 @@ class ChangePostcodeAPI: CovidSafeAuthenticatedAPI {
let params = [
"postcode": newPostcode,
]
guard let authHeaders = try? authenticatedHeaders() else {
completion(.RequestError)
return
}
CovidNetworking.shared.session.request("\(apiHost)/device",
method: .post,
parameters: params,
encoding: JSONEncoding.default,
headers: authenticatedHeaders,
headers: authHeaders,
interceptor: CovidRequestRetrier(retries:3)).validate().responseDecodable(of: DeviceResponse.self) { (response) in
switch response.result {
case .success:

View file

@ -56,6 +56,8 @@ enum CovidSafeAPIError: Error {
case ResponseError
case ServerError
case TokenExpiredError
case TokenAlreadyRefreshedError
case MaxRegistrationError
case UnknownError
}
@ -63,18 +65,16 @@ class CovidSafeAuthenticatedAPI {
static var isBusy = false
static var authenticatedHeaders: HTTPHeaders {
get {
let keychain = KeychainSwift()
guard let token = keychain.get("JWT_TOKEN") else {
return []
}
let headers: HTTPHeaders = [
"Authorization": "Bearer \(token)"
]
return headers
static func authenticatedHeaders() throws -> HTTPHeaders {
let keychain = KeychainSwift.shared
guard let token = keychain.get("JWT_TOKEN") else {
throw CovidSafeAPIError.RequestError
}
let headers: HTTPHeaders = [
"Authorization": "Bearer \(token)"
]
return headers
}
static func processUnauthorizedError(_ data: Data) -> CovidSafeAPIError {

View file

@ -20,8 +20,12 @@ final class CovidRequestRetrier: Alamofire.RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
let refreshExists = keychain.get("REFRESH_TOKEN") != nil
// turn off geolock error
UserDefaults.standard.setValue(false, forKey: showGeolockErrorKey)
// prevent authenticated api calls if the re-registration flow has been started
if UserDefaults.standard.bool(forKey: "ReauthenticationNeededKey") &&
refreshExists {
@ -81,13 +85,19 @@ final class CovidRequestRetrier: Alamofire.RequestInterceptor {
return completion(.retryWithDelay(1.0))
}
if let serverHeader = response.headers.first(where: { $0.name == "Server" }),
response.statusCode == 403 && serverHeader.value == "CloudFront" {
UserDefaults.standard.setValue(true, forKey: showGeolockErrorKey)
return completion(.doNotRetryWithError(error))
}
if !triedRefresh &&
(response.statusCode == 403 || response.statusCode == 401) {
(response.statusCode == 401 || response.statusCode == 403) {
triedRefresh = true
retriesExecuted += 1
AuthenticationAPI.issueTokensAPI { (response, authError) in
// this will update the tokens automatically
guard let respError = authError, respError == .TokenExpiredError else {
if let respError = authError, respError == .TokenExpiredError {
completion(.doNotRetryWithError(error))
return
}

View file

@ -26,7 +26,7 @@ class GetTempIdAPI: CovidSafeAuthenticatedAPI {
"version" : apiVersion
]
guard authenticatedHeaders.count > 0 else {
guard let authHeaders = try? authenticatedHeaders(), authHeaders.count > 0 else {
completion(nil, nil, nil, .TokenExpiredError)
return
}
@ -34,7 +34,7 @@ class GetTempIdAPI: CovidSafeAuthenticatedAPI {
CovidNetworking.shared.session.request("\(apiHost)/getTempId",
method: .get,
parameters: params,
headers: authenticatedHeaders,
headers: authHeaders,
interceptor: CovidRequestRetrier(retries: 3)).validate().responseDecodable(of: TempIdResponse.self) { (response) in
switch response.result {
case .success:

View file

@ -65,7 +65,7 @@ class MessageAPI: CovidSafeAuthenticatedAPI {
var shouldGetMessages = true
let calendar = NSCalendar.current
let currentDate = calendar.startOfDay(for: Date())
let currentDate = Date()
// if the current version is newer than the last version checked, allow messages call
if let currVersionStr = Bundle.main.version, let currVersion = Int(currVersionStr), currVersion > versionChecked {
@ -77,7 +77,7 @@ class MessageAPI: CovidSafeAuthenticatedAPI {
let components = calendar.dateComponents([.hour], from: lastCheckedDate, to: currentDate)
if let numHours = components.hour {
shouldGetMessages = numHours > 4
shouldGetMessages = numHours >= 4
}
}
@ -109,10 +109,15 @@ class MessageAPI: CovidSafeAuthenticatedAPI {
isBusy = true
guard let authHeaders = try? authenticatedHeaders() else {
completion(nil, .RequestError)
return
}
CovidNetworking.shared.session.request("\(apiHost)/messages",
method: .get,
parameters: params,
headers: authenticatedHeaders,
headers: authHeaders,
interceptor: CovidRequestRetrier(retries: 3)
).validate().responseDecodable(of: MessageResponse.self) { (response) in
switch response.result {
@ -120,7 +125,7 @@ class MessageAPI: CovidSafeAuthenticatedAPI {
guard let messageResponse = response.value else { return }
// save successful timestamp
let minutesToDefer = Int.random(in: 0..<10)
let minutesToDefer = Int.random(in: 0..<30)
let calendar = NSCalendar.current
let currentDate = Date()
if let deferredDate = calendar.date(byAdding: .minute, value: minutesToDefer, to: currentDate) {

View file

@ -11,7 +11,7 @@ import Alamofire
class PhoneValidationAPI {
static func verifyPhoneNumber(regInfo: RegistrationRequest,
completion: @escaping (String?, Swift.Error?) -> Void) {
completion: @escaping (String?, CovidSafeAPIError?) -> Void) {
guard let apiHost = PlistHelper.getvalueFromInfoPlist(withKey: "API_Host", plistName: "CovidSafe-config") else {
return
@ -33,8 +33,22 @@ class PhoneValidationAPI {
case .success:
guard let authResponse = response.value else { return }
completion(authResponse.session, nil)
case let .failure(error):
completion(nil, error)
case .failure(_):
var apiError = CovidSafeAPIError.RequestError
if let respData = response.data {
do {
let errorResponse = try JSONDecoder().decode(CovidSafeErrorResponse.self, from: respData)
if errorResponse.message == "MaxRegistrationsReached" {
apiError = .MaxRegistrationError
}
} catch {
// unable to parse response
apiError = .ResponseError
}
}
completion(nil, apiError)
}
}
}

View file

@ -24,10 +24,15 @@ class RestrictionsAPI: CovidSafeAuthenticatedAPI {
let params = ["state": "\(forState.rawValue.lowercased())"]
guard let authHeaders = try? authenticatedHeaders() else {
completion(nil, .RequestError)
return
}
CovidNetworking.shared.session.request("\(apiHost)/restrictions",
method: .get,
parameters: params,
headers: authenticatedHeaders,
headers: authHeaders,
interceptor: CovidRequestRetrier(retries: 3)
).validate().responseDecodable(of: StateRestriction.self) { (response) in
switch response.result {

View file

@ -20,10 +20,15 @@ class StatisticsAPI: CovidSafeAuthenticatedAPI {
let parameters = ["state" : "\(forState.rawValue)"]
guard let authHeaders = try? authenticatedHeaders() else {
completion(nil, .RequestError)
return
}
CovidNetworking.shared.session.request("\(apiHost)/v2/statistics",
method: .get,
parameters: parameters,
headers: authenticatedHeaders,
headers: authHeaders,
interceptor: CovidRequestRetrier(retries: 3)
).validate().responseDecodable(of: StatisticsResponse.self) { (response) in
switch response.result {

View file

@ -21,7 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
setupCoredataDir()
let firstRun = UserDefaults.standard.bool(forKey: "HasBeenLaunched")
if( !firstRun ) {
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
keychain.clear()
UserDefaults.standard.set(true, forKey: "HasBeenLaunched")
}

View file

@ -21,6 +21,7 @@
<outlet property="covidStatisticsContainer" destination="eZl-C5-gSv" id="37c-CV-VgS"/>
<outlet property="covidStatisticsSection" destination="Aop-Ae-hRv" id="Qpd-Hv-uA8"/>
<outlet property="inactiveAppSectionView" destination="784-Jf-kOX" id="J3m-Pu-697"/>
<outlet property="inactiveGenericError" destination="5Pd-c8-CYx" id="Fez-q4-Oc5"/>
<outlet property="inactiveSettingsContent" destination="AUW-C2-ven" id="NNP-o9-zkK"/>
<outlet property="inactiveTokenExpiredView" destination="nxM-ji-ttb" id="vVC-KW-yek"/>
<outlet property="locationPermissionsView" destination="u4f-uR-ri3" id="70q-Jr-1cc"/>
@ -127,19 +128,19 @@
</constraints>
</view>
<stackView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="h36-8c-K2n">
<rect key="frame" x="0.0" y="120" width="414" height="1940.5"/>
<rect key="frame" x="0.0" y="120" width="414" height="2105"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bRs-XW-qzv" userLabel="StatusView">
<rect key="frame" x="0.0" y="0.0" width="414" height="1136"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="1300.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="rcS-nL-IAO">
<rect key="frame" x="0.0" y="0.0" width="414" height="1124"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="1288.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="784-Jf-kOX" userLabel="InactiveView">
<rect key="frame" x="0.0" y="0.0" width="414" height="696.5"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="861"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="1" translatesAutoresizingMaskIntoConstraints="NO" id="xti-6W-zko" userLabel="Inactive Stack View">
<rect key="frame" x="0.0" y="0.0" width="414" height="696.5"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="861"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4fe-SU-8Q6" userLabel="InActiveHeader">
<rect key="frame" x="0.0" y="0.0" width="414" height="81"/>
@ -246,7 +247,7 @@
</subviews>
<accessibility key="accessibilityConfiguration" label="Bluetooth permissions off">
<accessibilityTraits key="traits" button="YES"/>
<bool key="isElement" value="YES"/>
<bool key="isElement" value="NO"/>
</accessibility>
<constraints>
<constraint firstItem="reL-DQ-aFx" firstAttribute="leading" secondItem="nxM-ji-ttb" secondAttribute="leading" id="DNN-Va-LUA"/>
@ -255,8 +256,69 @@
<constraint firstAttribute="bottom" secondItem="reL-DQ-aFx" secondAttribute="bottom" id="qeE-Hu-OAb"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5Pd-c8-CYx" userLabel="Generic Error View">
<rect key="frame" x="0.0" y="369" width="414" height="163.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8Nu-a1-6fd" userLabel="Generic Error Bar">
<rect key="frame" x="0.0" y="0.0" width="414" height="163.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Error connectiong to server" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yw4-mY-Pvb">
<rect key="frame" x="16" y="16" width="238.5" height="0.0"/>
<constraints>
<constraint firstAttribute="height" id="K73-MG-bAA"/>
</constraints>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle3"/>
<color key="textColor" red="0.63921568630000003" green="0.098039215690000001" blue="0.098039215690000001" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Iw2-bN-Bjb">
<rect key="frame" x="16" y="16" width="350" height="131.5"/>
<accessibility key="accessibilityConfiguration">
<bool key="isElement" value="NO"/>
</accessibility>
<string key="text">It looks like you may be accessing the internet from outside Australia. This sometimes happens when roaming on an international simcard. Please connect to an Australian network to continue.</string>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<color key="textColor" red="0.63921568630000003" green="0.098039215690000001" blue="0.098039215690000001" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="localizationKey" value="geoblock_error_message"/>
</userDefinedRuntimeAttributes>
</label>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="chevron-right-red" highlightedImage="redcross" translatesAutoresizingMaskIntoConstraints="NO" id="JZ8-95-6mR">
<rect key="frame" x="374" y="16" width="24" height="24"/>
<constraints>
<constraint firstAttribute="width" constant="24" id="5Dg-TP-jeV"/>
<constraint firstAttribute="height" constant="24" id="QDY-8b-rGB"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="yw4-mY-Pvb" firstAttribute="top" secondItem="8Nu-a1-6fd" secondAttribute="top" constant="16" id="8lj-Mp-QPd"/>
<constraint firstItem="Iw2-bN-Bjb" firstAttribute="top" secondItem="yw4-mY-Pvb" secondAttribute="bottom" id="BG1-vf-8aa"/>
<constraint firstItem="yw4-mY-Pvb" firstAttribute="leading" secondItem="8Nu-a1-6fd" secondAttribute="leading" constant="16" id="FB0-Gh-26E"/>
<constraint firstItem="Iw2-bN-Bjb" firstAttribute="leading" secondItem="8Nu-a1-6fd" secondAttribute="leading" constant="16" id="MsQ-XB-0Cp"/>
<constraint firstItem="JZ8-95-6mR" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="yw4-mY-Pvb" secondAttribute="trailing" constant="8" id="N9H-1e-dzZ"/>
<constraint firstAttribute="bottom" secondItem="Iw2-bN-Bjb" secondAttribute="bottom" constant="16" id="SmG-4M-0Ir"/>
<constraint firstAttribute="trailing" secondItem="Iw2-bN-Bjb" secondAttribute="trailing" constant="48" id="YQ6-nh-9k7"/>
<constraint firstAttribute="trailing" secondItem="JZ8-95-6mR" secondAttribute="trailing" constant="16" id="jSl-T3-bbB"/>
<constraint firstItem="JZ8-95-6mR" firstAttribute="top" secondItem="8Nu-a1-6fd" secondAttribute="top" constant="16" id="toG-5o-Kri"/>
</constraints>
</view>
</subviews>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" button="YES"/>
<bool key="isElement" value="NO"/>
</accessibility>
<constraints>
<constraint firstItem="8Nu-a1-6fd" firstAttribute="leading" secondItem="5Pd-c8-CYx" secondAttribute="leading" id="GvM-yl-CrV"/>
<constraint firstAttribute="bottom" secondItem="8Nu-a1-6fd" secondAttribute="bottom" id="gZR-KX-tky"/>
<constraint firstItem="8Nu-a1-6fd" firstAttribute="top" secondItem="5Pd-c8-CYx" secondAttribute="top" id="gl7-zv-NsS"/>
<constraint firstAttribute="trailing" secondItem="8Nu-a1-6fd" secondAttribute="trailing" id="tnp-QS-aoM"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="a5D-xk-n0n" userLabel="Bluetooth Permission Off Section">
<rect key="frame" x="0.0" y="369" width="414" height="80.5"/>
<rect key="frame" x="0.0" y="533.5" width="414" height="80.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ZoL-pC-0bR" userLabel="Bluetooth Permission Off Bar">
<rect key="frame" x="0.0" y="0.0" width="414" height="80.5"/>
@ -327,7 +389,7 @@
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ers-f9-BFH" userLabel="Bluetooth Status Off Section">
<rect key="frame" x="0.0" y="450.5" width="414" height="101.5"/>
<rect key="frame" x="0.0" y="615" width="414" height="101.5"/>
<subviews>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="p5d-dk-foR" userLabel="Bluetooth Status Bar Off">
<rect key="frame" x="0.0" y="0.0" width="414" height="101.5"/>
@ -394,7 +456,7 @@
</connections>
</view>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="u4f-uR-ri3" userLabel="Location Settings">
<rect key="frame" x="0.0" y="553" width="414" height="143.5"/>
<rect key="frame" x="0.0" y="717.5" width="414" height="143.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aGh-Fm-LNa" userLabel="Location Off bar">
<rect key="frame" x="0.0" y="0.0" width="414" height="142.5"/>
@ -473,7 +535,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="m1D-93-7sF" userLabel="ActiveView">
<rect key="frame" x="0.0" y="696.5" width="414" height="427.5"/>
<rect key="frame" x="0.0" y="861" width="414" height="427.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="1" translatesAutoresizingMaskIntoConstraints="NO" id="981-Cd-Xm3" userLabel="Active Stack View">
<rect key="frame" x="0.0" y="0.0" width="414" height="427.5"/>
@ -592,7 +654,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Aop-Ae-hRv" userLabel="StatisticsViewSection">
<rect key="frame" x="0.0" y="1136" width="414" height="124"/>
<rect key="frame" x="0.0" y="1300.5" width="414" height="124"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="eZl-C5-gSv" userLabel="StatisticsView">
<rect key="frame" x="0.0" y="12" width="414" height="100"/>
@ -610,7 +672,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="9cE-NC-A20" userLabel="Help">
<rect key="frame" x="0.0" y="1260" width="414" height="153"/>
<rect key="frame" x="0.0" y="1424.5" width="414" height="153"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="co1-dK-1WU">
<rect key="frame" x="0.0" y="12" width="414" height="129"/>
@ -743,7 +805,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7cN-DY-lc3" userLabel="Change language">
<rect key="frame" x="0.0" y="1413" width="414" height="153"/>
<rect key="frame" x="0.0" y="1577.5" width="414" height="153"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bE9-gT-Hba">
<rect key="frame" x="0.0" y="12" width="414" height="129"/>
@ -876,7 +938,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="CGS-rw-uZS" userLabel="PrivacyPolicy">
<rect key="frame" x="0.0" y="1566" width="414" height="153"/>
<rect key="frame" x="0.0" y="1730.5" width="414" height="153"/>
<subviews>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="BBB-zD-Non">
<rect key="frame" x="0.0" y="12" width="414" height="129"/>
@ -885,7 +947,7 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="129"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Privacy Policy" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsFontForContentSizeCategory="YES" translatesAutoresizingMaskIntoConstraints="NO" id="mFo-iF-cX3">
<rect key="frame" x="72" y="16" width="302" height="24"/>
<rect key="frame" x="72" y="16" width="302" height="77.5"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" none="YES"/>
<bool key="isElement" value="NO"/>
@ -908,7 +970,7 @@
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Privacy Policy for COVIDSafe Application" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gF9-eh-ER2">
<rect key="frame" x="72" y="40" width="302" height="73"/>
<rect key="frame" x="72" y="93.5" width="302" height="19.5"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" none="YES"/>
<bool key="isElement" value="NO"/>
@ -1009,7 +1071,7 @@
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vs9-rS-UOM" userLabel="Share CovidSafe">
<rect key="frame" x="0.0" y="1719" width="414" height="56"/>
<rect key="frame" x="0.0" y="1883.5" width="414" height="56"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RCa-zU-3Vo">
<rect key="frame" x="0.0" y="12" width="414" height="32"/>
@ -1120,7 +1182,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JSe-D6-hyV" userLabel="Upload Data">
<rect key="frame" x="0.0" y="1719" width="414" height="165.5"/>
<rect key="frame" x="0.0" y="1883.5" width="414" height="165.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8pS-Df-p0U">
<rect key="frame" x="0.0" y="12" width="414" height="141.5"/>
@ -1240,7 +1302,7 @@
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eZ2-CQ-dtQ" userLabel="Version View">
<rect key="frame" x="0.0" y="1884.5" width="414" height="56"/>
<rect key="frame" x="0.0" y="2049" width="414" height="56"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Version number:" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="O1w-Sl-OIH" userLabel="Version">
<rect key="frame" x="16" y="12" width="382" height="20"/>

View file

@ -4,15 +4,18 @@ import KeychainSwift
import SafariServices
import Reachability
let reauthenticationNeededKey = "ReauthenticationNeededKey"
let showGeolockErrorKey = "showGeolockErrorKey"
class HomeViewController: UIViewController, HomeDelegate {
private var observer: NSObjectProtocol?
private let reauthenticationNeededKey = "ReauthenticationNeededKey"
@IBOutlet weak var bluetoothStatusOffView: UIView!
@IBOutlet weak var bluetoothPermissionOffView: UIView!
@IBOutlet weak var locationPermissionsView: UIView!
@IBOutlet weak var inactiveSettingsContent: UIView!
@IBOutlet weak var inactiveTokenExpiredView: UIView!
@IBOutlet weak var inactiveGenericError: UIView!
@IBOutlet weak var shareView: UIView!
@IBOutlet weak var inactiveAppSectionView: UIView!
@IBOutlet weak var activeAppSectionView: UIView!
@ -42,8 +45,17 @@ class HomeViewController: UIViewController, HomeDelegate {
var allPermissionOn = true
var registrationNeeded: Bool {
return UserDefaults.standard.bool(forKey: reauthenticationNeededKey)
var showErrorToUser: Bool {
return showRegistrationError ||
showGenericError
}
var showGenericError: Bool {
return UserDefaults.standard.bool(forKey: showGeolockErrorKey)
}
var showRegistrationError: Bool {
return UserDefaults.standard.bool(forKey: reauthenticationNeededKey)
}
var bluetoothStatusOn = true
@ -222,7 +234,7 @@ class HomeViewController: UIViewController, HomeDelegate {
func updateJWTKeychainAccess() {
let hasUpdatedKeychainAccess = UserDefaults.standard.bool(forKey: "HasUpdatedKeychainAccess")
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
if (!hasUpdatedKeychainAccess) {
if let jwt = keychain.get("JWT_TOKEN") {
if (keychain.set(jwt, forKey: "JWT_TOKEN", withAccess: .accessibleAfterFirstUnlock)) {
@ -272,6 +284,7 @@ class HomeViewController: UIViewController, HomeDelegate {
self?.toggleShareView()
self?.toggleStatisticsView()
self?.toggleRegistrationNeededView()
self?.toggleGenericErrorView()
}
})
}
@ -349,37 +362,41 @@ class HomeViewController: UIViewController, HomeDelegate {
}
fileprivate func toggleUploadView() {
toggleViewVisibility(view: self.uploadView, isVisible: !self.didUploadData && !self.registrationNeeded)
toggleViewVisibility(view: self.uploadView, isVisible: !self.didUploadData && !self.showErrorToUser)
}
fileprivate func toggleShareView() {
toggleViewVisibility(view: shareView, isVisible: !registrationNeeded)
toggleViewVisibility(view: shareView, isVisible: !showErrorToUser)
}
fileprivate func toggleStatisticsView() {
toggleViewVisibility(view: covidStatisticsSection, isVisible: !registrationNeeded)
toggleViewVisibility(view: covidStatisticsSection, isVisible: !showErrorToUser)
}
fileprivate func toggleHeaderView() {
toggleViewVisibility(view: inactiveAppSectionView, isVisible: !self.allPermissionOn || registrationNeeded)
toggleViewVisibility(view: inactiveSettingsContent, isVisible: !self.allPermissionOn && !registrationNeeded)
toggleViewVisibility(view: activeAppSectionView, isVisible: self.allPermissionOn)
toggleViewVisibility(view: inactiveAppSectionView, isVisible: !self.allPermissionOn || showErrorToUser)
toggleViewVisibility(view: inactiveSettingsContent, isVisible: !self.allPermissionOn && !showErrorToUser)
toggleViewVisibility(view: activeAppSectionView, isVisible: self.allPermissionOn && !showErrorToUser)
}
fileprivate func toggleBluetoothStatusView() {
toggleViewVisibility(view: bluetoothStatusOffView, isVisible: self.bluetoothPermissionOn && !self.bluetoothStatusOn && !registrationNeeded)
toggleViewVisibility(view: bluetoothStatusOffView, isVisible: self.bluetoothPermissionOn && !self.bluetoothStatusOn && !showErrorToUser)
}
fileprivate func toggleBluetoothPermissionStatusView() {
toggleViewVisibility(view: bluetoothPermissionOffView, isVisible: !self.allPermissionOn && !self.bluetoothPermissionOn && !registrationNeeded)
toggleViewVisibility(view: bluetoothPermissionOffView, isVisible: !self.allPermissionOn && !self.bluetoothPermissionOn && !showErrorToUser)
}
fileprivate func toggleLocationPermissionStatusView() {
toggleViewVisibility(view: locationPermissionsView, isVisible: !allPermissionOn && !locationPermissionOn && (bluetoothPermissionOn && bluetoothStatusOn) && !registrationNeeded)
toggleViewVisibility(view: locationPermissionsView, isVisible: !allPermissionOn && !locationPermissionOn && (bluetoothPermissionOn && bluetoothStatusOn) && !showErrorToUser)
}
fileprivate func toggleRegistrationNeededView() {
toggleViewVisibility(view: inactiveTokenExpiredView, isVisible: registrationNeeded)
toggleViewVisibility(view: inactiveTokenExpiredView, isVisible: showRegistrationError)
}
fileprivate func toggleGenericErrorView() {
toggleViewVisibility(view: inactiveGenericError, isVisible: showGenericError)
}
func attemptTurnOnBluetooth() {

View file

@ -53,7 +53,7 @@ final class InfoViewController: UIViewController {
silentNotificationsCountLabel.text = "\(UserDefaults.standard.integer(forKey: "debugSilentNotificationCount"))"
apnTokenLabel.text = UserDefaults.standard.string(forKey: "deviceTokenForAPN")
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .medium
@ -92,7 +92,7 @@ final class InfoViewController: UIViewController {
}
@IBAction func requestUploadOTP(_ sender: UIButton) {
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
guard let jwt = keychain.get("JWT_TOKEN") else {
DLog("Error trying to upload when not logged in")
return
@ -199,7 +199,7 @@ final class InfoViewController: UIViewController {
}
@IBAction func setReauthenticationNeeded(_ sender: Any) {
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
keychain.set("corruptedjwt", forKey: "JWT_TOKEN", withAccess: .accessibleAfterFirstUnlock)
}

View file

@ -15,7 +15,7 @@ class InitialScreenViewController: UIViewController, EncounterDBMigrationProgres
var migrationStart: Date?
var isKeychainAvailable = false
var isDisplayTimeElapsed = false
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
var giveupTimer: Timer?
var initialDelayTimer: Timer?
var migrationViewController: UIViewController?

View file

@ -0,0 +1,13 @@
//
// KeychainSwift+Singleton.swift
// CovidSafe
//
// Copyright © 2021 Australian Government. All rights reserved.
//
import Foundation
import KeychainSwift
extension KeychainSwift {
static var shared = KeychainSwift()
}

View file

@ -240,7 +240,7 @@ class OTPViewController: UIViewController, RegistrationHandler {
result(.WrongOTP)
return
}
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
keychain.set(tokenToStore, forKey: "JWT_TOKEN", withAccess: .accessibleAfterFirstUnlock)
keychain.set(refreshToken, forKey: "REFRESH_TOKEN", withAccess: .accessibleAfterFirstUnlock)
UserDefaults.standard.set(true, forKey: "HasUpdatedKeychainAccess")

View file

@ -135,12 +135,34 @@ class PhoneNumberViewController: UIViewController, UITextFieldDelegate, Registra
self?.activityIndicator.stopAnimating()
self?.getOTPButton.isEnabled = true
if let error = error {
let alertMessage = error == .MaxRegistrationError ? "max_registrations".localizedString() : "PhoneVerificationErrorMessage".localizedString()
let errorAlert = UIAlertController(title: "PhoneVerificationErrorTitle".localizedString(),
message: "PhoneVerificationErrorMessage".localizedString(),
message: alertMessage,
preferredStyle: .alert)
errorAlert.addAction(UIAlertAction(title: "global_OK".localizedString(), style: .default, handler: { _ in
DLog("Unable to verify phone number")
}))
if error == .MaxRegistrationError {
errorAlert.addAction(UIAlertAction(title: "max_registrations_button1".localizedString(), style: .default, handler: { _ in
DLog("Max registrations error, request deletion tapped")
let deleteUrl = URLHelper.getDataDeletionURL()
guard let url = URL(string: deleteUrl) else {
DLog("Unable to create url")
return
}
let safariVC = SFSafariViewController(url: url)
self?.present(safariVC, animated: true, completion: nil)
}))
errorAlert.addAction(UIAlertAction(title: "max_registration_button2".localizedString(), style: .default, handler: { _ in
DLog("Max registrations error, close alert")
}))
} else {
errorAlert.addAction(UIAlertAction(title: "global_OK".localizedString(), style: .default, handler: { _ in
DLog("Unable to verify phone number")
}))
}
self?.present(errorAlert, animated: true)
DLog("Phone number verification error: \(error.localizedDescription)")
return

View file

@ -21,7 +21,7 @@ class QuestionUploadDataViewController: UIViewController {
// MARK: -
private func showUploadDataFlow() {
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
setIsLoading(true)
guard let jwt = keychain.get("JWT_TOKEN") else {
DLog("Error trying to upload when not logged in")

View file

@ -11,7 +11,7 @@ import KeychainSwift
class RegistrationIntroViewController: UIViewController {
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)

View file

@ -64,7 +64,6 @@ class RegistrationSuccessViewController: UIViewController {
if reauthenticating {
dismiss(animated: true, completion: nil)
} else {
let homeVC = HomeViewController(nibName: "HomeView", bundle: nil)
let tabVC = MainTabBarViewController()
self.navigationController?.setViewControllers([tabVC], animated: true)
}

View file

@ -62,4 +62,8 @@ struct URLHelper {
return "\(getHelpURL())#location-permissions"
}
static func getDataDeletionURL() -> String {
return "https://covidsafe-form.service.gov.au"
}
}

View file

@ -12,7 +12,7 @@ import KeychainSwift
final class UploadHelper {
public static func uploadEncounterData(pin: String?, _ result: @escaping (UploadResult, String?) -> Void) {
let keychain = KeychainSwift()
let keychain = KeychainSwift.shared
guard let managedContext = EncounterDB.shared.persistentContainer?.viewContext else {
result(.Failed, "[001]")

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "تابع";
"action_report_an_issue" = "بلِّغ عن مشكلة";
"action_upload_done" = "تمّ";
@ -286,6 +287,8 @@
"factors_intro_2" = "كما يمكن أن تتأثر المصافحات بالعوامل البيئية، بما في ذلك:";
/* Example: "From 22 to 28 January 2021" */
"from" = "";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "رجوع";
"global_close" = "إغلاق";
"global_double_tap" = "أنقر مرتين للتصحيح";
@ -367,6 +370,9 @@
"location_off" = "الموقع الجغرافي: معطّل";
"location_off_description" = "يتطلب جهاز iphone الخاص بك تشغيل الموقع الجغرافي ليتمكن تطبيق COVIDSafe من العمل. لا يقوم تطبيق COVIDSafe بتتبّع بيانات الموقع الجغرافي أو تخزينها.";
"main_restrictions" = "القيود الرئيسية";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "تحديث تطبيق COVIDSafe قيد الإنجاز. \n\nيُرجى التأكد من عدم إغلاق هاتفك إلى أن يتم اكتمال التحديث.";
"minute" = "دقيقة";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "Συνεχίστε";
"action_report_an_issue" = "Αναφέρετε το πρόβλημα";
"action_upload_done" = "Συνεχίστε";
@ -286,6 +287,8 @@
"factors_intro_2" = "Οι χειραψίες μπορούν επίσης να επηρεαστούν από περιβαλλοντικούς παράγοντες, όπως:";
/* Example: "From 22 to 28 January 2021" */
"from" = "";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "Πίσω";
"global_close" = "Κλείστε";
"global_double_tap" = "πατήστε δύο φορές για επεξεργασία";
@ -367,6 +370,9 @@
"location_off" = "Τοποθεσία: ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΗ";
"location_off_description" = "Το iPhone σας απαιτεί άδεια τοποθεσίας για να λειτουργήσει την COVIDSafe. Η COVIDSafe ΔΕΝ παρακολουθεί ούτε αποθηκεύει τα δεδομένα τοποθεσίας σας.";
"main_restrictions" = "Κύριοι περιορισμοί";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "Η ενημέρωση της COVIDSafe συνεχίζεται. \n\nΒεβαιωθείτε ότι το τηλέφωνό σας δεν είναι απενεργοποιημένο έως ότου ολοκληρωθεί η ενημέρωση.";
"minute" = "Λεπτό";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "Delete this app now";
"action_continue" = "Continue";
"action_report_an_issue" = "Report an issue";
"action_upload_done" = "Done";
@ -286,6 +287,8 @@
"factors_intro_2" = "Handshakes can also be affected by environmental factors, including:";
/* Example: "From 22 to 28 January 2021" */
"from" = "From";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "You may be connected using an internet provider from outside Australia. This could happen when using international data roaming on a non-Australian SIM card.\n\nPlease connect to an Australian network or use a local Wi-Fi internet connection to continue.";
"global_back" = "Back";
"global_close" = "Close";
"global_double_tap" = "double tap to edit";
@ -367,6 +370,9 @@
"location_off" = "Location: OFF";
"location_off_description" = "Your iPhone requires Location permission for COVIDSafe to work. COVIDSafe does NOT track or store your location data.";
"main_restrictions" = "Main restrictions";
"max_registration_button2" = "Cancel";
"max_registrations" = "You've reached the limit to register using the same mobile number.\n\nTo use the same mobile number to register again, you must request to delete your current registration information.";
"max_registrations_button1" = "Request deletion";
/* Splash Screen */
"migration_in_progress" = " COVIDSafe update in progress. \n\n Please make sure you phone is not switched off until the update is complete.";
"minute" = "Minute";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "Continua";
"action_report_an_issue" = "Segnala un problema";
"action_upload_done" = "Eseguito";
@ -286,6 +287,8 @@
"factors_intro_2" = "Anche le strette di mano possono essere influenzate da fattori ambientali:";
/* Example: "From 22 to 28 January 2021" */
"from" = "";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "Indietro";
"global_close" = "Chiudi";
"global_double_tap" = "Fai un doppio click per modificare";
@ -367,6 +370,9 @@
"location_off" = "Posizione: OFF";
"location_off_description" = "Il tuo iPhone richiede l'autorizzazione alla localizzazione affinché COVIDSafe funzioni. COVIDSafe NON memorizza né utilizza i dati relativi alla tua posizione.";
"main_restrictions" = "Restrizioni principali";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "Aggiornamento COVIDSafe in corso. \n\nAssicurati che il cellulare non sia spento fino al completamento dell'aggiornamento.";
"minute" = "Minuto";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "계속";
"action_report_an_issue" = "문제 신고하기";
"action_upload_done" = "완료";
@ -286,6 +287,8 @@
"factors_intro_2" = "다음과 같은 환경 요인 역시 블루투스 악수에 영향을 미칠 수 있습니다.";
/* Example: "From 22 to 28 January 2021" */
"from" = "부터";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "뒤로";
"global_close" = "닫기";
"global_double_tap" = "두 번 탭해서 수정하세요";
@ -367,6 +370,9 @@
"location_off" = "위치 서비스: 꺼짐";
"location_off_description" = "COVIDSafe가 제대로 실행되기 위해서는 여러분의 아이폰에서 위치 서비스 기능을 허용해야 합니다. COVIDSafe는 여러분의 위치 정보를 추적하거나 저장하지 않습니다.";
"main_restrictions" = "주요 규제 조치";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe 업데이트 진행 중. \n\n업데이트가 완료될 때까지 휴대폰이 꺼지지 않도록 해주세요.";
"minute" = "분";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "ਜਾਰੀ ਰੱਖੋ";
"action_report_an_issue" = "ਮੁੱਦੇ/ਮਸਲੇ ਬਾਰੇ ਰਿਪੋਰਟ ਕਰੋ|";
"action_upload_done" = "ਕਰ ਦਿੱਤਾ";
@ -286,6 +287,8 @@
"factors_intro_2" = "ਹੱਥ ਮਿਲਾਉਣੇ, ਵਾਤਾਵਰਣਕ ਕਾਰਕਾਂ ਦੁਆਰਾ ਵੀ ਪ੍ਰਭਾਵਤ ਹੋ ਸਕਦੇ ਹਨ, ਜਿੰਨ੍ਹਾਂ ਵਿੱਚ ਸ਼ਾਮਲ ਹਨ :";
/* Example: "From 22 to 28 January 2021" */
"from" = "ਵੱਲੋਂ";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "ਪਿੱਛੇ";
"global_close" = "ਬੰਦ ਕਰੋ";
"global_double_tap" = "ਸੋਧਣ ਲਈ ਡਬਲ ਟੈਪ ਕਰੋ";
@ -367,6 +370,9 @@
"location_off" = "ਲੋਕੇਸ਼ਨ: ਔਫ";
"location_off_description" = "COVIDSafe ਕੰਮ ਕਰ ਸਕੇ, ਇਸ ਦੇ ਲਈ ਤੁਹਾਡੇ iPhone ਨੂੰ ਲੋਕੇਸ਼ਨ ਆਗਿਆ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। *COVIDSafe ਤੁਹਾਡੇ ਸਥਾਨਕ ਅੰਕੜਿਆਂ ਨੂੰ ਨਹੀਂ ਢੂੰਡਦਾ ਜਾਂ ਸੰਭਾਲਦਾ।*";
"main_restrictions" = "ਮੁੱਖ ਪਾਬੰਦੀਆਂ";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe ਅੱਪਡੇਟ ਚੱਲ ਰਿਹਾ ਹੈ। \n\nਕਿਰਪਾ ਕਰਕੇ ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਤੁਹਾਡਾ ਫ਼ੋਨ ਤਦ ਤੱਕ ਬੰਦ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਜਦ ਤੱਕ ਅੱਪਡੇਟ ਪੂਰਾ ਨਹੀਂ ਹੋ ਜਾਂਦਾ।";
"minute" = "ਮਿੰਟ";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "Devam";
"action_report_an_issue" = "Sorun bildir";
"action_upload_done" = "Bitti";
@ -286,6 +287,8 @@
"factors_intro_2" = "El sıkışmalar çevresel faktörlerden de etkilenebilir, örneğin:";
/* Example: "From 22 to 28 January 2021" */
"from" = "";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "Geri";
"global_close" = "Kapat";
"global_double_tap" = "düzeltme yapmak için iki defa tıklayınız";
@ -367,6 +370,9 @@
"location_off" = "Konum: KAPALI";
"location_off_description" = "iPhoneunuz, COVIDSafein çalışması için Konum iznine ihtiyaç duyar. COVIDSafe konum verilerinizi İZLEMEZ veya SAKLAMAZ.";
"main_restrictions" = "Ana kısıtlamalar";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = " COVIDSafe güncellemesi devam ediyor. \n\n Lütfen güncelleme tamamlanana kadar telefonunuzu kapatmayın.";
"minute" = "Dakika";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "Tiếp tục";
"action_report_an_issue" = "Báo cáo sự cố";
"action_upload_done" = "Hoàn tất";
@ -286,6 +287,8 @@
"factors_intro_2" = "Chức năng bắt tay cũng có thể bị ảnh hưởng bởi các yếu tố môi trường, bao gồm:";
/* Example: "From 22 to 28 January 2021" */
"from" = "";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "Trở lại";
"global_close" = "Đóng";
"global_double_tap" = "nhấn đúp để chỉnh sửa";
@ -367,6 +370,9 @@
"location_off" = "Vị trí: TẮT";
"location_off_description" = "iPhone của bạn yêu cầu quyền truy cập Vị trí để COVIDSafe hoạt động. COVIDSafe KHÔNG theo dõi hoặc lưu trữ dữ liệu vị trí của bạn.";
"main_restrictions" = "Những hạn chế chính";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe trong tiến trình cập nhật. \n\nVui lòng đảm bảo điện thoại của bạn không bị tắt cho đến khi cập nhật hoàn tất.";
"minute" = "Phút";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "继续";
"action_report_an_issue" = "报告问题";
"action_upload_done" = "完成";
@ -286,6 +287,8 @@
"factors_intro_2" = "蓝牙握手也会受到环境因素的影响,这些因素包括:";
/* Example: "From 22 to 28 January 2021" */
"from" = "";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "返回";
"global_close" = "关闭";
"global_double_tap" = "双击进行编辑";
@ -367,6 +370,9 @@
"location_off" = "位置:关闭";
"location_off_description" = "您的 iPhone 要求COVIDSafe 获得位置权限才能运行。COVIDSafe 不会跟踪或存储您的位置数据。";
"main_restrictions" = "主要限制措施";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "COVIDSafe正在更新。 \n\n请保持开机状态直至更新完成。";
"minute" = "分钟";

View file

@ -1,3 +1,4 @@
"[test]decommission_message" = "";
"action_continue" = "繼續";
"action_report_an_issue" = "報告問題";
"action_upload_done" = "完成";
@ -286,6 +287,8 @@
"factors_intro_2" = "藍牙握手也可以受到環境因素影響,包括:";
/* Example: "From 22 to 28 January 2021" */
"from" = "";
/* Error message for when a user tries to signup outside of Australia. */
"geoblock_error_message" = "";
"global_back" = "返回";
"global_close" = "關閉";
"global_double_tap" = "點觸兩次來編輯";
@ -367,6 +370,9 @@
"location_off" = "位置:關閉";
"location_off_description" = "你的iPhone要求COVIDSafe獲得位置許可權才能正常運作。COVIDSafe不會跟蹤或者存儲你的位置資料。";
"main_restrictions" = "主要限制措施";
"max_registration_button2" = "";
"max_registrations" = "";
"max_registrations_button1" = "";
/* Splash Screen */
"migration_in_progress" = "正在更新 COVIDSafe。\n\n請保持開機狀態直至更新完成為止。";
"minute" = "分鐘 ";