Skip to content

Commit 0250de4

Browse files
authored
Add FXIOS-15848 [Add Shortcut Tile] Implement add shortcuts tile (#33929)
1 parent 36cde0c commit 0250de4

16 files changed

Lines changed: 396 additions & 19 deletions

firefox-ios/Client/Frontend/Home/Homepage/HomepageDiffableDataSource.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ final class HomepageDiffableDataSource: UICollectionViewDiffableDataSource<Homep
4747
case privacyNotice
4848
case messageCard(MessageCardConfiguration)
4949
case topSite(TopSiteConfiguration, TextColor?)
50+
case addShortcutTile(TextColor?)
5051
case topSiteEmpty
5152
case searchBar
5253
case jumpBackIn(JumpBackInTabConfiguration)
@@ -95,6 +96,15 @@ final class HomepageDiffableDataSource: UICollectionViewDiffableDataSource<Homep
9596
return nil
9697
}
9798
}
99+
100+
var canHandleLongPress: Bool {
101+
switch self {
102+
case .addShortcutTile:
103+
return false
104+
default:
105+
return true
106+
}
107+
}
98108
}
99109

100110
func updateSnapshot(
@@ -183,14 +193,22 @@ final class HomepageDiffableDataSource: UICollectionViewDiffableDataSource<Homep
183193
and textColor: TextColor?
184194
) -> TopSitesSnapshotData? {
185195
guard topSitesState.shouldShowSection else { return nil }
186-
let topSites: [HomeItem] = topSitesState.topSitesData.prefix(
187-
topSitesState.numberOfRows * topSitesState.numberOfTilesPerRow
196+
let maxVisibleItemCount = topSitesState.numberOfRows * topSitesState.numberOfTilesPerRow
197+
guard maxVisibleItemCount > 0 else { return nil }
198+
199+
let topSitesItems: [HomeItem] = topSitesState.topSitesData.prefix(
200+
maxVisibleItemCount
188201
).compactMap {
189202
.topSite($0, textColor)
190203
}
191-
guard !topSites.isEmpty else { return nil }
204+
let allItems = topSitesState.shouldShowAddShortcutTile
205+
? topSitesItems + [.addShortcutTile(textColor)]
206+
: topSitesItems
207+
let visibleItems = Array(allItems.prefix(maxVisibleItemCount))
208+
guard !visibleItems.isEmpty else { return nil }
209+
192210
return TopSitesSnapshotData(
193-
items: topSites,
211+
items: visibleItems,
194212
numberOfTilesPerRow: topSitesState.numberOfTilesPerRow,
195213
shouldShowSectionHeader: topSitesState.shouldShowSectionHeader
196214
)

firefox-ios/Client/Frontend/Home/Homepage/HomepageViewController.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,10 @@ final class HomepageViewController: UIViewController,
640640
return configuredCell(cellType: TopSiteCell.self, at: indexPath) { cell in
641641
cell.configure(site, position: indexPath.row, theme: currentTheme, textColor: textColor)
642642
}
643+
case .addShortcutTile(let textColor):
644+
return configuredCell(cellType: TopSiteCell.self, at: indexPath) { cell in
645+
cell.configureAddShortcutTile(theme: currentTheme, textColor: textColor)
646+
}
643647
case .topSiteEmpty:
644648
return configuredCell(cellType: EmptyTopSiteCell.self, at: indexPath) { cell in
645649
cell.applyTheme(theme: currentTheme)
@@ -1001,7 +1005,7 @@ final class HomepageViewController: UIViewController,
10011005
)
10021006
return
10031007
}
1004-
if section.canHandleLongPress {
1008+
if section.canHandleLongPress && item.canHandleLongPress {
10051009
navigateToContextMenu(for: item, sourceView: sourceView)
10061010
}
10071011
}
@@ -1264,9 +1268,9 @@ final class HomepageViewController: UIViewController,
12641268
)
12651269
return
12661270
}
1267-
dispatchDidSelectCardItemAction(with: item)
12681271
switch item {
12691272
case .topSite(let config, _):
1273+
dispatchDidSelectCardItemAction(with: item)
12701274
let destination = NavigationDestination(
12711275
.link,
12721276
url: config.site.url.asURL,
@@ -1280,11 +1284,13 @@ final class HomepageViewController: UIViewController,
12801284
actionType: TopSitesActionType.tapOnHomepageTopSitesCell
12811285
)
12821286
case .searchBar:
1287+
dispatchDidSelectCardItemAction(with: item)
12831288
dispatchNavigationBrowserAction(
12841289
with: NavigationDestination(.homepageZeroSearch),
12851290
actionType: NavigationBrowserActionType.tapOnHomepageSearchBar
12861291
)
12871292
case .jumpBackIn(let config):
1293+
dispatchDidSelectCardItemAction(with: item)
12881294
store.dispatch(
12891295
JumpBackInAction(
12901296
tab: config.tab,
@@ -1293,6 +1299,7 @@ final class HomepageViewController: UIViewController,
12931299
)
12941300
)
12951301
case .bookmark(let config):
1302+
dispatchDidSelectCardItemAction(with: item)
12961303
let destination = NavigationDestination(
12971304
.link,
12981305
url: URIFixup.getURL(config.site.url),
@@ -1301,6 +1308,7 @@ final class HomepageViewController: UIViewController,
13011308
)
13021309
dispatchNavigationBrowserAction(with: destination, actionType: NavigationBrowserActionType.tapOnCell)
13031310
case .merino(let story, _):
1311+
dispatchDidSelectCardItemAction(with: item)
13041312
let destination = NavigationDestination(
13051313
.link,
13061314
url: story.url,

firefox-ios/Client/Frontend/Home/Homepage/Layout/HomepageLayoutMeasurementCache.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct HomepageLayoutMeasurementCache {
1515
let containerWidth: Double
1616
let isLandscape: Bool
1717
let shouldShowSection: Bool
18+
let shouldShowAddShortcutTile: Bool
1819
let contentSizeCategory: UIContentSizeCategory
1920
}
2021

firefox-ios/Client/Frontend/Home/Homepage/Layout/HomepageSectionLayoutProvider.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ final class HomepageSectionLayoutProvider: FeatureFlaggable {
633633
containerWidth: containerWidth,
634634
isLandscape: UIDevice.current.orientation.isLandscape,
635635
shouldShowSection: topSitesState.shouldShowSection,
636+
shouldShowAddShortcutTile: topSitesState.shouldShowAddShortcutTile,
636637
contentSizeCategory: contentSizeCategory
637638
)
638639

@@ -655,7 +656,7 @@ final class HomepageSectionLayoutProvider: FeatureFlaggable {
655656
}
656657

657658
let cellsData = topSitesState.topSitesData.prefix(maxCells)
658-
guard !cellsData.isEmpty else {
659+
guard !cellsData.isEmpty || topSitesState.shouldShowAddShortcutTile else {
659660
measurementsCache.setHeight(0, for: measurementKey)
660661
return 0
661662
}
@@ -669,11 +670,17 @@ final class HomepageSectionLayoutProvider: FeatureFlaggable {
669670
}
670671

671672
// Build array of configured cells for the data being displayed on the homepage
672-
let allCells = cellsData.map { data in
673+
var allCells = cellsData.map { data in
673674
let cell = TopSiteCell()
674675
cell.configure(data, position: 0, theme: LightTheme(), textColor: .black)
675676
return cell
676677
}
678+
if topSitesState.shouldShowAddShortcutTile {
679+
let cell = TopSiteCell()
680+
cell.configureAddShortcutTile(theme: LightTheme(), textColor: .black)
681+
allCells.append(cell)
682+
}
683+
allCells = Array(allCells.prefix(maxCells))
677684

678685
// Group into rows and compute each rows max height
679686
let rowHeights = stride(from: 0, to: allCells.count, by: cols).map { start in

firefox-ios/Client/Frontend/Home/Homepage/TopSites/TopSiteCell.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class TopSiteCell: UICollectionViewCell, ReusableCell {
1717

1818
struct UX {
1919
static let imageBackgroundSize = CGSize(width: 60, height: 60)
20+
static let addShortcutIconSize = CGSize(width: 24, height: 24)
2021
static let pinIconSize = CGSize(width: 8, height: 8)
2122
static let textSafeSpace: CGFloat = 6
2223
static let faviconCornerRadius: CGFloat = 16
@@ -37,6 +38,11 @@ class TopSiteCell: UICollectionViewCell, ReusableCell {
3738
return imageView
3839
}()
3940

41+
private lazy var addShortcutImageView: UIImageView = .build { imageView in
42+
imageView.image = UIImage.templateImageNamed(StandardImageIdentifiers.Large.plus)
43+
imageView.isHidden = true
44+
}
45+
4046
private lazy var descriptionWrapper: UIStackView = .build { stackView in
4147
stackView.backgroundColor = .clear
4248
stackView.axis = .vertical
@@ -106,6 +112,8 @@ class TopSiteCell: UICollectionViewCell, ReusableCell {
106112
titleLabel.text = nil
107113
sponsoredLabel.text = nil
108114
pinImageView.isHidden = true
115+
imageView.isHidden = true
116+
addShortcutImageView.isHidden = true
109117
imageViewConstraints.forEach { $0.constant = 0 }
110118
}
111119

@@ -132,6 +140,8 @@ class TopSiteCell: UICollectionViewCell, ReusableCell {
132140
self.theme = theme
133141
homeTopSite = topSite
134142
titleLabel.text = topSite.title
143+
imageView.isHidden = false
144+
addShortcutImageView.isHidden = true
135145
selectedOverlay.isHidden = true
136146
accessibilityLabel = topSite.accessibilityLabel
137147
accessibilityTraits = .link
@@ -173,13 +183,30 @@ class TopSiteCell: UICollectionViewCell, ReusableCell {
173183
applyTheme(theme: theme)
174184
}
175185

186+
func configureAddShortcutTile(theme: Theme, textColor: UIColor?) {
187+
self.theme = theme
188+
self.textColor = textColor
189+
homeTopSite = nil
190+
titleLabel.text = .FirefoxHomepage.Shortcuts.AddShortcut.TileTitle
191+
sponsoredLabel.text = nil
192+
pinImageView.isHidden = true
193+
imageView.isHidden = true
194+
addShortcutImageView.isHidden = false
195+
selectedOverlay.isHidden = true
196+
accessibilityLabel = .FirefoxHomepage.Shortcuts.AddShortcut.TileTitle
197+
accessibilityTraits = .button
198+
199+
applyTheme(theme: theme)
200+
}
201+
176202
// MARK: - Setup Helper methods
177203

178204
private func setupLayout() {
179205
descriptionWrapper.addArrangedSubview(titleLabel)
180206
descriptionWrapper.addArrangedSubview(sponsoredLabel)
181207

182208
rootContainer.addSubview(imageView)
209+
rootContainer.addSubview(addShortcutImageView)
183210
rootContainer.addSubview(selectedOverlay)
184211
rootContainer.addSubview(pinImageView)
185212
contentView.addSubview(rootContainer)
@@ -201,6 +228,11 @@ class TopSiteCell: UICollectionViewCell, ReusableCell {
201228
selectedOverlay.trailingAnchor.constraint(equalTo: rootContainer.trailingAnchor),
202229
selectedOverlay.bottomAnchor.constraint(equalTo: rootContainer.bottomAnchor),
203230

231+
addShortcutImageView.centerXAnchor.constraint(equalTo: rootContainer.centerXAnchor),
232+
addShortcutImageView.centerYAnchor.constraint(equalTo: rootContainer.centerYAnchor),
233+
addShortcutImageView.widthAnchor.constraint(equalToConstant: UX.addShortcutIconSize.width),
234+
addShortcutImageView.heightAnchor.constraint(equalToConstant: UX.addShortcutIconSize.height),
235+
204236
pinImageView.topAnchor.constraint(equalTo: rootContainer.topAnchor),
205237
pinImageView.leadingAnchor.constraint(equalTo: rootContainer.leadingAnchor),
206238
pinImageView.widthAnchor.constraint(equalToConstant: UX.pinIconSize.width),
@@ -263,6 +295,7 @@ extension TopSiteCell: ThemeApplicable {
263295
sponsoredLabel.textColor = textColor ?? theme.colors.textPrimary
264296
selectedOverlay.backgroundColor = theme.colors.layer5Hover.withAlphaComponent(0.25)
265297
pinImageView.tintColor = theme.colors.iconSecondary
298+
addShortcutImageView.tintColor = theme.colors.iconPrimary
266299

267300
adjustBlur(theme: theme)
268301
}

firefox-ios/Client/Frontend/Home/Homepage/TopSites/TopSitesAction.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@ struct TopSitesAction: Action {
1717
let topSites: [TopSiteConfiguration]?
1818
let numberOfRows: Int?
1919
let isEnabled: Bool?
20+
let shouldShowAddShortcutTile: Bool?
2021
let telemetryConfig: TopSitesTelemetryConfig?
2122

2223
init(
2324
topSites: [TopSiteConfiguration]? = nil,
2425
numberOfRows: Int? = nil,
2526
isEnabled: Bool? = nil,
27+
shouldShowAddShortcutTile: Bool? = nil,
2628
telemetryConfig: TopSitesTelemetryConfig? = nil,
2729
windowUUID: WindowUUID,
2830
actionType: any ActionType
2931
) {
3032
self.windowUUID = windowUUID
3133
self.actionType = actionType
3234
self.isEnabled = isEnabled
35+
self.shouldShowAddShortcutTile = shouldShowAddShortcutTile
3336
self.topSites = topSites
3437
self.numberOfRows = numberOfRows
3538
self.telemetryConfig = telemetryConfig

firefox-ios/Client/Frontend/Home/Homepage/TopSites/TopSitesMiddleware.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ final class TopSitesMiddleware {
1414
private let homepageTelemetry: HomepageTelemetry
1515
private let bookmarksTelemetry: BookmarksTelemetry
1616
private let unifiedAdsTelemetry: UnifiedAdsCallbackTelemetry
17+
private let featureFlagsProvider: FeatureFlagProviding
1718
private let logger: Logger
1819
private let profile: Profile
1920

@@ -23,6 +24,7 @@ final class TopSitesMiddleware {
2324
homepageTelemetry: HomepageTelemetry = HomepageTelemetry(),
2425
bookmarksTelemetry: BookmarksTelemetry = BookmarksTelemetry(),
2526
unifiedAdsTelemetry: UnifiedAdsCallbackTelemetry = DefaultUnifiedAdsCallbackTelemetry(),
27+
featureFlagsProvider: FeatureFlagProviding = AppContainer.shared.resolve(),
2628
searchEnginesManager: SearchEnginesManager = AppContainer.shared.resolve(),
2729
logger: Logger = DefaultLogger.shared
2830
) {
@@ -37,6 +39,7 @@ final class TopSitesMiddleware {
3739
self.homepageTelemetry = homepageTelemetry
3840
self.bookmarksTelemetry = bookmarksTelemetry
3941
self.unifiedAdsTelemetry = unifiedAdsTelemetry
42+
self.featureFlagsProvider = featureFlagsProvider
4043
self.logger = logger
4144
self.profile = profile
4245
}
@@ -120,6 +123,7 @@ final class TopSitesMiddleware {
120123
store.dispatch(
121124
TopSitesAction(
122125
topSites: topSites,
126+
shouldShowAddShortcutTile: featureFlagsProvider.isEnabled(.homepageAddShortcutTile),
123127
windowUUID: windowUUID,
124128
actionType: TopSitesMiddlewareActionType.retrievedUpdatedSites
125129
)

0 commit comments

Comments
 (0)