From 7669a95abe22000b9b06c639b3d1d0779fa7647f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Tue, 17 Sep 2024 15:12:45 +0200 Subject: [PATCH] Decouple NTP settings persistence from AppUserDefaults (#3354) Task/Issue URL: https://app.asana.com/0/72649045549333/1208273840160821/f Tech Design URL: CC: **Description**: Extract NTP data persistence into smaller protocols and use `UserDefaultsWrapper` to implement the actual storing. The change allows for better separation of concerns and potentially storing data in a different way on a case-by-case basis. **Steps to test this PR**: 1. Verify NTP sections and shortcuts settings (order, enabled state) are preserved across app instances. 2. Verify intro message is dismissed after 3 views (it can be set up from Debug menu). **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +++++ DuckDuckGo/AppSettings.swift | 6 ---- DuckDuckGo/AppUserDefaults.swift | 15 -------- DuckDuckGo/NewTabPageIntroDataStoring.swift | 34 +++++++++++++++++++ DuckDuckGo/NewTabPageIntroMessageSetup.swift | 10 +++--- DuckDuckGo/NewTabPageManager.swift | 19 ++++++++--- DuckDuckGo/NewTabPageModel.swift | 16 ++++----- DuckDuckGo/NewTabPageSectionsDebugView.swift | 26 +++++++------- .../NewTabPageSectionsSettingsStorage.swift | 2 +- .../NewTabPageSettingsPersistentStorage.swift | 17 +++++----- .../NewTabPageSettingsPersistentStore.swift | 31 +++++++++++++++++ .../NewTabPageShortcutsSettingsStorage.swift | 2 +- DuckDuckGo/NewTabPageView.swift | 6 +++- .../NewTabPageIntroMessageSetupTests.swift | 21 +++++++----- DuckDuckGoTests/NewTabPageModelTests.swift | 20 +++++------ ...NewTabPageSectionsSettingsModelTests.swift | 7 ++-- ...abPageSettingsPersistentStorageTests.swift | 8 ++--- ...ewTabPageShortcutsSettingsModelTests.swift | 3 +- 18 files changed, 161 insertions(+), 90 deletions(-) create mode 100644 DuckDuckGo/NewTabPageIntroDataStoring.swift create mode 100644 DuckDuckGo/NewTabPageSettingsPersistentStore.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 18e8d0eb00..db7fdaaf73 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -338,6 +338,8 @@ 6FD8E5202C5BA23200345670 /* NewTabPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD8E51F2C5BA23200345670 /* NewTabPageModel.swift */; }; 6FD8E5222C5BA5C400345670 /* NewTabPageIntroMessageSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD8E5212C5BA5C400345670 /* NewTabPageIntroMessageSetup.swift */; }; 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */; }; + 6FDC64012C92F4A300DB71B3 /* NewTabPageIntroDataStoring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDC64002C92F4A300DB71B3 /* NewTabPageIntroDataStoring.swift */; }; + 6FDC64032C92F4D600DB71B3 /* NewTabPageSettingsPersistentStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDC64022C92F4D600DB71B3 /* NewTabPageSettingsPersistentStore.swift */; }; 6FE018402C25CB3F001F680D /* FavoritesSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE0183F2C25CB3F001F680D /* FavoritesSectionHeader.swift */; }; 6FE095D82BD90AFB00490FF8 /* UniversalOmniBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE095D72BD90AFB00490FF8 /* UniversalOmniBarState.swift */; }; 6FE127382C20492500EB5724 /* NewTabPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127372C20492500EB5724 /* NewTabPage.swift */; }; @@ -1607,6 +1609,8 @@ 6FD8E51F2C5BA23200345670 /* NewTabPageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageModel.swift; sourceTree = ""; }; 6FD8E5212C5BA5C400345670 /* NewTabPageIntroMessageSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageIntroMessageSetup.swift; sourceTree = ""; }; 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDisplayHelper.swift; sourceTree = ""; }; + 6FDC64002C92F4A300DB71B3 /* NewTabPageIntroDataStoring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageIntroDataStoring.swift; sourceTree = ""; }; + 6FDC64022C92F4D600DB71B3 /* NewTabPageSettingsPersistentStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsPersistentStore.swift; sourceTree = ""; }; 6FE0183F2C25CB3F001F680D /* FavoritesSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesSectionHeader.swift; sourceTree = ""; }; 6FE095D72BD90AFB00490FF8 /* UniversalOmniBarState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalOmniBarState.swift; sourceTree = ""; }; 6FE127372C20492500EB5724 /* NewTabPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPage.swift; sourceTree = ""; }; @@ -3844,6 +3848,7 @@ 6F5345AE2C53F2DE00424A43 /* NewTabPageSettingsPersistentStorage.swift */, 6F9FFE252C579BCD00A238BE /* NewTabPageShortcutsSettingsStorage.swift */, 6F9FFE272C579DEA00A238BE /* NewTabPageSectionsSettingsStorage.swift */, + 6FDC64022C92F4D600DB71B3 /* NewTabPageSettingsPersistentStore.swift */, ); name = Storage; sourceTree = ""; @@ -3912,6 +3917,7 @@ children = ( 6FD8E51D2C5B84DE00345670 /* NewTabPageIntroMessageView.swift */, 6FD8E5212C5BA5C400345670 /* NewTabPageIntroMessageSetup.swift */, + 6FDC64002C92F4A300DB71B3 /* NewTabPageIntroDataStoring.swift */, ); name = IntroMessage; sourceTree = ""; @@ -7299,6 +7305,7 @@ F4E1936625AF722F001D2666 /* HighlightCutOutView.swift in Sources */, 6FB2A67C2C2D9DF0004D20C8 /* FavoritesEmptyStateView.swift in Sources */, 1E162605296840D80004127F /* Triangle.swift in Sources */, + 6FDC64012C92F4A300DB71B3 /* NewTabPageIntroDataStoring.swift in Sources */, B609D5522862EAFF0088CAC2 /* InlineWKDownloadDelegate.swift in Sources */, BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */, B652DEFD287BE67400C12A9C /* UserScripts.swift in Sources */, @@ -7379,6 +7386,7 @@ 6FB2A67A2C2C5BAE004D20C8 /* FavoriteEmptyStateItem.swift in Sources */, 6FBF0F8B2BD7C0A900136CF0 /* AllProtectedCell.swift in Sources */, 9F4CC5242C4A4F0D006A96EB /* SwiftUITestUtilities.swift in Sources */, + 6FDC64032C92F4D600DB71B3 /* NewTabPageSettingsPersistentStore.swift in Sources */, 1E4F4A5A297193DE00625985 /* MainViewController+CookiesManaged.swift in Sources */, C12324C32C4697C900FBB26B /* AutofillBreakageReportTableViewCell.swift in Sources */, 8586A10D24CBA7070049720E /* FindInPageActivity.swift in Sources */, diff --git a/DuckDuckGo/AppSettings.swift b/DuckDuckGo/AppSettings.swift index c0613b3fd5..d3b99acc70 100644 --- a/DuckDuckGo/AppSettings.swift +++ b/DuckDuckGo/AppSettings.swift @@ -82,14 +82,8 @@ protocol AppSettings: AnyObject, AppDebugSettings { var duckPlayerMode: DuckPlayerMode { get set } var duckPlayerAskModeOverlayHidden: Bool { get set } - - var newTabPageShortcutsSettings: Data? { get set } - var newTabPageSectionsSettings: Data? { get set } - var newTabPageIntroMessageEnabled: Bool? { get set } - var newTabPageIntroMessageSeenCount: Int { get set } } protocol AppDebugSettings { - var newTabPageSectionsEnabled: Bool { get set } var onboardingHighlightsEnabled: Bool { get set } } diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index ceb2538043..2c54a3a749 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -383,9 +383,6 @@ public class AppUserDefaults: AppSettings { userDefaults?.setValue(newValue.rawValue, forKey: Keys.crashCollectionOptInStatus) } } - - @UserDefaultsWrapper(key: .debugNewTabPageSectionsEnabledKey, defaultValue: false) - var newTabPageSectionsEnabled: Bool var duckPlayerMode: DuckPlayerMode { get { @@ -418,18 +415,6 @@ public class AppUserDefaults: AppSettings { } } - @UserDefaultsWrapper(key: .newTabPageShortcutsSettings, defaultValue: nil) - var newTabPageShortcutsSettings: Data? - - @UserDefaultsWrapper(key: .newTabPageSectionsSettings, defaultValue: nil) - var newTabPageSectionsSettings: Data? - - @UserDefaultsWrapper(key: .newTabPageIntroMessageEnabled, defaultValue: nil) - var newTabPageIntroMessageEnabled: Bool? - - @UserDefaultsWrapper(key: .newTabPageIntroMessageSeenCount, defaultValue: 0) - var newTabPageIntroMessageSeenCount: Int - @UserDefaultsWrapper(key: .debugOnboardingHighlightsEnabledKey, defaultValue: false) var onboardingHighlightsEnabled: Bool } diff --git a/DuckDuckGo/NewTabPageIntroDataStoring.swift b/DuckDuckGo/NewTabPageIntroDataStoring.swift new file mode 100644 index 0000000000..af42baa75f --- /dev/null +++ b/DuckDuckGo/NewTabPageIntroDataStoring.swift @@ -0,0 +1,34 @@ +// +// NewTabPageIntroDataStoring.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Core + +protocol NewTabPageIntroDataStoring: AnyObject { + var newTabPageIntroMessageEnabled: Bool? { get set } + var newTabPageIntroMessageSeenCount: Int { get set } +} + +final class NewTabPageIntroDataUserDefaultsStorage: NewTabPageIntroDataStoring { + @UserDefaultsWrapper(key: .newTabPageIntroMessageEnabled, defaultValue: nil) + var newTabPageIntroMessageEnabled: Bool? + + @UserDefaultsWrapper(key: .newTabPageIntroMessageSeenCount, defaultValue: 0) + var newTabPageIntroMessageSeenCount: Int +} diff --git a/DuckDuckGo/NewTabPageIntroMessageSetup.swift b/DuckDuckGo/NewTabPageIntroMessageSetup.swift index 41010acdf5..5ee9a2df42 100644 --- a/DuckDuckGo/NewTabPageIntroMessageSetup.swift +++ b/DuckDuckGo/NewTabPageIntroMessageSetup.swift @@ -21,24 +21,24 @@ import BrowserServicesKit import Core struct NewTabPageIntroMessageSetup { - let appSettings: AppSettings + let storage: NewTabPageIntroDataStoring let statistics: StatisticsStore let newTabPageManager: NewTabPageManaging - init(appSettings: AppSettings = AppDependencyProvider.shared.appSettings, + init(storage: NewTabPageIntroDataStoring = NewTabPageIntroDataUserDefaultsStorage(), statistics: StatisticsStore = StatisticsUserDefaults(), newTabPageManager: NewTabPageManaging = NewTabPageManager()) { - self.appSettings = appSettings + self.storage = storage self.statistics = statistics self.newTabPageManager = newTabPageManager } func perform() { - let isNotSetUp = appSettings.newTabPageIntroMessageEnabled == nil + let isNotSetUp = storage.newTabPageIntroMessageEnabled == nil guard newTabPageManager.isAvailableInPublicRelease && isNotSetUp else { return } // For new users we **don't** want intro message - appSettings.newTabPageIntroMessageEnabled = statistics.installDate != nil + storage.newTabPageIntroMessageEnabled = statistics.installDate != nil } } diff --git a/DuckDuckGo/NewTabPageManager.swift b/DuckDuckGo/NewTabPageManager.swift index 6a7dfa2baa..e4b6a33de7 100644 --- a/DuckDuckGo/NewTabPageManager.swift +++ b/DuckDuckGo/NewTabPageManager.swift @@ -31,17 +31,26 @@ protocol NewTabPageDebugging: NewTabPageManaging { var isFeatureFlagEnabled: Bool { get } } +protocol NewTabPageLocalFlagStoring: AnyObject { + var newTabPageSectionsEnabled: Bool { get set } +} + +final class NewTabPageLocalFlagUserDefaultsStorage: NewTabPageLocalFlagStoring { + @UserDefaultsWrapper(key: .debugNewTabPageSectionsEnabledKey, defaultValue: false) + var newTabPageSectionsEnabled: Bool +} + final class NewTabPageManager: NewTabPageManaging, NewTabPageDebugging { - var appDefaults: AppDebugSettings + let localFlagStorage: NewTabPageLocalFlagStoring let featureFlagger: FeatureFlagger let internalUserDecider: InternalUserDecider - init(appDefaults: AppDebugSettings = AppDependencyProvider.shared.appSettings, + init(localFlagStorage: NewTabPageLocalFlagStoring = NewTabPageLocalFlagUserDefaultsStorage(), featureFlager: FeatureFlagger = AppDependencyProvider.shared.featureFlagger, internalUserDecider: InternalUserDecider = AppDependencyProvider.shared.internalUserDecider) { - self.appDefaults = appDefaults + self.localFlagStorage = localFlagStorage self.featureFlagger = featureFlager self.internalUserDecider = internalUserDecider } @@ -67,10 +76,10 @@ final class NewTabPageManager: NewTabPageManaging, NewTabPageDebugging { var isLocalFlagEnabled: Bool { get { - appDefaults.newTabPageSectionsEnabled + localFlagStorage.newTabPageSectionsEnabled } set { - appDefaults.newTabPageSectionsEnabled = newValue + localFlagStorage.newTabPageSectionsEnabled = newValue } } diff --git a/DuckDuckGo/NewTabPageModel.swift b/DuckDuckGo/NewTabPageModel.swift index f9d826133b..8cd18b0d00 100644 --- a/DuckDuckGo/NewTabPageModel.swift +++ b/DuckDuckGo/NewTabPageModel.swift @@ -26,15 +26,15 @@ final class NewTabPageModel: ObservableObject { @Published private(set) var isOnboarding: Bool @Published var isShowingSettings: Bool - private let appSettings: AppSettings + private var introDataStorage: NewTabPageIntroDataStoring private let pixelFiring: PixelFiring.Type - init(appSettings: AppSettings = AppDependencyProvider.shared.appSettings, + init(introDataStorage: NewTabPageIntroDataStoring = NewTabPageIntroDataUserDefaultsStorage(), pixelFiring: PixelFiring.Type = Pixel.self) { - self.appSettings = appSettings + self.introDataStorage = introDataStorage self.pixelFiring = pixelFiring - isIntroMessageVisible = appSettings.newTabPageIntroMessageEnabled ?? false + isIntroMessageVisible = introDataStorage.newTabPageIntroMessageEnabled ?? false isOnboarding = false isShowingSettings = false } @@ -42,16 +42,16 @@ final class NewTabPageModel: ObservableObject { func introMessageDisplayed() { pixelFiring.fire(.newTabPageMessageDisplayed, withAdditionalParameters: [:]) - appSettings.newTabPageIntroMessageSeenCount += 1 - if appSettings.newTabPageIntroMessageSeenCount >= 3 { - appSettings.newTabPageIntroMessageEnabled = false + introDataStorage.newTabPageIntroMessageSeenCount += 1 + if introDataStorage.newTabPageIntroMessageSeenCount >= 3 { + introDataStorage.newTabPageIntroMessageEnabled = false } } func dismissIntroMessage() { pixelFiring.fire(.newTabPageMessageDismissed, withAdditionalParameters: [:]) - appSettings.newTabPageIntroMessageEnabled = false + introDataStorage.newTabPageIntroMessageEnabled = false isIntroMessageVisible = false } diff --git a/DuckDuckGo/NewTabPageSectionsDebugView.swift b/DuckDuckGo/NewTabPageSectionsDebugView.swift index 1f3a05e198..a0c4c381b0 100644 --- a/DuckDuckGo/NewTabPageSectionsDebugView.swift +++ b/DuckDuckGo/NewTabPageSectionsDebugView.swift @@ -22,8 +22,8 @@ import SwiftUI struct NewTabPageSectionsDebugView: View { private var newTabPageDebugging: NewTabPageDebugging - private var appSettings: AppSettings - + private let introDataStorage: NewTabPageIntroDataStoring + @State private var isFeatureEnabled: Bool @State private var introMessageCount: Int @State private var isIntroMessageInitialized: Bool @@ -34,24 +34,24 @@ struct NewTabPageSectionsDebugView: View { } set: { newTabPageDebugging.isLocalFlagEnabled = $0 isFeatureEnabled = newTabPageDebugging.isNewTabPageSectionsEnabled - isIntroMessageInitialized = appSettings.newTabPageIntroMessageEnabled != nil + isIntroMessageInitialized = introDataStorage.newTabPageIntroMessageEnabled != nil } } private var introMessageEnabled: Binding { Binding { - appSettings.newTabPageIntroMessageEnabled ?? false + introDataStorage.newTabPageIntroMessageEnabled ?? false } set: { - appSettings.newTabPageIntroMessageEnabled = $0 - isIntroMessageInitialized = appSettings.newTabPageIntroMessageEnabled != nil + introDataStorage.newTabPageIntroMessageEnabled = $0 + isIntroMessageInitialized = introDataStorage.newTabPageIntroMessageEnabled != nil } } private var introMessageCountBinding: Binding { Binding { - appSettings.newTabPageIntroMessageSeenCount + introDataStorage.newTabPageIntroMessageSeenCount } set: { - appSettings.newTabPageIntroMessageSeenCount = $0 + introDataStorage.newTabPageIntroMessageSeenCount = $0 introMessageCount = $0 } } @@ -60,10 +60,10 @@ struct NewTabPageSectionsDebugView: View { let manager = NewTabPageManager() newTabPageDebugging = manager isFeatureEnabled = manager.isNewTabPageSectionsEnabled - - appSettings = AppDependencyProvider.shared.appSettings - introMessageCount = appSettings.newTabPageIntroMessageSeenCount - isIntroMessageInitialized = appSettings.newTabPageIntroMessageEnabled != nil + + introDataStorage = NewTabPageIntroDataUserDefaultsStorage() + introMessageCount = introDataStorage.newTabPageIntroMessageSeenCount + isIntroMessageInitialized = introDataStorage.newTabPageIntroMessageEnabled != nil } var body: some View { @@ -134,7 +134,7 @@ struct NewTabPageSectionsDebugView: View { }) Button("Reset intro message", action: { - appSettings.newTabPageIntroMessageEnabled = nil + introDataStorage.newTabPageIntroMessageEnabled = nil introMessageCountBinding.wrappedValue = 0 isIntroMessageInitialized = false }) diff --git a/DuckDuckGo/NewTabPageSectionsSettingsStorage.swift b/DuckDuckGo/NewTabPageSectionsSettingsStorage.swift index 12f38c47e6..476baad6a7 100644 --- a/DuckDuckGo/NewTabPageSectionsSettingsStorage.swift +++ b/DuckDuckGo/NewTabPageSectionsSettingsStorage.swift @@ -28,7 +28,7 @@ typealias NewTabPageSectionsSettingsStorage = NewTabPageSettingsPersistentStorag extension NewTabPageSettingsPersistentStorage { convenience init() { - self.init(keyPath: \.newTabPageSectionsSettings, + self.init(persistentStore: NewTabPageSectionsSettingsStore(), defaultOrder: NewTabPageSection.allCases, defaultEnabledItems: NewTabPageSection.allCases) } diff --git a/DuckDuckGo/NewTabPageSettingsPersistentStorage.swift b/DuckDuckGo/NewTabPageSettingsPersistentStorage.swift index 9c746bccf6..c9b7bb3298 100644 --- a/DuckDuckGo/NewTabPageSettingsPersistentStorage.swift +++ b/DuckDuckGo/NewTabPageSettingsPersistentStorage.swift @@ -24,19 +24,20 @@ private struct NewTabPageItemSettings: Coda let enabledItems: Set } +protocol NewTabPageSettingsPersistentStore: AnyObject { + var data: Data? { get set } +} + final class NewTabPageSettingsPersistentStorage: NewTabPageSettingsStorage { private(set) var itemsOrder: [Item] private var enabledItems: Set - private var appSettings: AppSettings - private let keyPath: WritableKeyPath + private var persistentStore: any NewTabPageSettingsPersistentStore - init(appSettings: AppSettings = AppDependencyProvider.shared.appSettings, - keyPath: WritableKeyPath, + init(persistentStore: NewTabPageSettingsPersistentStore, defaultOrder: [Item], defaultEnabledItems: [Item]) { - self.appSettings = appSettings - self.keyPath = keyPath + self.persistentStore = persistentStore self.itemsOrder = defaultOrder self.enabledItems = Set(defaultEnabledItems) @@ -62,12 +63,12 @@ final class NewTabPageSettingsPersistentStorage.self, from: settingsData) { itemsOrder = settings.itemsOrder enabledItems = settings.enabledItems diff --git a/DuckDuckGo/NewTabPageSettingsPersistentStore.swift b/DuckDuckGo/NewTabPageSettingsPersistentStore.swift new file mode 100644 index 0000000000..fa3a12540f --- /dev/null +++ b/DuckDuckGo/NewTabPageSettingsPersistentStore.swift @@ -0,0 +1,31 @@ +// +// NewTabPageSettingsPersistentStore.swift +// DuckDuckGo +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Core + +final class NewTabPageShorctutsSettingsStore: NewTabPageSettingsPersistentStore { + @UserDefaultsWrapper(key: .newTabPageShortcutsSettings, defaultValue: nil) + var data: Data? +} + +final class NewTabPageSectionsSettingsStore: NewTabPageSettingsPersistentStore { + @UserDefaultsWrapper(key: .newTabPageSectionsSettings, defaultValue: nil) + var data: Data? +} diff --git a/DuckDuckGo/NewTabPageShortcutsSettingsStorage.swift b/DuckDuckGo/NewTabPageShortcutsSettingsStorage.swift index 7560398968..f4f435d921 100644 --- a/DuckDuckGo/NewTabPageShortcutsSettingsStorage.swift +++ b/DuckDuckGo/NewTabPageShortcutsSettingsStorage.swift @@ -23,7 +23,7 @@ typealias NewTabPageShortcutsSettingsStorage = NewTabPageSettingsPersistentStora extension NewTabPageSettingsPersistentStorage { convenience init() { - self.init(keyPath: \.newTabPageShortcutsSettings, + self.init(persistentStore: NewTabPageShorctutsSettingsStore(), defaultOrder: NewTabPageShortcut.allCases, defaultEnabledItems: NewTabPageShortcut.enabledByDefault) } diff --git a/DuckDuckGo/NewTabPageView.swift b/DuckDuckGo/NewTabPageView.swift index ecc09bbba7..e8a5b8584f 100644 --- a/DuckDuckGo/NewTabPageView.swift +++ b/DuckDuckGo/NewTabPageView.swift @@ -335,6 +335,10 @@ private final class PreviewMessagesConfiguration: HomePageMessagesConfiguration private extension NewTabPageSectionsSettingsStorage { static func emptyStorage() -> Self { - Self.init(keyPath: \.newTabPageSectionsSettings, defaultOrder: [], defaultEnabledItems: []) + Self.init(persistentStore: EmptyStore(), defaultOrder: [], defaultEnabledItems: []) + } + + private final class EmptyStore: NewTabPageSettingsPersistentStore { + var data: Data? } } diff --git a/DuckDuckGoTests/NewTabPageIntroMessageSetupTests.swift b/DuckDuckGoTests/NewTabPageIntroMessageSetupTests.swift index 53780e2e9f..5cc797fa44 100644 --- a/DuckDuckGoTests/NewTabPageIntroMessageSetupTests.swift +++ b/DuckDuckGoTests/NewTabPageIntroMessageSetupTests.swift @@ -22,7 +22,7 @@ import XCTest final class NewTabPageIntroMessageSetupTests: XCTestCase { - private let appSettings = AppSettingsMock() + private let storage = NewTabPageIntroDataStoringMock() private let statistics = MockStatisticsStore() private let ntpManagerMock = NewTabPageManagerMock() @@ -32,7 +32,7 @@ final class NewTabPageIntroMessageSetupTests: XCTestCase { sut.perform() - XCTAssertEqual(appSettings.newTabPageIntroMessageEnabled, true) + XCTAssertEqual(storage.newTabPageIntroMessageEnabled, true) } func testDisablesFeatureForNewUser() { @@ -41,32 +41,32 @@ final class NewTabPageIntroMessageSetupTests: XCTestCase { sut.perform() - XCTAssertEqual(appSettings.newTabPageIntroMessageEnabled, false) + XCTAssertEqual(storage.newTabPageIntroMessageEnabled, false) } func testDoesNothingIfSetAlready() { let sut = createSUT() statistics.installDate = nil - appSettings.newTabPageIntroMessageEnabled = true + storage.newTabPageIntroMessageEnabled = true sut.perform() - XCTAssertEqual(appSettings.newTabPageIntroMessageEnabled, true) + XCTAssertEqual(storage.newTabPageIntroMessageEnabled, true) } func testDoesNothingIfNotPubliclyReleased() { let sut = createSUT() statistics.installDate = nil ntpManagerMock.isAvailableInPublicRelease = false - appSettings.newTabPageIntroMessageEnabled = nil + storage.newTabPageIntroMessageEnabled = nil sut.perform() - XCTAssertNil(appSettings.newTabPageIntroMessageEnabled) + XCTAssertNil(storage.newTabPageIntroMessageEnabled) } private func createSUT() -> NewTabPageIntroMessageSetup { - NewTabPageIntroMessageSetup(appSettings: appSettings, statistics: statistics, newTabPageManager: ntpManagerMock) + NewTabPageIntroMessageSetup(storage: storage, statistics: statistics, newTabPageManager: ntpManagerMock) } } @@ -74,3 +74,8 @@ private final class NewTabPageManagerMock: NewTabPageManaging { var isNewTabPageSectionsEnabled: Bool = true var isAvailableInPublicRelease: Bool = true } + +final class NewTabPageIntroDataStoringMock: NewTabPageIntroDataStoring { + var newTabPageIntroMessageEnabled: Bool? + var newTabPageIntroMessageSeenCount: Int = 0 +} diff --git a/DuckDuckGoTests/NewTabPageModelTests.swift b/DuckDuckGoTests/NewTabPageModelTests.swift index bf71f38f22..7025fed9c1 100644 --- a/DuckDuckGoTests/NewTabPageModelTests.swift +++ b/DuckDuckGoTests/NewTabPageModelTests.swift @@ -22,45 +22,45 @@ import XCTest final class NewTabPageModelTests: XCTestCase { - let appSettings = AppSettingsMock() + let introDataStorage = NewTabPageIntroDataStoringMock() override func tearDown() { PixelFiringMock.tearDown() } func testDoesNotShowIntroIfSettingUndefined() { - let sut = NewTabPageModel(appSettings: appSettings) + let sut = NewTabPageModel(introDataStorage: introDataStorage) XCTAssertFalse(sut.isIntroMessageVisible) } func testShowsIntroMessage() { - appSettings.newTabPageIntroMessageEnabled = true - let sut = NewTabPageModel(appSettings: appSettings) + introDataStorage.newTabPageIntroMessageEnabled = true + let sut = NewTabPageModel(introDataStorage: introDataStorage) XCTAssertTrue(sut.isIntroMessageVisible) } func testDisablesIntroMessageWhenDismissed() { - appSettings.newTabPageIntroMessageEnabled = true - let sut = NewTabPageModel(appSettings: appSettings) + introDataStorage.newTabPageIntroMessageEnabled = true + let sut = NewTabPageModel(introDataStorage: introDataStorage) sut.dismissIntroMessage() XCTAssertFalse(sut.isIntroMessageVisible) - XCTAssertEqual(appSettings.newTabPageIntroMessageEnabled, false) + XCTAssertEqual(introDataStorage.newTabPageIntroMessageEnabled, false) } func testDisablesIntroMessageAfterMultipleImpressions() { - appSettings.newTabPageIntroMessageEnabled = true - let sut = NewTabPageModel(appSettings: appSettings) + introDataStorage.newTabPageIntroMessageEnabled = true + let sut = NewTabPageModel(introDataStorage: introDataStorage) for _ in 1...3 { sut.introMessageDisplayed() } XCTAssertTrue(sut.isIntroMessageVisible) // We want to keep the message visible on last occurence - XCTAssertEqual(appSettings.newTabPageIntroMessageEnabled, false) + XCTAssertEqual(introDataStorage.newTabPageIntroMessageEnabled, false) } func testFiresPixelWhenIntroMessageDismissed() { diff --git a/DuckDuckGoTests/NewTabPageSectionsSettingsModelTests.swift b/DuckDuckGoTests/NewTabPageSectionsSettingsModelTests.swift index 4a7fa31fae..bcc4b14177 100644 --- a/DuckDuckGoTests/NewTabPageSectionsSettingsModelTests.swift +++ b/DuckDuckGoTests/NewTabPageSectionsSettingsModelTests.swift @@ -60,8 +60,7 @@ final class NewTabPageSectionsSettingsModelTests: XCTestCase { private func createSUT() -> NewTabPageSectionsSettingsModel { let storage = NewTabPageSectionsSettingsStorage( - appSettings: AppSettingsMock(), - keyPath: \.newTabPageSectionsSettings, + persistentStore: NewTabPageSettingsPersistentStoreMock(), defaultOrder: NewTabPageSection.allCases, defaultEnabledItems: NewTabPageSection.allCases ) @@ -69,3 +68,7 @@ final class NewTabPageSectionsSettingsModelTests: XCTestCase { return NewTabPageSectionsSettingsModel(storage: storage, pixelFiring: PixelFiringMock.self) } } + +final class NewTabPageSettingsPersistentStoreMock: NewTabPageSettingsPersistentStore { + var data: Data? +} diff --git a/DuckDuckGoTests/NewTabPageSettingsPersistentStorageTests.swift b/DuckDuckGoTests/NewTabPageSettingsPersistentStorageTests.swift index 30f58cf733..a54ba48fe4 100644 --- a/DuckDuckGoTests/NewTabPageSettingsPersistentStorageTests.swift +++ b/DuckDuckGoTests/NewTabPageSettingsPersistentStorageTests.swift @@ -23,7 +23,7 @@ import XCTest final class NewTabPageSettingsPersistentStorageTests: XCTestCase { - private var appSettings = AppSettingsMock() + private var settingsPersistentStore = NewTabPageSettingsPersistentStoreMock() func testLoadsInitialStateFromDefaults() { let sut = createSUT() @@ -35,7 +35,7 @@ final class NewTabPageSettingsPersistentStorageTests: XCTestCase { func testUsesDefaultsIfDataCorrupted() { let sut = createSUT() - appSettings[keyPath: Constant.keyPath] = "Random data".data(using: .utf8) + settingsPersistentStore.data = "Random data".data(using: .utf8) XCTAssertEqual(sut.itemsOrder, Constant.defaultItems) XCTAssertEqual(sut.enabledItems, Constant.defaultItems) @@ -90,15 +90,13 @@ final class NewTabPageSettingsPersistentStorageTests: XCTestCase { } private func createSUT(defaultOrder: [StorageItem] = Constant.defaultItems, defaultEnabledItems: [StorageItem] = Constant.defaultItems) -> NewTabPageSettingsPersistentStorage { - NewTabPageSettingsPersistentStorage(appSettings: appSettings, - keyPath: Constant.keyPath, + NewTabPageSettingsPersistentStorage(persistentStore: settingsPersistentStore, defaultOrder: defaultOrder, defaultEnabledItems: defaultEnabledItems) } private enum Constant { static let defaultItems = [StorageItem.one, .two, .three] - static let keyPath = \AppSettings.newTabPageSectionsSettings } } diff --git a/DuckDuckGoTests/NewTabPageShortcutsSettingsModelTests.swift b/DuckDuckGoTests/NewTabPageShortcutsSettingsModelTests.swift index 463dc48c07..012f2b3b23 100644 --- a/DuckDuckGoTests/NewTabPageShortcutsSettingsModelTests.swift +++ b/DuckDuckGoTests/NewTabPageShortcutsSettingsModelTests.swift @@ -54,8 +54,7 @@ final class NewTabPageShortcutsSettingsModelTests: XCTestCase { private func createSUT() -> NewTabPageShortcutsSettingsModel { let storage = NewTabPageShortcutsSettingsStorage( - appSettings: AppSettingsMock(), - keyPath: \.newTabPageShortcutsSettings, + persistentStore: NewTabPageSettingsPersistentStoreMock(), defaultOrder: NewTabPageShortcut.allCases, defaultEnabledItems: NewTabPageShortcut.allCases )