From c6e8ef89e72a33d2456924b1bd7d2da2f07b28ca Mon Sep 17 00:00:00 2001 From: PARAIPAN SORIN Date: Mon, 26 Feb 2024 13:19:24 +0100 Subject: [PATCH] Add bookmarks, history and synced tabs suggestions --- .../Views/BrowserViewController.swift | 3 +- .../Browser/SearchEngines/SearchEngines.swift | 46 ++++++++- .../Browser/SearchViewController.swift | 94 ++++++++++++++----- .../SearchSettingsTableViewController.swift | 75 +++++++++++++++ firefox-ios/Shared/Prefs.swift | 3 + 5 files changed, 196 insertions(+), 25 deletions(-) diff --git a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift index 00b00c7d9daf..9d297a636d42 100644 --- a/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/BrowserViewController/Views/BrowserViewController.swift @@ -2015,8 +2015,7 @@ class BrowserViewController: UIViewController, let alwaysShowSearchSuggestionsView = browserViewControllerState? .searchScreenState .showSearchSugestionsView ?? false - let isSettingEnabled = profile.searchEngines.shouldShowPrivateModeSearchSuggestions || - profile.searchEngines.shouldShowPrivateModeFirefoxSuggestions + let isSettingEnabled = profile.searchEngines.isPrivateModeSettingEnabled return featureFlagEnabled && !alwaysShowSearchSuggestionsView && !isSettingEnabled } diff --git a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngines.swift b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngines.swift index 2fbaa4fd296a..23dc0cae6bce 100644 --- a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngines.swift +++ b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEngines.swift @@ -43,10 +43,19 @@ class SearchEngines { init(prefs: Prefs, files: FileAccessor, engineProvider: SearchEngineProvider = DefaultSearchEngineProvider()) { self.prefs = prefs - // By default, show search suggestions + self.shouldShowSearchSuggestions = prefs.boolForKey( PrefsKeys.SearchSettings.showSearchSuggestions ) ?? true + shouldShowBrowsingHistorySuggestions = prefs.boolForKey( + PrefsKeys.SearchSettings.showFirefoxBrowsingHistorySuggestions + ) ?? true + shouldShowBookmarksSuggestions = prefs.boolForKey( + PrefsKeys.SearchSettings.showFirefoxBookmarksSuggestions + ) ?? true + shouldShowSyncedTabsSuggestions = prefs.boolForKey( + PrefsKeys.SearchSettings.showFirefoxSyncedTabsSuggestions + ) ?? true shouldShowFirefoxSuggestions = prefs.boolForKey( PrefsKeys.SearchSettings.showFirefoxNonSponsoredSuggestions ) ?? true @@ -117,6 +126,33 @@ class SearchEngines { } } + var shouldShowBrowsingHistorySuggestions: Bool { + didSet { + prefs.setBool( + shouldShowBrowsingHistorySuggestions, + forKey: PrefsKeys.SearchSettings.showFirefoxBrowsingHistorySuggestions + ) + } + } + + var shouldShowBookmarksSuggestions: Bool { + didSet { + prefs.setBool( + shouldShowBookmarksSuggestions, + forKey: PrefsKeys.SearchSettings.showFirefoxBookmarksSuggestions + ) + } + } + + var shouldShowSyncedTabsSuggestions: Bool { + didSet { + prefs.setBool( + shouldShowSyncedTabsSuggestions, + forKey: PrefsKeys.SearchSettings.showFirefoxSyncedTabsSuggestions + ) + } + } + var shouldShowFirefoxSuggestions: Bool { didSet { prefs.setBool( @@ -153,6 +189,14 @@ class SearchEngines { } } + var isPrivateModeSettingEnabled: Bool { + return shouldShowPrivateModeSearchSuggestions || + shouldShowPrivateModeFirefoxSuggestions || + shouldShowBookmarksSuggestions || + shouldShowSyncedTabsSuggestions || + shouldShowBrowsingHistorySuggestions + } + func isEngineEnabled(_ engine: OpenSearchEngine) -> Bool { return disabledEngines.index(forKey: engine.shortName) == nil } diff --git a/firefox-ios/Client/Frontend/Browser/SearchViewController.swift b/firefox-ios/Client/Frontend/Browser/SearchViewController.swift index 22366f11549d..ba47b704283f 100644 --- a/firefox-ios/Client/Frontend/Browser/SearchViewController.swift +++ b/firefox-ios/Client/Frontend/Browser/SearchViewController.swift @@ -133,13 +133,25 @@ class SearchViewController: SiteTableViewController, var searchFeature: FeatureHolder static var userAgent: String? + private var bookmarkSites: [Site] { + data.compactMap { $0 } + .filter { $0.bookmarked == true } + } + + private var historySites: [Site] { + data.compactMap { $0 } + .filter { $0.bookmarked == false } + } + var hasFirefoxSuggestions: Bool { let dataCount = data.count - return dataCount != 0 - || !filteredOpenedTabs.isEmpty - || !filteredRemoteClientTabs.isEmpty - || !searchHighlights.isEmpty - || !firefoxSuggestions.isEmpty + return dataCount != 0 && + (model.shouldShowBookmarksSuggestions || + model.shouldShowBrowsingHistorySuggestions) + || !filteredOpenedTabs.isEmpty + || (!filteredRemoteClientTabs.isEmpty && model.shouldShowSyncedTabsSuggestions) + || !searchHighlights.isEmpty + || (!firefoxSuggestions.isEmpty && shouldShowNonSponsoredSuggestions) } init(profile: Profile, @@ -679,11 +691,10 @@ class SearchViewController: SiteTableViewController, loadSearchHighlights() _ = loadFirefoxSuggestions() - guard shoudShowSearchEngineSuggestions else { return } let tempSearchQuery = searchQuery suggestClient?.query(searchQuery, callback: { suggestions, error in - if error == nil { + if error == nil, self.shoudShowSearchEngineSuggestions { self.suggestions = suggestions! // Remove user searching term inside suggestions list self.suggestions?.removeAll(where: { @@ -696,7 +707,8 @@ class SearchViewController: SiteTableViewController, } // If there are no suggestions, just use whatever the user typed. - if suggestions?.isEmpty ?? true { + if self.shoudShowSearchEngineSuggestions && + suggestions?.isEmpty ?? true { self.suggestions = [self.searchQuery] } @@ -841,9 +853,10 @@ class SearchViewController: SiteTableViewController, case .openedTabs: return filteredOpenedTabs.count case .remoteTabs: - return filteredRemoteClientTabs.count + return model.shouldShowSyncedTabsSuggestions ? + filteredRemoteClientTabs.count : 0 case .bookmarksAndHistory: - return data.count + return numberOfItemsFromData case .searchHighlights: return searchHighlights.count case .firefoxSuggestions: @@ -851,6 +864,19 @@ class SearchViewController: SiteTableViewController, } } + private var numberOfItemsFromData: Int { + if model.shouldShowBookmarksSuggestions, + model.shouldShowBrowsingHistorySuggestions { + return data.count + } else if model.shouldShowBookmarksSuggestions { + return bookmarkSites.count + } else if model.shouldShowBrowsingHistorySuggestions { + return historySites.count + } + + return 0 + } + func numberOfSections(in tableView: UITableView) -> Int { return SearchListSection.allCases.count } @@ -941,7 +967,8 @@ class SearchViewController: SiteTableViewController, cell = twoLineCell } case .remoteTabs: - if self.filteredRemoteClientTabs.count > indexPath.row { + if model.shouldShowSyncedTabsSuggestions, + filteredRemoteClientTabs.count > indexPath.row { let remoteTab = self.filteredRemoteClientTabs[indexPath.row].tab let remoteClient = self.filteredRemoteClientTabs[indexPath.row].client twoLineCell.descriptionLabel.isHidden = false @@ -956,24 +983,31 @@ class SearchViewController: SiteTableViewController, cell = twoLineCell } case .bookmarksAndHistory: - if let site = data[indexPath.row] { - let isBookmark = site.bookmarked ?? false + if model.shouldShowBookmarksSuggestions && + model.shouldShowBrowsingHistorySuggestions { + if let site = data[indexPath.row] { + configureBookmarksAndHistoryCell( + twoLineCell, + site.title, + site.url, + site.bookmarked ?? false + ) + cell = twoLineCell + } + } else if model.shouldShowBookmarksSuggestions { + let site = bookmarkSites[indexPath.row] + configureBookmarksAndHistoryCell(twoLineCell, site.title, site.url, true) cell = twoLineCell - twoLineCell.descriptionLabel.isHidden = false - twoLineCell.titleLabel.text = site.title - twoLineCell.descriptionLabel.text = site.url - twoLineCell.leftOverlayImageView.image = isBookmark ? self.bookmarkedBadge : nil - twoLineCell.leftImageView.layer.borderColor = SearchViewControllerUX.IconBorderColor.cgColor - twoLineCell.leftImageView.layer.borderWidth = SearchViewControllerUX.IconBorderWidth - twoLineCell.leftImageView.setFavicon(FaviconImageViewModel(siteURLString: site.url)) - twoLineCell.accessoryView = nil + } else if model.shouldShowBrowsingHistorySuggestions { + let site = historySites[indexPath.row] + configureBookmarksAndHistoryCell(twoLineCell, site.title, site.url) cell = twoLineCell } + case .searchHighlights: let highlightItem = searchHighlights[indexPath.row] let urlString = highlightItem.urlString ?? "" let site = Site(url: urlString, title: highlightItem.displayTitle) - cell = twoLineCell twoLineCell.descriptionLabel.isHidden = false twoLineCell.titleLabel.text = highlightItem.displayTitle twoLineCell.descriptionLabel.text = urlString @@ -1006,6 +1040,22 @@ class SearchViewController: SiteTableViewController, return cell } + private func configureBookmarksAndHistoryCell( + _ cell: TwoLineImageOverlayCell, + _ title: String, + _ description: String, + _ isBookmark: Bool = false + ) { + cell.descriptionLabel.isHidden = false + cell.titleLabel.text = title + cell.descriptionLabel.text = description + cell.leftOverlayImageView.image = isBookmark ? bookmarkedBadge : nil + cell.leftImageView.layer.borderColor = SearchViewControllerUX.IconBorderColor.cgColor + cell.leftImageView.layer.borderWidth = SearchViewControllerUX.IconBorderWidth + cell.leftImageView.setFavicon(FaviconImageViewModel(siteURLString: description)) + cell.accessoryView = nil + } + private func shouldShowHeader(for section: Int) -> Bool { switch section { case SearchListSection.remoteTabs.rawValue: diff --git a/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift b/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift index f414b1f82a33..1eea833b2eef 100644 --- a/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift +++ b/firefox-ios/Client/Frontend/Settings/SearchSettingsTableViewController.swift @@ -50,6 +50,9 @@ final class SearchSettingsTableViewController: ThemedTableViewController, Featur } private enum FirefoxSuggestItem: Int, CaseIterable { + case browsingHistory + case bookmarks + case syncedTabs case nonSponsored case sponsored case privateSuggestions @@ -229,6 +232,63 @@ final class SearchSettingsTableViewController: ThemedTableViewController, Featur case .firefoxSuggestSettings: switch indexPath.item { + case FirefoxSuggestItem.browsingHistory.rawValue: + let setting = BoolSetting( + prefs: profile.prefs, + theme: themeManager.currentTheme, + prefKey: PrefsKeys.SearchSettings.showFirefoxBrowsingHistorySuggestions, + defaultValue: model.shouldShowBrowsingHistorySuggestions, + titleText: String.localizedStringWithFormat( + .Settings.Search.Suggest.SearchBrowsingHistory + ) + ) + setting.onConfigureCell(cell, theme: themeManager.currentTheme) + setting.control.switchView.addTarget( + self, + action: #selector(didToggleBrowsingHistorySuggestions), + for: .valueChanged + ) + cell.editingAccessoryView = setting.control + cell.selectionStyle = .none + + case FirefoxSuggestItem.bookmarks.rawValue: + let setting = BoolSetting( + prefs: profile.prefs, + theme: themeManager.currentTheme, + prefKey: PrefsKeys.SearchSettings.showFirefoxBookmarksSuggestions, + defaultValue: model.shouldShowBookmarksSuggestions, + titleText: String.localizedStringWithFormat( + .Settings.Search.Suggest.SearchBookmarks + ) + ) + setting.onConfigureCell(cell, theme: themeManager.currentTheme) + setting.control.switchView.addTarget( + self, + action: #selector(didToggleBookmarksSuggestions), + for: .valueChanged + ) + cell.editingAccessoryView = setting.control + cell.selectionStyle = .none + + case FirefoxSuggestItem.syncedTabs.rawValue: + let setting = BoolSetting( + prefs: profile.prefs, + theme: themeManager.currentTheme, + prefKey: PrefsKeys.SearchSettings.showFirefoxSyncedTabsSuggestions, + defaultValue: model.shouldShowSyncedTabsSuggestions, + titleText: String.localizedStringWithFormat( + .Settings.Search.Suggest.SearchSyncedTabs + ) + ) + setting.onConfigureCell(cell, theme: themeManager.currentTheme) + setting.control.switchView.addTarget( + self, + action: #selector(didToggleSyncedTabsSuggestions), + for: .valueChanged + ) + cell.editingAccessoryView = setting.control + cell.selectionStyle = .none + case FirefoxSuggestItem.nonSponsored.rawValue: let setting = BoolSetting( prefs: profile.prefs, @@ -581,6 +641,21 @@ extension SearchSettingsTableViewController { model.shouldShowPrivateModeFirefoxSuggestions = toggle.isOn } + @objc + func didToggleBrowsingHistorySuggestions(_ toggle: ThemedSwitch) { + model.shouldShowBrowsingHistorySuggestions = toggle.isOn + } + + @objc + func didToggleBookmarksSuggestions(_ toggle: ThemedSwitch) { + model.shouldShowBookmarksSuggestions = toggle.isOn + } + + @objc + func didToggleSyncedTabsSuggestions(_ toggle: ThemedSwitch) { + model.shouldShowSyncedTabsSuggestions = toggle.isOn + } + @objc func didToggleEnableNonSponsoredSuggestions(_ toggle: ThemedSwitch) { model.shouldShowFirefoxSuggestions = toggle.isOn diff --git a/firefox-ios/Shared/Prefs.swift b/firefox-ios/Shared/Prefs.swift index f6e635af9b1b..3c969a9c01d2 100644 --- a/firefox-ios/Shared/Prefs.swift +++ b/firefox-ios/Shared/Prefs.swift @@ -77,6 +77,9 @@ public struct PrefsKeys { } public struct SearchSettings { + public static let showFirefoxBrowsingHistorySuggestions = "FirefoxSuggestBrowsingHistorySuggestions" + public static let showFirefoxBookmarksSuggestions = "FirefoxSuggestBookmarksSuggestions" + public static let showFirefoxSyncedTabsSuggestions = "FirefoxSuggestSyncedTabsSuggestions" public static let showFirefoxNonSponsoredSuggestions = "FirefoxSuggestShowNonSponsoredSuggestions" public static let showFirefoxSponsoredSuggestions = "FirefoxSuggestShowSponsoredSuggestions" public static let showPrivateModeFirefoxSuggestions = "ShowPrivateModeFirefoxSuggestionsKey"