Skip to content

Commit

Permalink
Add FXIOS-10971 [Homepage] [Context Menu] display proper actions per …
Browse files Browse the repository at this point in the history
…item (#23959)

Add FXIOS-10971 [Homepage] [Context Menu] display proper actions per item
  • Loading branch information
cyndichin authored Dec 28, 2024
1 parent 360b1de commit b5c0ffd
Show file tree
Hide file tree
Showing 14 changed files with 333 additions and 27 deletions.
8 changes: 8 additions & 0 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -825,6 +825,7 @@
8A4B14852CF8D67800FCE2D0 /* UnifiedTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4B14842CF8D67300FCE2D0 /* UnifiedTile.swift */; };
8A4B14872CF8D81800FCE2D0 /* UnifiedAdsProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4B14862CF8D80F00FCE2D0 /* UnifiedAdsProviderTests.swift */; };
8A4B148B2CF919C800FCE2D0 /* UnifiedAdsConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4B148A2CF919C300FCE2D0 /* UnifiedAdsConverter.swift */; };
8A4B66422D1DC96A008C5B64 /* ContextMenuConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4B66412D1DC962008C5B64 /* ContextMenuConfigurationTests.swift */; };
8A4EA0D12C010BE700E4E4F1 /* MicrosurveySurfaceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EA0D02C010BE700E4E4F1 /* MicrosurveySurfaceManager.swift */; };
8A4EA0D42C01100200E4E4F1 /* MicrosurveySurfaceManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EA0D22C010BF800E4E4F1 /* MicrosurveySurfaceManagerTests.swift */; };
8A4EA0D92C01127C00E4E4F1 /* MicrosurveyMockModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A4EA0D72C01125100E4E4F1 /* MicrosurveyMockModel.swift */; };
Expand Down Expand Up @@ -1003,6 +1004,7 @@
8ABE9F1B2CB4620A0080E1DF /* RemoteSettingsFetchConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = 8AF4E76D2C41D86100BAD91C /* RemoteSettingsFetchConfig.json */; };
8ABE9F1D2CB462730080E1DF /* RemoteSettingsFetchConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABE9F1C2CB462730080E1DF /* RemoteSettingsFetchConfigTests.swift */; };
8ABE9F1E2CB462CA0080E1DF /* RemoteSettingsFetchConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = 8AF4E76D2C41D86100BAD91C /* RemoteSettingsFetchConfig.json */; };
8AC0B1C02D1D935C004237FD /* ContextMenuConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC0B1BF2D1D9356004237FD /* ContextMenuConfiguration.swift */; };
8AC1065F28D0CD700013263A /* OpenQLPreviewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC1065E28D0CD700013263A /* OpenQLPreviewHelper.swift */; };
8AC225662B6D403200CDA7FD /* HomepageTelemetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC225642B6D3FA400CDA7FD /* HomepageTelemetryTests.swift */; };
8AC5D55F28BFE6C8001F6F7F /* Presenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AC5D55E28BFE6C8001F6F7F /* Presenter.swift */; };
Expand Down Expand Up @@ -7603,6 +7605,7 @@
8A4B14842CF8D67300FCE2D0 /* UnifiedTile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedTile.swift; sourceTree = "<group>"; };
8A4B14862CF8D80F00FCE2D0 /* UnifiedAdsProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedAdsProviderTests.swift; sourceTree = "<group>"; };
8A4B148A2CF919C300FCE2D0 /* UnifiedAdsConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedAdsConverter.swift; sourceTree = "<group>"; };
8A4B66412D1DC962008C5B64 /* ContextMenuConfigurationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuConfigurationTests.swift; sourceTree = "<group>"; };
8A4EA0D02C010BE700E4E4F1 /* MicrosurveySurfaceManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrosurveySurfaceManager.swift; sourceTree = "<group>"; };
8A4EA0D22C010BF800E4E4F1 /* MicrosurveySurfaceManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MicrosurveySurfaceManagerTests.swift; sourceTree = "<group>"; };
8A4EA0D72C01125100E4E4F1 /* MicrosurveyMockModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosurveyMockModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -7787,6 +7790,7 @@
8ABCFEA42B45CAC300C2988A /* PrivateBrowsingTelemetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateBrowsingTelemetryTests.swift; sourceTree = "<group>"; };
8ABDBAA52CB6BF6600B51F63 /* HomepageHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageHeaderCell.swift; sourceTree = "<group>"; };
8ABE9F1C2CB462730080E1DF /* RemoteSettingsFetchConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteSettingsFetchConfigTests.swift; sourceTree = "<group>"; };
8AC0B1BF2D1D9356004237FD /* ContextMenuConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenuConfiguration.swift; sourceTree = "<group>"; };
8AC1065E28D0CD700013263A /* OpenQLPreviewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenQLPreviewHelper.swift; sourceTree = "<group>"; };
8AC225642B6D3FA400CDA7FD /* HomepageTelemetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageTelemetryTests.swift; sourceTree = "<group>"; };
8AC5D55E28BFE6C8001F6F7F /* Presenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presenter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -11374,6 +11378,7 @@
isa = PBXGroup;
children = (
8AD3AFC42D143F0D00CFC887 /* TopSitesDimensionImplementationTests.swift */,
8A4B66412D1DC962008C5B64 /* ContextMenuConfigurationTests.swift */,
8A87B4352CC1A8FD003A9239 /* Mock */,
8A87B4302CC1A3BA003A9239 /* PocketManagerTests.swift */,
8A552AC22CB43A7000564C98 /* Redux */,
Expand Down Expand Up @@ -11737,6 +11742,7 @@
8A62B15B2CED40800045F46E /* ContextMenu */ = {
isa = PBXGroup;
children = (
8AC0B1BF2D1D9356004237FD /* ContextMenuConfiguration.swift */,
8A62B15C2CED408B0045F46E /* ContextMenuState.swift */,
);
path = ContextMenu;
Expand Down Expand Up @@ -16515,6 +16521,7 @@
AB9CBC052C53B64C00102610 /* TrackingProtectionState.swift in Sources */,
D0E89A2920910917001CE5C7 /* DownloadsPanel.swift in Sources */,
D3BA7E0E1B0E934F00153782 /* ContextMenuHelper.swift in Sources */,
8AC0B1C02D1D935C004237FD /* ContextMenuConfiguration.swift in Sources */,
E19B38B328A42D5E00D8C541 /* WallpaperCollectionViewCell.swift in Sources */,
0BB5B30B1AC0AD1F0052877D /* LoginsHelper.swift in Sources */,
8A3EF8092A2FD02B00796E3A /* ExperimentsSettings.swift in Sources */,
Expand Down Expand Up @@ -17430,6 +17437,7 @@
965C3C96293431FC006499ED /* MockLaunchSessionProvider.swift in Sources */,
C869915628917803007ACC5C /* WallpaperJSONTestProvider.swift in Sources */,
8A95FF672B1E97A800AC303D /* TelemetryContextualIdentifierTests.swift in Sources */,
8A4B66422D1DC96A008C5B64 /* ContextMenuConfigurationTests.swift in Sources */,
965C3C9829343445006499ED /* MockAppSessionManager.swift in Sources */,
8AFCE50929DE136300B1B253 /* MockLaunchFinishedLoadingDelegate.swift in Sources */,
8AE1E1DB27B1C1320024C45E /* SearchBarSettingsViewModelTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ class BrowserCoordinator: BaseCoordinator,
browserViewController.homePanel(didSelectURL: url, visitType: visitType, isGoogleTopSite: isGoogleTopSite)
}

func showContextMenu() {
// TODO: FXIOS-10613 - Add proper context menu actions
let state = ContextMenuState()
func showContextMenu(for configuration: ContextMenuConfiguration) {
let state = ContextMenuState(configuration: configuration)
let viewModel = PhotonActionSheetViewModel(actions: state.actions,
site: state.site,
modalStyle: .overFullScreen)
let sheet = PhotonActionSheet(viewModel: viewModel, windowUUID: windowUUID)
sheet.modalTransitionStyle = .crossDissolve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ protocol BrowserNavigationHandler: AnyObject, QRCodeNavigationHandler {
func navigateFromHomePanel(to url: URL, visitType: VisitType, isGoogleTopSite: Bool)

/// Navigates to our custom context menu (Photon Action Sheet)
func showContextMenu()
func showContextMenu(for configuration: ContextMenuConfiguration)

/// Navigates to the edit bookmark view
func showEditBookmark(parentFolder: FxBookmarkNode, bookmark: FxBookmarkNode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import Redux
class NavigationBrowserAction: Action {
let url: URL?
let isGoogleTopSite: Bool?
let contextMenuConfiguration: ContextMenuConfiguration?

init(url: URL? = nil,
isGoogleTopSite: Bool? = nil,
contextMenuConfiguration: ContextMenuConfiguration? = nil,
windowUUID: WindowUUID,
actionType: ActionType) {
self.url = url
self.isGoogleTopSite = isGoogleTopSite
self.contextMenuConfiguration = contextMenuConfiguration
super.init(windowUUID: windowUUID,
actionType: actionType)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,30 @@ import Foundation
/// View types that the browser coordinator can navigate to
enum BrowserNavigationDestination: Equatable {
// Native views
case contextMenu
case customizeHomepage
case trackingProtectionSettings

// Webpage views
case link

// Context menu views
case contextMenu
}

/// This type exists as a field on the BrowserViewControllerState
struct NavigationDestination: Equatable {
let destination: BrowserNavigationDestination
let url: URL?
let isGoogleTopSite: Bool?
let contextMenuConfiguration: ContextMenuConfiguration?

init(
_ destination: BrowserNavigationDestination,
url: URL? = nil,
isGoogleTopSite: Bool? = nil
isGoogleTopSite: Bool? = nil,
contextMenuConfiguration: ContextMenuConfiguration? = nil
) {
self.destination = destination
self.url = url
self.isGoogleTopSite = isGoogleTopSite
self.contextMenuConfiguration = contextMenuConfiguration
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ struct BrowserViewControllerState: ScreenState, Equatable {
windowUUID: state.windowUUID,
browserViewType: state.browserViewType,
microsurveyState: MicrosurveyPromptState.reducer(state.microsurveyState, action),
navigationDestination: NavigationDestination(.contextMenu)
navigationDestination: NavigationDestination(.contextMenu,
contextMenuConfiguration: action.contextMenuConfiguration)
)

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2103,7 +2103,15 @@ class BrowserViewController: UIViewController,
private func handleNavigation(to type: NavigationDestination) {
switch type.destination {
case .contextMenu:
navigationHandler?.showContextMenu()
guard let configuration = type.contextMenuConfiguration else {
logger.log(
"configuration should not be nil when navigating for a context menu type",
level: .warning,
category: .coordinator
)
return
}
navigationHandler?.showContextMenu(for: configuration)
case .trackingProtectionSettings:
navigationHandler?.show(settings: .contentBlocker)
case .customizeHomepage:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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 Storage
struct ContextMenuConfiguration: Equatable {
var homepageSection: HomepageSection
var sourceView: UIView?

var site: Site? {
switch item {
case .topSite(let state, _):
return state.site
case .pocket(let state):
return Site(url: state.url?.absoluteString ?? "",
title: state.title)
case .pocketDiscover(let state):
return Site(url: state.url?.absoluteString ?? "",
title: state.title)
default:
return nil
}
}

private var item: HomepageItem?

init(
homepageSection: HomepageSection,
item: HomepageItem? = nil,
sourceView: UIView? = nil
) {
self.homepageSection = homepageSection
self.item = item
self.sourceView = sourceView
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,182 @@

import Common
import Foundation
import Storage

/// State to populate actions for the `PhotonActionSheet` view
/// Ideally, we want that view to subscribe to the store and update its state following the redux pattern
/// For now, we will instantiate this state and populate the associated view model instead to avoid
/// increasing scope of homepage rebuild project.

struct ContextMenuState {
var site: Site?
var actions: [[PhotonRowActions]] = [[]]

init() {
actions = [[getTemporaryAction()]]
init(configuration: ContextMenuConfiguration) {
guard let site = configuration.site else { return }
self.site = site

switch configuration.homepageSection {
case .topSites:
actions = [getTopSitesActions(site: site)]
case .pocket:
actions = [getPocketActions(site: site)]
default:
return
}
}

// MARK: - Top sites item's context menu actions
private func getTopSitesActions(site: Site) -> [PhotonRowActions] {
let topSiteActions: [PhotonRowActions]
if site is PinnedSite {
topSiteActions = getPinnedTileActions()
} else if site as? SponsoredTile != nil {
topSiteActions = getSponsoredTileActions()
} else {
topSiteActions = getOtherTopSitesActions()
}
return topSiteActions
}

private func getPinnedTileActions() -> [PhotonRowActions] {
return [getRemovePinTopSiteAction(),
getOpenInNewTabAction(),
getOpenInNewPrivateTabAction(),
getRemoveTopSiteAction(),
getShareAction()]
}

private func getSponsoredTileActions() -> [PhotonRowActions] {
return [getOpenInNewTabAction(),
getOpenInNewPrivateTabAction(),
getSettingsAction(),
getSponsoredContentAction(),
getShareAction()]
}

private func getOtherTopSitesActions() -> [PhotonRowActions] {
return [getPinTopSiteAction(),
getOpenInNewTabAction(),
getOpenInNewPrivateTabAction(),
getRemoveTopSiteAction(),
getShareAction()]
}

/// This action removes the tile out of the top sites.
/// If site is pinned, it removes it from pinned and remove from top sites in general.
private func getRemoveTopSiteAction() -> PhotonRowActions {
return SingleActionViewModel(title: .RemoveContextMenuTitle,
iconString: StandardImageIdentifiers.Large.cross,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
}).items
}

private func getPinTopSiteAction() -> PhotonRowActions {
return SingleActionViewModel(title: .PinTopsiteActionTitle2,
iconString: StandardImageIdentifiers.Large.pin,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
}).items
}

/// This unpin action removes the top site from the location it's in.
/// The tile can stil appear in the top sites as unpinned.
private func getRemovePinTopSiteAction() -> PhotonRowActions {
return SingleActionViewModel(title: .UnpinTopsiteActionTitle2,
iconString: StandardImageIdentifiers.Large.pinSlash,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
}).items
}

private func getSettingsAction() -> PhotonRowActions {
return SingleActionViewModel(title: .FirefoxHomepage.ContextualMenu.Settings,
iconString: StandardImageIdentifiers.Large.settings,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
}).items
}
// TODO: FXIOS-10613 - Update with proper actions
private func getTemporaryAction() -> PhotonRowActions {

private func getSponsoredContentAction() -> PhotonRowActions {
return SingleActionViewModel(title: .FirefoxHomepage.ContextualMenu.SponsoredContent,
iconString: StandardImageIdentifiers.Large.helpCircle,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
}).items
}

// MARK: - Pocket item's context menu actions
private func getPocketActions(site: Site) -> [PhotonRowActions] {
let openInNewTabAction = getOpenInNewTabAction()
let openInNewPrivateTabAction = getOpenInNewPrivateTabAction()
let shareAction = getShareAction()
let bookmarkAction = getBookmarkAction(site: site)

return [openInNewTabAction, openInNewPrivateTabAction, bookmarkAction, shareAction]
}

// MARK: - Default actions
private func getOpenInNewTabAction() -> PhotonRowActions {
return SingleActionViewModel(
title: .OpenInNewTabContextMenuTitle,
iconString: StandardImageIdentifiers.Large.plus,
tapHandler: { _ in
}).items
allowIconScaling: true
) { _ in
// TODO: FXIOS-10613 - Add proper actions
}.items
}

private func getOpenInNewPrivateTabAction() -> PhotonRowActions {
return SingleActionViewModel(
title: .OpenInNewPrivateTabContextMenuTitle,
iconString: StandardImageIdentifiers.Large.privateMode,
allowIconScaling: true
) { _ in
// TODO: FXIOS-10613 - Add proper actions
}.items
}

private func getBookmarkAction(site: Site) -> PhotonRowActions {
let bookmarkAction: SingleActionViewModel
if site.bookmarked ?? false {
bookmarkAction = getRemoveBookmarkAction()
} else {
bookmarkAction = getAddBookmarkAction()
}
return bookmarkAction.items
}

private func getRemoveBookmarkAction() -> SingleActionViewModel {
return SingleActionViewModel(title: .RemoveBookmarkContextMenuTitle,
iconString: StandardImageIdentifiers.Large.bookmarkSlash,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
})
}

private func getAddBookmarkAction() -> SingleActionViewModel {
return SingleActionViewModel(title: .BookmarkContextMenuTitle,
iconString: StandardImageIdentifiers.Large.bookmark,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
})
}

private func getShareAction() -> PhotonRowActions {
return SingleActionViewModel(title: .ShareContextMenuTitle,
iconString: StandardImageIdentifiers.Large.share,
allowIconScaling: true,
tapHandler: { _ in
// TODO: FXIOS-10613 - Add proper actions
}).items
}
}
Loading

0 comments on commit b5c0ffd

Please sign in to comment.