Skip to content

Commit a460c52

Browse files
authored
Add FXIOS-14322 [Homepage Redesign] Bookmark cell UI updates (#31104)
1 parent d1f94ba commit a460c52

File tree

7 files changed

+278
-86
lines changed

7 files changed

+278
-86
lines changed

firefox-ios/Client.xcodeproj/project.pbxproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,7 +1096,7 @@
10961096
8A9D31662D13586500171502 /* MockFolderHierarchyFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D31652D13586100171502 /* MockFolderHierarchyFetcher.swift */; };
10971097
8A9D31682D135A1300171502 /* MockBookmarksSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D31672D135A1300171502 /* MockBookmarksSaver.swift */; };
10981098
8A9D316A2D135EB000171502 /* EditBookmarkViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9D31692D135EB000171502 /* EditBookmarkViewModelTests.swift */; };
1099-
8A9E04192D4D07EF0022ED90 /* BookmarksCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9E04182D4D07EA0022ED90 /* BookmarksCell.swift */; };
1099+
8A9E04192D4D07EF0022ED90 /* LegacyBookmarksCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9E04182D4D07EA0022ED90 /* LegacyBookmarksCell.swift */; };
11001100
8A9E041B2D4D08350022ED90 /* BookmarkConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9E041A2D4D08350022ED90 /* BookmarkConfiguration.swift */; };
11011101
8A9E041D2D4D09240022ED90 /* BookmarksSectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9E041C2D4D09230022ED90 /* BookmarksSectionState.swift */; };
11021102
8A9E041F2D4D0B7A0022ED90 /* BookmarksSectionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A9E041E2D4D0B7A0022ED90 /* BookmarksSectionStateTests.swift */; };
@@ -1576,6 +1576,7 @@
15761576
C78FC19E2E62448400B077DA /* ShortcutsLibraryViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C78FC19D2E62448400B077DA /* ShortcutsLibraryViewControllerTests.swift */; };
15771577
C796E22A2E3D48D3005D6A1D /* ShortcutsLibraryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C796E2292E3D48D3005D6A1D /* ShortcutsLibraryViewController.swift */; };
15781578
C79CEE5E2EDF3C4B004C4EB8 /* JumpBackInCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79CEE5D2EDF3C48004C4EB8 /* JumpBackInCell.swift */; };
1579+
C79CF6B22EE0E593004C4EB8 /* BookmarksCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C79CF6B12EE0E590004C4EB8 /* BookmarksCell.swift */; };
15791580
C7A48CF72EBA98A70098845C /* StoriesWebviewViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A48CF62EBA98A00098845C /* StoriesWebviewViewControllerTests.swift */; };
15801581
C7A492722EBE45D50098845C /* MockStoriesFeedTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A492712EBE45CD0098845C /* MockStoriesFeedTelemetry.swift */; };
15811582
C7A492742EBE78D00098845C /* DismissalNotifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7A492732EBE78CA0098845C /* DismissalNotifiable.swift */; };
@@ -9105,7 +9106,7 @@
91059106
8A9D31652D13586100171502 /* MockFolderHierarchyFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFolderHierarchyFetcher.swift; sourceTree = "<group>"; };
91069107
8A9D31672D135A1300171502 /* MockBookmarksSaver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBookmarksSaver.swift; sourceTree = "<group>"; };
91079108
8A9D31692D135EB000171502 /* EditBookmarkViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditBookmarkViewModelTests.swift; sourceTree = "<group>"; };
9108-
8A9E04182D4D07EA0022ED90 /* BookmarksCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksCell.swift; sourceTree = "<group>"; };
9109+
8A9E04182D4D07EA0022ED90 /* LegacyBookmarksCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBookmarksCell.swift; sourceTree = "<group>"; };
91099110
8A9E041A2D4D08350022ED90 /* BookmarkConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkConfiguration.swift; sourceTree = "<group>"; };
91109111
8A9E041C2D4D09230022ED90 /* BookmarksSectionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksSectionState.swift; sourceTree = "<group>"; };
91119112
8A9E041E2D4D0B7A0022ED90 /* BookmarksSectionStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksSectionStateTests.swift; sourceTree = "<group>"; };
@@ -10002,6 +10003,7 @@
1000210003
C78FC19D2E62448400B077DA /* ShortcutsLibraryViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsLibraryViewControllerTests.swift; sourceTree = "<group>"; };
1000310004
C796E2292E3D48D3005D6A1D /* ShortcutsLibraryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsLibraryViewController.swift; sourceTree = "<group>"; };
1000410005
C79CEE5D2EDF3C48004C4EB8 /* JumpBackInCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpBackInCell.swift; sourceTree = "<group>"; };
10006+
C79CF6B12EE0E590004C4EB8 /* BookmarksCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksCell.swift; sourceTree = "<group>"; };
1000510007
C7A48CF62EBA98A00098845C /* StoriesWebviewViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoriesWebviewViewControllerTests.swift; sourceTree = "<group>"; };
1000610008
C7A492712EBE45CD0098845C /* MockStoriesFeedTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStoriesFeedTelemetry.swift; sourceTree = "<group>"; };
1000710009
C7A492732EBE78CA0098845C /* DismissalNotifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissalNotifiable.swift; sourceTree = "<group>"; };
@@ -13682,7 +13684,8 @@
1368213684
8A9E04172D4D07E30022ED90 /* Bookmark */ = {
1368313685
isa = PBXGroup;
1368413686
children = (
13685-
8A9E04182D4D07EA0022ED90 /* BookmarksCell.swift */,
13687+
C79CF6B12EE0E590004C4EB8 /* BookmarksCell.swift */,
13688+
8A9E04182D4D07EA0022ED90 /* LegacyBookmarksCell.swift */,
1368613689
8A9E041A2D4D08350022ED90 /* BookmarkConfiguration.swift */,
1368713690
8A9E041C2D4D09230022ED90 /* BookmarksSectionState.swift */,
1368813691
8A7835322D50FEFC0052E328 /* BookmarksMiddleware.swift */,
@@ -18878,7 +18881,7 @@
1887818881
81E9B6E82E2AB73400CE6FF6 /* MerinoStory.swift in Sources */,
1887918882
E1442FD2294782D9003680B0 /* UIViewController+Extension.swift in Sources */,
1888018883
E653422D1C5944F90039DD9E /* BrowserPrompts.swift in Sources */,
18881-
8A9E04192D4D07EF0022ED90 /* BookmarksCell.swift in Sources */,
18884+
8A9E04192D4D07EF0022ED90 /* LegacyBookmarksCell.swift in Sources */,
1888218885
E127313C28B6AD99006F39D2 /* WallpaperSettingsViewController.swift in Sources */,
1888318886
43D4BCBA2972082400775FB5 /* CreditCardSettingsViewModel.swift in Sources */,
1888418887
9636D92A27F767EC00771F5E /* NimbusMessagingEvaluationUtility.swift in Sources */,
@@ -19035,6 +19038,7 @@
1903519038
E1A6AB4828CA833000EBEBDD /* WallpaperBaseViewController.swift in Sources */,
1903619039
8A1CBB972BE0182C008BE4D4 /* MicrosurveyPromptMiddleware.swift in Sources */,
1903719040
4331A9BB27193DF0005E8080 /* ContextualHintViewController.swift in Sources */,
19041+
C79CF6B22EE0E593004C4EB8 /* BookmarksCell.swift in Sources */,
1903819042
8A9E041D2D4D09240022ED90 /* BookmarksSectionState.swift in Sources */,
1903919043
8A008F6D2D70C51B005F4A6C /* HomepageMiddleware.swift in Sources */,
1904019044
39EF434E260A73950011E22E /* Experiments.swift in Sources */,

firefox-ios/Client/Frontend/Home/Homepage/Bookmark/BookmarksCell.swift

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,36 @@ import Common
66
import Foundation
77
import SiteImageView
88

9+
@MainActor
10+
protocol BookmarksCellProtocol: ReusableCell where Self: UIView {
11+
func configure(config: BookmarkConfiguration, theme: Theme)
12+
}
13+
914
/// A cell used in homepage's Bookmarks section.
10-
final class BookmarksCell: UICollectionViewCell, ReusableCell, ThemeApplicable, Blurrable {
15+
final class BookmarksCell: UICollectionViewCell, BookmarksCellProtocol, ThemeApplicable, Blurrable {
1116
private struct UX {
12-
static let containerSpacing: CGFloat = 16
13-
static let heroImageSize = CGSize(width: 126, height: 82)
17+
static let containerSpacing: CGFloat = 4
18+
static let heroImageSize = CGSize(width: 126, height: 68)
1419
static let generalSpacing: CGFloat = 8
20+
static let contentSpacing: CGFloat = 4
21+
static let generalCornerRadius: CGFloat = 16
22+
static let heroImageCornerRadius: CGFloat = 13
1523
}
1624

1725
// MARK: - UI Elements
1826
private var rootContainer: UIView = .build { view in
1927
view.backgroundColor = .clear
20-
view.layer.cornerRadius = HomepageUX.generalCornerRadius
28+
view.layer.cornerRadius = UX.generalCornerRadius
2129
}
2230

2331
private var heroImageView: HeroImageView = .build { _ in }
2432

33+
let titleContainer: UIView = .build()
34+
2535
let itemTitle: UILabel = .build { label in
2636
label.font = FXFontStyles.Regular.caption1.scaledFont()
2737
label.adjustsFontForContentSizeCategory = true
38+
label.numberOfLines = 2
2839
}
2940

3041
// MARK: - Inits
@@ -49,14 +60,15 @@ final class BookmarksCell: UICollectionViewCell, ReusableCell, ThemeApplicable,
4960

5061
override func layoutSubviews() {
5162
super.layoutSubviews()
52-
rootContainer.layer.shadowPath = UIBezierPath(
53-
roundedRect: rootContainer.bounds,
54-
cornerRadius: HomepageUX.generalCornerRadius).cgPath
63+
contentView.layer.shadowPath = UIBezierPath(
64+
roundedRect: contentView.bounds,
65+
cornerRadius: UX.generalCornerRadius).cgPath
5566
}
5667

5768
func configure(config: BookmarkConfiguration, theme: Theme) {
5869
let heroImageViewModel = HomepageHeroImageViewModel(
5970
urlStringRequest: config.site.url,
71+
generalCornerRadius: UX.heroImageCornerRadius,
6072
heroImageSize: UX.heroImageSize
6173
)
6274
heroImageView.setHeroImage(heroImageViewModel)
@@ -69,7 +81,8 @@ final class BookmarksCell: UICollectionViewCell, ReusableCell, ThemeApplicable,
6981

7082
private func setupLayout() {
7183
contentView.backgroundColor = .clear
72-
rootContainer.addSubviews(heroImageView, itemTitle)
84+
titleContainer.addSubview(itemTitle)
85+
rootContainer.addSubviews(heroImageView, titleContainer)
7386
contentView.addSubview(rootContainer)
7487

7588
NSLayoutConstraint.activate([
@@ -78,32 +91,32 @@ final class BookmarksCell: UICollectionViewCell, ReusableCell, ThemeApplicable,
7891
rootContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
7992
rootContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
8093

81-
heroImageView.topAnchor.constraint(equalTo: rootContainer.topAnchor,
82-
constant: UX.containerSpacing),
83-
heroImageView.leadingAnchor.constraint(equalTo: rootContainer.leadingAnchor,
84-
constant: UX.containerSpacing),
85-
heroImageView.trailingAnchor.constraint(equalTo: rootContainer.trailingAnchor,
86-
constant: -UX.containerSpacing),
94+
heroImageView.topAnchor.constraint(equalTo: rootContainer.topAnchor, constant: UX.containerSpacing),
95+
heroImageView.leadingAnchor.constraint(equalTo: rootContainer.leadingAnchor, constant: UX.containerSpacing),
96+
heroImageView.trailingAnchor.constraint(equalTo: rootContainer.trailingAnchor, constant: -UX.containerSpacing),
8797
heroImageView.heightAnchor.constraint(equalToConstant: UX.heroImageSize.height),
8898
heroImageView.widthAnchor.constraint(equalToConstant: UX.heroImageSize.width),
8999

90-
itemTitle.topAnchor.constraint(equalTo: heroImageView.bottomAnchor,
91-
constant: UX.generalSpacing),
92-
itemTitle.leadingAnchor.constraint(equalTo: heroImageView.leadingAnchor),
93-
itemTitle.trailingAnchor.constraint(equalTo: heroImageView.trailingAnchor),
94-
itemTitle.bottomAnchor.constraint(equalTo: rootContainer.bottomAnchor,
95-
constant: -UX.generalSpacing),
100+
titleContainer.topAnchor.constraint(equalTo: heroImageView.bottomAnchor, constant: UX.contentSpacing),
101+
titleContainer.leadingAnchor.constraint(equalTo: rootContainer.leadingAnchor, constant: UX.generalSpacing),
102+
titleContainer.trailingAnchor.constraint(equalTo: rootContainer.trailingAnchor, constant: -UX.generalSpacing),
103+
titleContainer.bottomAnchor.constraint(equalTo: rootContainer.bottomAnchor, constant: -UX.generalSpacing),
104+
105+
itemTitle.topAnchor.constraint(equalTo: titleContainer.topAnchor),
106+
itemTitle.leadingAnchor.constraint(equalTo: titleContainer.leadingAnchor),
107+
itemTitle.trailingAnchor.constraint(equalTo: titleContainer.trailingAnchor),
108+
itemTitle.bottomAnchor.constraint(lessThanOrEqualTo: titleContainer.bottomAnchor)
96109
])
97110
}
98111

99112
private func setupShadow(theme: Theme) {
100-
rootContainer.layer.shadowPath = UIBezierPath(roundedRect: rootContainer.bounds,
101-
cornerRadius: HomepageUX.generalCornerRadius).cgPath
113+
contentView.layer.shadowPath = UIBezierPath(roundedRect: contentView.bounds,
114+
cornerRadius: UX.generalCornerRadius).cgPath
102115

103-
rootContainer.layer.shadowColor = theme.colors.shadowDefault.cgColor
104-
rootContainer.layer.shadowOpacity = HomepageUX.shadowOpacity
105-
rootContainer.layer.shadowOffset = HomepageUX.shadowOffset
106-
rootContainer.layer.shadowRadius = HomepageUX.shadowRadius
116+
contentView.layer.shadowColor = theme.colors.shadowDefault.cgColor
117+
contentView.layer.shadowOpacity = HomepageUX.shadowOpacity
118+
contentView.layer.shadowOffset = HomepageUX.shadowOffset
119+
contentView.layer.shadowRadius = HomepageUX.shadowRadius
107120
}
108121

109122
// MARK: - ThemeApplicable
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/
4+
5+
import Common
6+
import Foundation
7+
import SiteImageView
8+
9+
/// A cell used in homepage's Bookmarks section.
10+
final class LegacyBookmarksCell: UICollectionViewCell, BookmarksCellProtocol, ThemeApplicable, Blurrable {
11+
private struct UX {
12+
static let containerSpacing: CGFloat = 16
13+
static let heroImageSize = CGSize(width: 126, height: 82)
14+
static let generalSpacing: CGFloat = 8
15+
}
16+
17+
// MARK: - UI Elements
18+
private var rootContainer: UIView = .build { view in
19+
view.backgroundColor = .clear
20+
view.layer.cornerRadius = HomepageUX.generalCornerRadius
21+
}
22+
23+
private var heroImageView: HeroImageView = .build { _ in }
24+
25+
let itemTitle: UILabel = .build { label in
26+
label.font = FXFontStyles.Regular.caption1.scaledFont()
27+
label.adjustsFontForContentSizeCategory = true
28+
}
29+
30+
// MARK: - Inits
31+
32+
override init(frame: CGRect) {
33+
super.init(frame: .zero)
34+
35+
isAccessibilityElement = true
36+
accessibilityIdentifier = AccessibilityIdentifiers.FirefoxHomepage.Bookmarks.itemCell
37+
38+
setupLayout()
39+
}
40+
41+
required init?(coder: NSCoder) {
42+
fatalError("init(coder:) has not been implemented")
43+
}
44+
45+
override func prepareForReuse() {
46+
super.prepareForReuse()
47+
itemTitle.text = nil
48+
}
49+
50+
override func layoutSubviews() {
51+
super.layoutSubviews()
52+
rootContainer.layer.shadowPath = UIBezierPath(
53+
roundedRect: rootContainer.bounds,
54+
cornerRadius: HomepageUX.generalCornerRadius).cgPath
55+
}
56+
57+
func configure(config: BookmarkConfiguration, theme: Theme) {
58+
let heroImageViewModel = HomepageHeroImageViewModel(
59+
urlStringRequest: config.site.url,
60+
heroImageSize: UX.heroImageSize
61+
)
62+
heroImageView.setHeroImage(heroImageViewModel)
63+
itemTitle.text = config.site.title
64+
accessibilityLabel = config.accessibilityLabel
65+
applyTheme(theme: theme)
66+
}
67+
68+
// MARK: - Helpers
69+
70+
private func setupLayout() {
71+
contentView.backgroundColor = .clear
72+
rootContainer.addSubviews(heroImageView, itemTitle)
73+
contentView.addSubview(rootContainer)
74+
75+
NSLayoutConstraint.activate([
76+
rootContainer.topAnchor.constraint(equalTo: contentView.topAnchor),
77+
rootContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
78+
rootContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
79+
rootContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
80+
81+
heroImageView.topAnchor.constraint(equalTo: rootContainer.topAnchor,
82+
constant: UX.containerSpacing),
83+
heroImageView.leadingAnchor.constraint(equalTo: rootContainer.leadingAnchor,
84+
constant: UX.containerSpacing),
85+
heroImageView.trailingAnchor.constraint(equalTo: rootContainer.trailingAnchor,
86+
constant: -UX.containerSpacing),
87+
heroImageView.heightAnchor.constraint(equalToConstant: UX.heroImageSize.height),
88+
heroImageView.widthAnchor.constraint(equalToConstant: UX.heroImageSize.width),
89+
90+
itemTitle.topAnchor.constraint(equalTo: heroImageView.bottomAnchor,
91+
constant: UX.generalSpacing),
92+
itemTitle.leadingAnchor.constraint(equalTo: heroImageView.leadingAnchor),
93+
itemTitle.trailingAnchor.constraint(equalTo: heroImageView.trailingAnchor),
94+
itemTitle.bottomAnchor.constraint(equalTo: rootContainer.bottomAnchor,
95+
constant: -UX.generalSpacing),
96+
])
97+
}
98+
99+
private func setupShadow(theme: Theme) {
100+
rootContainer.layer.shadowPath = UIBezierPath(roundedRect: rootContainer.bounds,
101+
cornerRadius: HomepageUX.generalCornerRadius).cgPath
102+
103+
rootContainer.layer.shadowColor = theme.colors.shadowDefault.cgColor
104+
rootContainer.layer.shadowOpacity = HomepageUX.shadowOpacity
105+
rootContainer.layer.shadowOffset = HomepageUX.shadowOffset
106+
rootContainer.layer.shadowRadius = HomepageUX.shadowRadius
107+
}
108+
109+
// MARK: - ThemeApplicable
110+
func applyTheme(theme: Theme) {
111+
itemTitle.textColor = theme.colors.textPrimary
112+
let heroImageColors = HeroImageViewColor(faviconTintColor: theme.colors.iconPrimary,
113+
faviconBackgroundColor: theme.colors.layer1,
114+
faviconBorderColor: theme.colors.layer1)
115+
heroImageView.updateHeroImageTheme(with: heroImageColors)
116+
117+
adjustBlur(theme: theme)
118+
}
119+
120+
// MARK: - Blurrable
121+
func adjustBlur(theme: Theme) {
122+
// If blur is disabled set background color
123+
if shouldApplyWallpaperBlur {
124+
rootContainer.layoutIfNeeded()
125+
rootContainer.addBlurEffectWithClearBackgroundAndClipping(using: .systemThickMaterial)
126+
} else {
127+
rootContainer.removeVisualEffectView()
128+
rootContainer.backgroundColor = theme.colors.layer5
129+
setupShadow(theme: theme)
130+
}
131+
}
132+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ final class HomepageDiffableDataSource:
5656
LegacyJumpBackInCell.self,
5757
JumpBackInCell.self,
5858
SyncedTabCell.self,
59+
LegacyBookmarksCell.self,
5960
BookmarksCell.self,
6061
MerinoStandardCell.self,
6162
StoryCell.self,

0 commit comments

Comments
 (0)