Skip to content

Commit

Permalink
Add FXIOS-10379 [Unified Search] Implement initial redux setup (#22767)
Browse files Browse the repository at this point in the history
* Stub placeholder button for testing search engine toggle.

* Stub in basic SearchEngineSelectionState.

* Stub in SearchEngineSelectionAction and SearchEngineSelectionMiddleware.

* Hook up SearchEngineSelectionViewController to use new redux setup for SearchEngineSelection.

* Make OpenSearchEngine test helpers public.

* Add SearchEngineSelectionStateTests. Add SearchEngineSelectionMiddlewareTests.
  • Loading branch information
ih-codes authored Oct 25, 2024
1 parent b9b88d9 commit 82dcbe9
Show file tree
Hide file tree
Showing 11 changed files with 397 additions and 10 deletions.
28 changes: 28 additions & 0 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1832,6 +1832,11 @@
EBFDB790211C83A5005CCA2F /* BrowserViewController+FindInPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBFDB787211C83A5005CCA2F /* BrowserViewController+FindInPage.swift */; };
ED07C0E62CCACD7E006C0627 /* Locale+possibilitiesForLanguageIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED07C0E52CCACD7E006C0627 /* Locale+possibilitiesForLanguageIdentifier.swift */; };
ED07C0E72CCACE18006C0627 /* Locale+possibilitiesForLanguageIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED07C0E52CCACD7E006C0627 /* Locale+possibilitiesForLanguageIdentifier.swift */; };
ED07C0EB2CCADCD5006C0627 /* SearchEngineSelectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED07C0E92CCADCD5006C0627 /* SearchEngineSelectionState.swift */; };
ED07C0ED2CCAE745006C0627 /* SearchEngineSelectionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED07C0EC2CCAE745006C0627 /* SearchEngineSelectionAction.swift */; };
ED07C0EF2CCAE856006C0627 /* SearchEngineSelectionMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED07C0EE2CCAE856006C0627 /* SearchEngineSelectionMiddleware.swift */; };
ED07C0F22CCAFED1006C0627 /* SearchEngineSelectionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED07C0F02CCAFEC2006C0627 /* SearchEngineSelectionStateTests.swift */; };
ED07C0F52CCB020B006C0627 /* SearchEngineSelectionMiddlewareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED07C0F32CCB0200006C0627 /* SearchEngineSelectionMiddlewareTests.swift */; };
ED28DAC92C45A95F00D2641C /* TabScrollBehaviorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED28DAC82C45A95F00D2641C /* TabScrollBehaviorModel.swift */; };
ED371A682CB881EF0099F3C8 /* SearchEngineSelectionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED371A672CB881EF0099F3C8 /* SearchEngineSelectionCoordinator.swift */; };
ED45893E2CC800D9006F2C0B /* SearchEngineSelectionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED45893D2CC800D9006F2C0B /* SearchEngineSelectionViewControllerTests.swift */; };
Expand Down Expand Up @@ -8984,6 +8989,11 @@
EC934F7BB2F8B247BC714BE2 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/ClearPrivateData.strings"; sourceTree = "<group>"; };
ECDA4593850BA4E08FDAC5AF /* kk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kk; path = kk.lproj/ClearPrivateData.strings; sourceTree = "<group>"; };
ED07C0E52CCACD7E006C0627 /* Locale+possibilitiesForLanguageIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Locale+possibilitiesForLanguageIdentifier.swift"; sourceTree = "<group>"; };
ED07C0E92CCADCD5006C0627 /* SearchEngineSelectionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchEngineSelectionState.swift; sourceTree = "<group>"; };
ED07C0EC2CCAE745006C0627 /* SearchEngineSelectionAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineSelectionAction.swift; sourceTree = "<group>"; };
ED07C0EE2CCAE856006C0627 /* SearchEngineSelectionMiddleware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineSelectionMiddleware.swift; sourceTree = "<group>"; };
ED07C0F02CCAFEC2006C0627 /* SearchEngineSelectionStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineSelectionStateTests.swift; sourceTree = "<group>"; };
ED07C0F32CCB0200006C0627 /* SearchEngineSelectionMiddlewareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineSelectionMiddlewareTests.swift; sourceTree = "<group>"; };
ED264785844AD63AD616A882 /* cy */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cy; path = cy.lproj/InfoPlist.strings; sourceTree = "<group>"; };
ED28DAC82C45A95F00D2641C /* TabScrollBehaviorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabScrollBehaviorModel.swift; sourceTree = "<group>"; };
ED364EECB95B96AB639A82DE /* su */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = su; path = su.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -10853,6 +10863,7 @@
8A1E3BE428CBBF1E003388C4 /* SearchEngines */ = {
isa = PBXGroup;
children = (
ED07C0EA2CCADCD5006C0627 /* Redux */,
ED371A692CB885B20099F3C8 /* Views */,
EDC3C2552CCAC9CB005A047F /* SearchEnginesManager.swift */,
8A1E3BE528CBBF44003388C4 /* OpenSearchEngine.swift */,
Expand All @@ -10872,6 +10883,8 @@
D3FA777A1A43B2990010CD32 /* SearchTests.swift */,
96A5F734298D8EB900234E5F /* MockSearchEngineProvider.swift */,
EDC3D34E2CB5E70500C62DE3 /* SearchEngineTestAssets.xcassets */,
ED07C0F02CCAFEC2006C0627 /* SearchEngineSelectionStateTests.swift */,
ED07C0F32CCB0200006C0627 /* SearchEngineSelectionMiddlewareTests.swift */,
);
path = SearchEngines;
sourceTree = "<group>";
Expand Down Expand Up @@ -13512,6 +13525,16 @@
path = InternalSchemeHandler;
sourceTree = "<group>";
};
ED07C0EA2CCADCD5006C0627 /* Redux */ = {
isa = PBXGroup;
children = (
ED07C0E92CCADCD5006C0627 /* SearchEngineSelectionState.swift */,
ED07C0EC2CCAE745006C0627 /* SearchEngineSelectionAction.swift */,
ED07C0EE2CCAE856006C0627 /* SearchEngineSelectionMiddleware.swift */,
);
path = Redux;
sourceTree = "<group>";
};
ED371A692CB885B20099F3C8 /* Views */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -15570,6 +15593,7 @@
E1A6AB4628CA6A4C00EBEBDD /* String+Extension.swift in Sources */,
1D2F68AF2ACB272500524B92 /* RemoteTabsTableViewController.swift in Sources */,
210887CC293E8800000AB4EE /* LegacyRemoteTabsErrorCell.swift in Sources */,
ED07C0ED2CCAE745006C0627 /* SearchEngineSelectionAction.swift in Sources */,
8AABBCFC2A0010900089941E /* GleanWrapper.swift in Sources */,
8C92DE912A7128CB0090BD28 /* ProductAnalysisResponse.swift in Sources */,
F85C7F0F271DD154004BDBA4 /* AppAuthenticator.swift in Sources */,
Expand Down Expand Up @@ -15638,6 +15662,7 @@
6ACB550C28633860007A6ABD /* TabManagerNavDelegate.swift in Sources */,
E18259DF29B25E4F00E6BE76 /* UserNotificationCenterProtocol.swift in Sources */,
DFFC9AD12A681FA0002A6AAD /* NimbusFakespotFeatureLayer.swift in Sources */,
ED07C0EB2CCADCD5006C0627 /* SearchEngineSelectionState.swift in Sources */,
1DEBC55E2AC4ED70006E4801 /* RemoteTabsPanel.swift in Sources */,
435D660523D794B90046EFA2 /* UpdateViewModel.swift in Sources */,
9658143C29FAB610007339BD /* CreditCardInputFieldHelper.swift in Sources */,
Expand Down Expand Up @@ -16311,6 +16336,7 @@
C84655E42887394B00861B4A /* WallpaperMetadata.swift in Sources */,
8AB8572E27D94A1A0075C173 /* UXSizeClass.swift in Sources */,
965C3C8F29313A1B006499ED /* AppSessionManager.swift in Sources */,
ED07C0EF2CCAE856006C0627 /* SearchEngineSelectionMiddleware.swift in Sources */,
45D5EDA729269F7500311934 /* DataObserver.swift in Sources */,
81A3F6F02C2DAEE200BDD86B /* MainMenuCoordinator.swift in Sources */,
961577922A38FDB300391E8D /* SponsoredTileDataUtility.swift in Sources */,
Expand Down Expand Up @@ -16571,6 +16597,7 @@
8A5D1CA02A30C9D7005AD35C /* MockAppSettingsDelegate.swift in Sources */,
1D7B789F2AE088930011E9F2 /* EventQueueTests.swift in Sources */,
21A1C3C72996AFF800181B7C /* OverlayModeManagerTests.swift in Sources */,
ED07C0F52CCB020B006C0627 /* SearchEngineSelectionMiddlewareTests.swift in Sources */,
C8DF92F72A14101500AA7B05 /* OnboardingViewControllerProtocolTests.swift in Sources */,
8A4EA0D92C01127C00E4E4F1 /* MicrosurveyMockModel.swift in Sources */,
1D4D79472BF2F4FD007C6796 /* Throttler.swift in Sources */,
Expand Down Expand Up @@ -16633,6 +16660,7 @@
8CEDF07E2BFE04B100D2617B /* AddressListViewModelTests.swift in Sources */,
21D884412A79628E00AF144C /* MockSettingsDelegate.swift in Sources */,
F1BC457E2A40F6D2005541D5 /* EnhancedTrackingProtectionCoordinatorTests.swift in Sources */,
ED07C0F22CCAFED1006C0627 /* SearchEngineSelectionStateTests.swift in Sources */,
8A13FA8F2AD83F00007527AB /* DefaultBackgroundTabLoaderTests.swift in Sources */,
8A827E362C20CB5B008D5E3C /* MicrosurveyPromptMiddlewareTests.swift in Sources */,
5A31275828906422001F30FA /* BookmarksDelegateMock.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import Common
import Foundation
import MenuKit
import Redux

final class SearchEngineSelectionAction: Action {
let searchEngines: [OpenSearchEngine]?

init(windowUUID: WindowUUID, actionType: ActionType, searchEngines: [OpenSearchEngine]? = nil) {
self.searchEngines = searchEngines
super.init(windowUUID: windowUUID, actionType: actionType)
}
}

enum SearchEngineSelectionActionType: ActionType {
case viewDidLoad
case didLoadSearchEngines
}

enum SearchEngineSelectionMiddlewareActionType: ActionType {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import Common
import Redux
import ToolbarKit

final class SearchEngineSelectionMiddleware {
private let profile: Profile
private let logger: Logger
private let searchEnginesManager: SearchEnginesManager

init(profile: Profile = AppContainer.shared.resolve(),
searchEnginesManager: SearchEnginesManager? = nil,
logger: Logger = DefaultLogger.shared) {
self.profile = profile
self.logger = logger
self.searchEnginesManager = searchEnginesManager ?? SearchEnginesManager(prefs: profile.prefs, files: profile.files)
}

lazy var searchEngineSelectionProvider: Middleware<AppState> = { [self] state, action in
guard let action = action as? SearchEngineSelectionAction else { return }

switch action.actionType {
case SearchEngineSelectionActionType.viewDidLoad:
guard let searchEngines = searchEnginesManager.orderedEngines, !searchEngines.isEmpty else {
// The SearchEngineManager should have loaded these by now, but if not, attempt to fetch the search engines
self.searchEnginesManager.getOrderedEngines { [weak self] searchEngines in
self?.notifyDidLoad(windowUUID: action.windowUUID, searchEngines: searchEngines)
}
return
}

notifyDidLoad(windowUUID: action.windowUUID, searchEngines: searchEngines)

default:
break
}
}

private func notifyDidLoad(windowUUID: WindowUUID, searchEngines: [OpenSearchEngine]) {
let action = SearchEngineSelectionAction(
windowUUID: windowUUID,
actionType: SearchEngineSelectionActionType.didLoadSearchEngines,
searchEngines: searchEngines
)
store.dispatch(action)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/

import Common
import Shared
import Redux

struct SearchEngineSelectionState: ScreenState, Equatable {
var windowUUID: WindowUUID
var shouldDismiss: Bool
// Default search engine should appear in position 0
var searchEngines: [OpenSearchEngine]

init(appState: AppState, uuid: WindowUUID) {
guard let state = store.state.screenState(
SearchEngineSelectionState.self,
for: .searchEngineSelection,
window: uuid
) else {
self.init(windowUUID: uuid)
return
}

self.init(
windowUUID: state.windowUUID,
searchEngines: state.searchEngines,
shouldDismiss: state.shouldDismiss
)
}

init(windowUUID: WindowUUID) {
self.init(windowUUID: windowUUID, searchEngines: [])
}

private init(
windowUUID: WindowUUID,
searchEngines: [OpenSearchEngine],
shouldDismiss: Bool = false
) {
self.windowUUID = windowUUID
self.searchEngines = searchEngines
self.shouldDismiss = shouldDismiss
}

/// Returns a new `SearchEngineSelectionState` which clears any transient data.
static func defaultState(fromPreviousState state: SearchEngineSelectionState) -> SearchEngineSelectionState {
return SearchEngineSelectionState(
windowUUID: state.windowUUID,
searchEngines: state.searchEngines
)
}

static let reducer: Reducer<Self> = { state, action in
// Only process actions for the current window
guard action.windowUUID == .unavailable || action.windowUUID == state.windowUUID else {
return defaultState(fromPreviousState: state)
}

switch action.actionType {
case SearchEngineSelectionActionType.didLoadSearchEngines:
guard let action = action as? SearchEngineSelectionAction,
let searchEngines = action.searchEngines
else { return defaultState(fromPreviousState: state) }

return SearchEngineSelectionState(
windowUUID: state.windowUUID,
searchEngines: searchEngines
)

default:
return defaultState(fromPreviousState: state)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import Redux
class SearchEngineSelectionViewController: UIViewController,
UISheetPresentationControllerDelegate,
UIPopoverPresentationControllerDelegate,
Themeable {
Themeable,
StoreSubscriber {
// MARK: - Properties
var notificationCenter: NotificationProtocol
var themeManager: ThemeManager
Expand All @@ -20,6 +21,7 @@ class SearchEngineSelectionViewController: UIViewController,

weak var coordinator: SearchEngineSelectionCoordinator?
private let windowUUID: WindowUUID
private var state: SearchEngineSelectionState
private let logger: Logger

// MARK: - UI/UX elements
Expand All @@ -32,6 +34,15 @@ class SearchEngineSelectionViewController: UIViewController,

view.addTarget(self, action: #selector(self.didTapOpenSettings), for: .touchUpInside)
}
// FIXME FXIOS-10189 This will be deleted later.
private lazy var placeholderSwitchSearchEngineButton: UIButton = .build { view in
view.setTitle("Test changing search engine", for: .normal)
view.setTitleColor(.systemPink, for: .normal)
view.titleLabel?.numberOfLines = 0
view.titleLabel?.textAlignment = .center

view.addTarget(self, action: #selector(self.testDidChangeSearchEngine), for: .touchUpInside)
}

// MARK: - Initializers and Lifecycle

Expand All @@ -42,11 +53,15 @@ class SearchEngineSelectionViewController: UIViewController,
logger: Logger = DefaultLogger.shared
) {
self.windowUUID = windowUUID
self.state = SearchEngineSelectionState(windowUUID: windowUUID)
self.logger = logger
self.notificationCenter = notificationCenter
self.themeManager = themeManager
self.logger = logger

super.init(nibName: nil, bundle: nil)

subscribeToRedux()

// TODO Additional setup to come
// ...
}
Expand All @@ -55,6 +70,10 @@ class SearchEngineSelectionViewController: UIViewController,
fatalError("init(coder:) has not been implemented")
}

deinit {
unsubscribeFromRedux()
}

override func viewDidLoad() {
super.viewDidLoad()

Expand All @@ -63,6 +82,13 @@ class SearchEngineSelectionViewController: UIViewController,

setupView()
listenForThemeChange(view)

store.dispatch(
SearchEngineSelectionAction(
windowUUID: self.windowUUID,
actionType: SearchEngineSelectionActionType.viewDidLoad
)
)
}

override func viewWillAppear(_ animated: Bool) {
Expand All @@ -75,14 +101,54 @@ class SearchEngineSelectionViewController: UIViewController,

private func setupView() {
view.addSubview(placeholderOpenSettingsButton)
view.addSubviews(placeholderSwitchSearchEngineButton)

NSLayoutConstraint.activate([
placeholderOpenSettingsButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 80),
placeholderOpenSettingsButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
placeholderOpenSettingsButton.widthAnchor.constraint(equalToConstant: 200)
placeholderOpenSettingsButton.widthAnchor.constraint(equalToConstant: 200),

placeholderSwitchSearchEngineButton.topAnchor.constraint(equalTo: placeholderOpenSettingsButton.bottomAnchor),
placeholderSwitchSearchEngineButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
])
}

// MARK: - Redux

func subscribeToRedux() {
store.dispatch(
ScreenAction(
windowUUID: windowUUID,
actionType: ScreenActionType.showScreen,
screen: .searchEngineSelection
)
)

let uuid = windowUUID
store.subscribe(self, transform: {
return $0.select({ appState in
return SearchEngineSelectionState(appState: appState, uuid: uuid)
})
})
}

func unsubscribeFromRedux() {
store.dispatch(
ScreenAction(
windowUUID: windowUUID,
actionType: ScreenActionType.closeScreen,
screen: .searchEngineSelection
)
)
}

func newState(state: SearchEngineSelectionState) {
self.state = state

// FIXME FXIOS-10189 Eventually we'll have a tableview. Placeholder for temporary testing redux.
placeholderSwitchSearchEngineButton.setTitle(state.searchEngines.last?.shortName ?? "Empty!", for: .normal)
}

// MARK: - Theme

func applyTheme() {
Expand All @@ -103,4 +169,11 @@ class SearchEngineSelectionViewController: UIViewController,
func didTapOpenSettings(sender: UIButton) {
coordinator?.navigateToSearchSettings(animated: true)
}

// FIXME FXIOS-10189 This will be deleted later.
@objc
func testDidChangeSearchEngine(sender: UIButton) {
// TODO FXIOS-10384 Push action to the toolbar to update the search engine selection for the next search and
// to focus the toolbar (if it isn't already focused).
}
}
Loading

0 comments on commit 82dcbe9

Please sign in to comment.