Merge pull request #209 from LukasKorba/196_user_preferences_storage
User preferences storage
This commit is contained in:
commit
7a8e19a956
|
@ -82,14 +82,16 @@
|
|||
9E2AC0FF27D8EC120042AA47 /* MnemonicSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9E2AC0FE27D8EC120042AA47 /* MnemonicSwift */; };
|
||||
9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */; };
|
||||
9E2AC10327DA28200042AA47 /* RecoveryPhraseStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */; };
|
||||
9E2AC10627DA34610042AA47 /* RecoveryPhraseStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2AC10527DA34610042AA47 /* RecoveryPhraseStorageTests.swift */; };
|
||||
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */; };
|
||||
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */; };
|
||||
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */; };
|
||||
9E37A2B827C8F59F00AE57B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */; };
|
||||
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */; };
|
||||
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; };
|
||||
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; };
|
||||
9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */; };
|
||||
9EF8135C27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */; };
|
||||
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; };
|
||||
F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; };
|
||||
F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; };
|
||||
F93874F0273C4DE200F0E875 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.swift */; };
|
||||
|
@ -223,7 +225,10 @@
|
|||
9E37A2B727C8F59F00AE57B3 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicDesignModifier.swift; sourceTree = "<group>"; };
|
||||
9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantButtonStyles.swift; sourceTree = "<group>"; };
|
||||
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = "<group>"; };
|
||||
9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; };
|
||||
9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseStorageTests.swift; sourceTree = "<group>"; };
|
||||
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = "<group>"; };
|
||||
F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = "<group>"; };
|
||||
F93673D52742CB840099C6AF /* Previews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Previews.swift; sourceTree = "<group>"; };
|
||||
F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = "<group>"; };
|
||||
|
@ -363,6 +368,7 @@
|
|||
0D4E7A1926B364180058B01E /* secantTests */,
|
||||
0D4E7A2426B364180058B01E /* secantUITests */,
|
||||
0D4E7A0626B364170058B01E /* Products */,
|
||||
9EF8135627ECAFF50075AF48 /* Recovered References */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
|
@ -411,7 +417,7 @@
|
|||
0D4E7A1926B364180058B01E /* secantTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E2AC10427DA34450042AA47 /* Util */,
|
||||
9EF8135927ECC25E0075AF48 /* Util */,
|
||||
0DFE93E4272CB6D0000FCCA5 /* RecoveryPhraseValidationTests */,
|
||||
0DFE93DD272C6D4B000FCCA5 /* BackupFlowTests */,
|
||||
6654C7422715A48E00901167 /* OnboardingTests */,
|
||||
|
@ -504,6 +510,7 @@
|
|||
0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */,
|
||||
9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */,
|
||||
9E2AC10227DA28200042AA47 /* RecoveryPhraseStorage.swift */,
|
||||
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
|
@ -657,14 +664,6 @@
|
|||
path = CircularFrame;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E2AC10427DA34450042AA47 /* Util */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E2AC10527DA34610042AA47 /* RecoveryPhraseStorageTests.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9E2DF99727CF704D00649636 /* ImportWallet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -691,6 +690,23 @@
|
|||
path = Preamble;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9EF8135627ECAFF50075AF48 /* Recovered References */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9E2AC10527DA34610042AA47 /* RecoveryPhraseStorageTests.swift */,
|
||||
);
|
||||
name = "Recovered References";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9EF8135927ECC25E0075AF48 /* Util */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9EF8135A27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift */,
|
||||
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F93874EC273C4DE200F0E875 /* Home */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1053,6 +1069,7 @@
|
|||
0D35CC46277A36E00074316A /* ScrollableWhenScaled.swift in Sources */,
|
||||
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */,
|
||||
669FDAE9272C23B3007B9422 /* CircularFrame.swift in Sources */,
|
||||
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */,
|
||||
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
|
||||
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
|
||||
0DC487C32772574C00BE6A63 /* ValidationSucceededView.swift in Sources */,
|
||||
|
@ -1140,9 +1157,10 @@
|
|||
0DFE93DF272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift in Sources */,
|
||||
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
|
||||
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||
9E2AC10627DA34610042AA47 /* RecoveryPhraseStorageTests.swift in Sources */,
|
||||
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */,
|
||||
0DFE93E6272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift in Sources */,
|
||||
9EF8135C27ECC25E0075AF48 /* RecoveryPhraseStorageTests.swift in Sources */,
|
||||
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// UserPreferencesStorage.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Lukáš Korba on 03/18/2022.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Representation of the user preferences stored in the local persistent storage (non-encrypted, no security needed)
|
||||
protocol UserPreferences {
|
||||
/// From when the app is on and uninterrupted
|
||||
var activeAppSessionFrom: TimeInterval { get set }
|
||||
/// What is the set up currency
|
||||
var currency: String { get set }
|
||||
/// Whether the fiat conversion is on/off
|
||||
var isFiatConverted: Bool { get set }
|
||||
/// Whether user finished recovery phrase backup test
|
||||
var isRecoveryPhraseTestCompleted: Bool { get set }
|
||||
/// Whether the user has been autoshielded in the running session
|
||||
var isSessionAutoshielded: Bool { get set }
|
||||
}
|
||||
|
||||
/// Live implementation of the `UserPreferences` using User Defaults
|
||||
/// according to https://developer.apple.com/documentation/foundation/userdefaults
|
||||
/// the UserDefaults class is thread-safe.
|
||||
struct UserPreferencesStorage: UserPreferences {
|
||||
enum Constants: String, CaseIterable {
|
||||
case zcashActiveAppSessionFrom
|
||||
case zcashCurrency
|
||||
case zcashFiatConverted
|
||||
case zcashRecoveryPhraseTestCompleted
|
||||
case zcashSessionAutoshielded
|
||||
}
|
||||
|
||||
static let `default` = UserPreferencesStorage(
|
||||
appSessionFrom: Date().timeIntervalSince1970,
|
||||
convertedCurrency: "USD",
|
||||
fiatConvertion: true,
|
||||
recoveryPhraseTestCompleted: false,
|
||||
sessionAutoshielded: true,
|
||||
userDefaults: UserDefaults.standard
|
||||
)
|
||||
|
||||
/// Default values for all preferences in case there is no value stored (counterparts to `Constants`)
|
||||
private let appSessionFrom: TimeInterval
|
||||
private let convertedCurrency: String
|
||||
private let fiatConvertion: Bool
|
||||
private let recoveryPhraseTestCompleted: Bool
|
||||
private let sessionAutoshielded: Bool
|
||||
|
||||
private let userDefaults: UserDefaults
|
||||
|
||||
init(
|
||||
appSessionFrom: TimeInterval,
|
||||
convertedCurrency: String,
|
||||
fiatConvertion: Bool,
|
||||
recoveryPhraseTestCompleted: Bool,
|
||||
sessionAutoshielded: Bool,
|
||||
userDefaults: UserDefaults
|
||||
) {
|
||||
self.appSessionFrom = appSessionFrom
|
||||
self.convertedCurrency = convertedCurrency
|
||||
self.fiatConvertion = fiatConvertion
|
||||
self.recoveryPhraseTestCompleted = recoveryPhraseTestCompleted
|
||||
self.sessionAutoshielded = sessionAutoshielded
|
||||
self.userDefaults = userDefaults
|
||||
}
|
||||
|
||||
/// From when the app is on and uninterrupted
|
||||
var activeAppSessionFrom: TimeInterval {
|
||||
get { getValue(forKey: Constants.zcashActiveAppSessionFrom.rawValue, default: appSessionFrom) }
|
||||
set { setValue(newValue, forKey: Constants.zcashActiveAppSessionFrom.rawValue) }
|
||||
}
|
||||
|
||||
/// What is the set up currency
|
||||
var currency: String {
|
||||
get { getValue(forKey: Constants.zcashCurrency.rawValue, default: convertedCurrency) }
|
||||
set { setValue(newValue, forKey: Constants.zcashCurrency.rawValue) }
|
||||
}
|
||||
|
||||
/// Whether the fiat conversion is on/off
|
||||
var isFiatConverted: Bool {
|
||||
get { getValue(forKey: Constants.zcashFiatConverted.rawValue, default: fiatConvertion) }
|
||||
set { setValue(newValue, forKey: Constants.zcashFiatConverted.rawValue) }
|
||||
}
|
||||
|
||||
/// Whether user finished recovery phrase backup test
|
||||
var isRecoveryPhraseTestCompleted: Bool {
|
||||
get { getValue(forKey: Constants.zcashRecoveryPhraseTestCompleted.rawValue, default: recoveryPhraseTestCompleted) }
|
||||
set { setValue(newValue, forKey: Constants.zcashRecoveryPhraseTestCompleted.rawValue) }
|
||||
}
|
||||
|
||||
/// Whether the user has been autoshielded in the running session
|
||||
var isSessionAutoshielded: Bool {
|
||||
get { getValue(forKey: Constants.zcashSessionAutoshielded.rawValue, default: sessionAutoshielded) }
|
||||
set { setValue(newValue, forKey: Constants.zcashSessionAutoshielded.rawValue) }
|
||||
}
|
||||
|
||||
/// Use carefully: Deletes all user preferences from the User Defaults
|
||||
func removeAll() {
|
||||
Constants.allCases.forEach { userDefaults.removeObject(forKey: $0.rawValue) }
|
||||
}
|
||||
}
|
||||
|
||||
private extension UserPreferencesStorage {
|
||||
func getValue<Value>(forKey: String, default defaultIfNil: Value) -> Value {
|
||||
userDefaults.object(forKey: forKey) as? Value ?? defaultIfNil
|
||||
}
|
||||
|
||||
func setValue<Value>(_ value: Value, forKey: String) {
|
||||
userDefaults.set(value, forKey: forKey)
|
||||
userDefaults.synchronize()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// UserPreferencesStorageTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Lukáš Korba on 22.03.2022.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
|
||||
class UserPreferencesStorageTests: XCTestCase {
|
||||
// swiftlint:disable:next implicitly_unwrapped_optional
|
||||
var storage: UserPreferencesStorage!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
storage = UserPreferencesStorage(
|
||||
appSessionFrom: 12345678.0,
|
||||
convertedCurrency: "USD",
|
||||
fiatConvertion: true,
|
||||
recoveryPhraseTestCompleted: true,
|
||||
sessionAutoshielded: false,
|
||||
userDefaults: .standard
|
||||
)
|
||||
storage.removeAll()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
storage = nil
|
||||
}
|
||||
|
||||
func testAppSessionFrom_defaultValue() throws {
|
||||
XCTAssertEqual(12345678.0, storage.activeAppSessionFrom, "User Preferences: `activeAppSessionFrom` default doesn't match.")
|
||||
}
|
||||
|
||||
func testConvertedCurrency_defaultValue() throws {
|
||||
XCTAssertEqual("USD", storage.currency, "User Preferences: `currency` default doesn't match.")
|
||||
}
|
||||
|
||||
func testFiatConvertion_defaultValue() throws {
|
||||
XCTAssertEqual(true, storage.isFiatConverted, "User Preferences: `isFiatConverted` default doesn't match.")
|
||||
}
|
||||
|
||||
func testRecoveryPhraseTestCompleted_defaultValue() throws {
|
||||
XCTAssertEqual(true, storage.isRecoveryPhraseTestCompleted, "User Preferences: `isRecoveryPhraseTestCompleted` default doesn't match.")
|
||||
}
|
||||
|
||||
func testSessionAutoshielded_defaultValue() throws {
|
||||
XCTAssertEqual(false, storage.isSessionAutoshielded, "User Preferences: `isSessionAutoshielded` default doesn't match.")
|
||||
}
|
||||
|
||||
func testRemoveAll() throws {
|
||||
let userDefaults = UserDefaults.standard
|
||||
|
||||
// fill in the data
|
||||
UserPreferencesStorage.Constants.allCases.forEach {
|
||||
userDefaults.set("anyValue", forKey: $0.rawValue)
|
||||
}
|
||||
|
||||
// remove it
|
||||
storage?.removeAll()
|
||||
|
||||
// check the presence
|
||||
UserPreferencesStorage.Constants.allCases.forEach {
|
||||
XCTAssertNil(
|
||||
userDefaults.object(forKey: $0.rawValue),
|
||||
"User Preferences: key \($0.rawValue) should be removed but it's still present in User Defaults"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue