Skip to content

Commit

Permalink
Show shortcuts for enabled features only (#3193)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/72649045549333/1207977496450580/f
Tech Design URL:
CC:

**Description**:

Some features are available conditionally. These should not be visible
on NTP and in settings.

**Steps to test this PR**:
1. Enable New Tab Page sections in Debug menu.
2. Reorder shortcuts and enable/disable as you see fit.
3. Change shortcuts visibility in
`NewTabPageShortcutsSettingsModel.swift`
4. Make sure only allowed shortcuts are visible.
5. Reorder and enable/disable once again.
6. Verify the order and settings are preserved.
7. Re-enable all shortcuts in `NewTabPageShortcutsSettingsModel.swift`.
8. Verify all shortcuts are visible.
  • Loading branch information
dus7 authored Aug 7, 2024
1 parent 4d77d20 commit 059c6f5
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 6 deletions.
4 changes: 4 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@
6F9FFE302C57B34800A238BE /* NewTabPageSectionsSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9FFE2F2C57B34800A238BE /* NewTabPageSectionsSettingsModel.swift */; };
6FA3438F2C3D3BC300470677 /* Favorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA3438E2C3D3BC300470677 /* Favorite.swift */; };
6FA343922C3D3C3B00470677 /* FavoriteIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA343912C3D3C3B00470677 /* FavoriteIconView.swift */; };
6FABAA692C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FABAA682C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift */; };
6FB1FE9E2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1FE9D2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift */; };
6FB1FEA22C256ACD0075B68B /* NewTabPageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1FEA12C256ACD0075B68B /* NewTabPageManager.swift */; };
6FB2A67A2C2C5BAE004D20C8 /* FavoriteEmptyStateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB2A6792C2C5BAE004D20C8 /* FavoriteEmptyStateItem.swift */; };
Expand Down Expand Up @@ -1504,6 +1505,7 @@
6F9FFE2F2C57B34800A238BE /* NewTabPageSectionsSettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSectionsSettingsModel.swift; sourceTree = "<group>"; };
6FA3438E2C3D3BC300470677 /* Favorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Favorite.swift; sourceTree = "<group>"; };
6FA343912C3D3C3B00470677 /* FavoriteIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteIconView.swift; sourceTree = "<group>"; };
6FABAA682C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageShortcutsSettingsStorageTests.swift; sourceTree = "<group>"; };
6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = "<group>"; };
6FB1FE9D2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSectionsDebugView.swift; sourceTree = "<group>"; };
6FB1FEA12C256ACD0075B68B /* NewTabPageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3618,6 +3620,7 @@
6FD0C41E2C5BF097000561C9 /* NewTabPageIntroMessageSetupTests.swift */,
6FD0C4202C5BF774000561C9 /* NewTabPageModelTests.swift */,
564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */,
6FABAA682C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift */,
);
name = NewTabPage;
sourceTree = "<group>";
Expand Down Expand Up @@ -7549,6 +7552,7 @@
C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */,
B6AD9E3A28D456820019CDE9 /* PrivacyConfigurationManagerMock.swift in Sources */,
F189AED71F18F6DE001EBAE1 /* TabTests.swift in Sources */,
6FABAA692C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift in Sources */,
9F5E5AB22C3E606D00165F54 /* ContextualOnboardingPresenterTests.swift in Sources */,
9F4CC5172C48B8D4006A96EB /* TabViewControllerDaxDialogTests.swift in Sources */,
F13B4BFB1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift in Sources */,
Expand Down
38 changes: 34 additions & 4 deletions DuckDuckGo/NewTabPageSettingsModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,30 @@ final class NewTabPageSettingsModel<SettingItem: NewTabPageSettingsStorageItem,
/// Settings page settings collection with bindings
@Published private(set) var itemsSettings: [NTPSetting<SettingItem>] = []

private var filteredItemsOrder: [SettingItem] = []

/// Enabled items, ordered.
@Published private(set) var enabledItems: [SettingItem] = []

private var indexMapping: [Int: Int] = [:]

private let settingsStorage: Storage
private let visibilityFilter: ((SettingItem) -> Bool)

init(settingsStorage: Storage) {
init(settingsStorage: Storage, visibilityFilter: @escaping ((SettingItem) -> Bool) = { _ in true }) {
self.settingsStorage = settingsStorage
self.visibilityFilter = visibilityFilter

updatePublishedValues()
}

func moveItems(from: IndexSet, to: Int) {
let from = mapIndexSet(from)

// If index is not found it means we're moving to the end.
// Guard index range in case there's no filtering.
let to = indexMapping[to] ?? min(settingsStorage.itemsOrder.count, to + 1)

settingsStorage.moveItems(from, toOffset: to)
updatePublishedValues()
}
Expand All @@ -51,18 +63,36 @@ final class NewTabPageSettingsModel<SettingItem: NewTabPageSettingsStorageItem,
}

private func populateEnabledItems() {
enabledItems = settingsStorage.enabledItems
enabledItems = filteredItemsOrder.filter(settingsStorage.isEnabled(_:))
}

private func populateSettings() {
itemsSettings = settingsStorage.itemsOrder.map { item in
NTPSetting(item: item, isEnabled: Binding(get: {
let allItemsOrder = settingsStorage.itemsOrder
filteredItemsOrder = allItemsOrder.filter(visibilityFilter)

itemsSettings = filteredItemsOrder.compactMap { item in
return NTPSetting(item: item,
isEnabled: Binding(get: {
self.settingsStorage.isEnabled(item)
}, set: { newValue in
self.settingsStorage.setItem(item, enabled: newValue)
self.updatePublishedValues()
}))
}

for (index, item) in allItemsOrder.enumerated() {
if let filteredIndex = filteredItemsOrder.firstIndex(of: item) {
indexMapping[filteredIndex] = index
}
}
}

private func mapIndexSet(_ indexSet: IndexSet) -> IndexSet {
let mappedIndices = indexSet.compactMap { index in
indexMapping[index]
}

return IndexSet(mappedIndices)
}
}

Expand Down
13 changes: 11 additions & 2 deletions DuckDuckGo/NewTabPageShortcutsSettingsModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,20 @@
//

import Foundation
import BrowserServicesKit

typealias NewTabPageShortcutsSettingsModel = NewTabPageSettingsModel<NewTabPageShortcut, NewTabPageShortcutsSettingsStorage>

extension NewTabPageShortcutsSettingsModel {
convenience init(storage: NewTabPageShortcutsSettingsStorage = NewTabPageShortcutsSettingsStorage()) {
self.init(settingsStorage: storage)
convenience init(storage: NewTabPageShortcutsSettingsStorage = NewTabPageShortcutsSettingsStorage(),
featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger) {
self.init(settingsStorage: storage) { shortcut in
switch shortcut {
case .aiChat, .bookmarks, .downloads, .settings:
return true
case .passwords:
return featureFlagger.isFeatureOn(.autofillAccessCredentialManagement)
}
}
}
}
121 changes: 121 additions & 0 deletions DuckDuckGoTests/NewTabPageShortcutsSettingsStorageTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// NewTabPageShortcutsSettingsStorageTests.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 XCTest
@testable import DuckDuckGo

final class NewTabPageSettingsModelTests: XCTestCase {

private let storage = StorageMock(itemsOrder: SettingsItemMock.allCases)

func testFiltersSettingsUsingProvidedFilter() {
let sut = createSUT { item in
item != .two
}

XCTAssertFalse(sut.enabledItems.contains(.two))
}

func testMovingReordersFullSet() {
let sut = createSUT { item in
item != .two
}

sut.moveItems(from: IndexSet(integer: 0), to: 1)

XCTAssertEqual(storage.itemsOrder, [.two, .one, .three])
}

func testMovingItemToEndWhenNoFilter() {
let sut = createSUT()

sut.moveItems(from: IndexSet(integer: 0), to: 3)

XCTAssertEqual(storage.itemsOrder, [.two, .three, .one])
}

func testMovingItemToEndWhenFiltered() {
let sut = createSUT { item in
item != .two
}

sut.moveItems(from: IndexSet(integer: 0), to: 2)

XCTAssertEqual(storage.itemsOrder, [.two, .three, .one])
}

func testMovingToStartWhenFiltered() {
let sut = createSUT { item in
item != .one
}

sut.moveItems(from: IndexSet(integer: 1), to: 0)

XCTAssertEqual(storage.itemsOrder, [.one, .three, .two])
}

func testItemsSettingsAreFiltered() {
let sut = createSUT { item in
item != .one
}

XCTAssertEqual(sut.itemsSettings.map(\.item), [.two, .three])
}

func testEnabledItemsAreFiltered() {
let sut = createSUT { item in
item != .one
}

XCTAssertEqual(sut.enabledItems, [.two, .three])
}

private func createSUT(filter: @escaping (SettingsItemMock) -> Bool = { _ in true }) -> NewTabPageSettingsModel<SettingsItemMock, StorageMock> {
NewTabPageSettingsModel(settingsStorage: storage, visibilityFilter: filter)
}
}

private final class StorageMock: NewTabPageSettingsStorage {
var itemsOrder: [SettingsItemMock]

init(itemsOrder: [SettingsItemMock]) {
self.itemsOrder = itemsOrder
}

func isEnabled(_ item: SettingsItemMock) -> Bool {
return true
}

func setItem(_ item: SettingsItemMock, enabled: Bool) {

}

func moveItems(_ fromOffsets: IndexSet, toOffset: Int) {
itemsOrder.move(fromOffsets: fromOffsets, toOffset: toOffset)
}

func save() {
}
}

private enum SettingsItemMock: CaseIterable, NewTabPageSettingsStorageItem {
case one
case two
case three
}

0 comments on commit 059c6f5

Please sign in to comment.