From 842005a2192f3344dc0bf7fd104d384a2e329a50 Mon Sep 17 00:00:00 2001 From: Alexandru Farcasanu Date: Thu, 5 Jun 2025 19:46:26 +0300 Subject: [PATCH 1/4] =?UTF-8?q?FXIOS-12363=20#26946=20=E2=81=83=20[Menu=20?= =?UTF-8?q?Redesign]=20Update=20horizontal=20squares=20options=20to=20the?= =?UTF-8?q?=20latest=20design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Constants/StandardImageIdentifiers.swift | 1 + .../MenuRedesign/MenuCollectionView.swift | 44 +++++----- .../MenuRedesign/MenuRedesignMainView.swift | 17 +--- .../MenuRedesign/MenuRedesignTableView.swift | 44 +++++++++- .../MenuKit/MenuRedesign/MenuSquareCell.swift | 80 +++++++++++------- BrowserKit/Sources/MenuKit/MenuSection.swift | 6 +- .../bookmarkTrayLarge.imageset/Contents.json | 15 ++++ .../bookmarkTrayLarge.pdf | Bin 0 -> 3528 bytes .../MainMenuConfigurationUtility.swift | 17 ++-- 9 files changed, 144 insertions(+), 80 deletions(-) create mode 100644 firefox-ios/Client/Assets/Images.xcassets/bookmarkTrayLarge.imageset/Contents.json create mode 100644 firefox-ios/Client/Assets/Images.xcassets/bookmarkTrayLarge.imageset/bookmarkTrayLarge.pdf diff --git a/BrowserKit/Sources/Common/Constants/StandardImageIdentifiers.swift b/BrowserKit/Sources/Common/Constants/StandardImageIdentifiers.swift index 05af890786a10..d5844ef8d0a38 100644 --- a/BrowserKit/Sources/Common/Constants/StandardImageIdentifiers.swift +++ b/BrowserKit/Sources/Common/Constants/StandardImageIdentifiers.swift @@ -54,6 +54,7 @@ public struct StandardImageIdentifiers { public static let bookmarkFill = "bookmarkFillLarge" public static let bookmarkHalf = "bookmarkHalfLarge" public static let bookmarkSlash = "bookmarkSlashLarge" + public static let bookmarkTray = "bookmarkTrayLarge" public static let bookmarkTrayFill = "bookmarkTrayFillLarge" public static let checkmark = "checkmarkLarge" public static let chevronDown = "chevronDownLarge" diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionView.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionView.swift index d3e57b50fd1f0..0ec2a6f21ea67 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionView.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionView.swift @@ -5,40 +5,31 @@ import UIKit import Common -final class MenuCollectionView: UIView, +final class MenuCollectionView: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, + UICollectionViewDelegateFlowLayout, + ReusableCell, ThemeApplicable { private struct UX { - static let collectionViewHorizontalMargin: CGFloat = 10 - static let collectionViewVerticalMargin: CGFloat = 12 - static let cellHeight: CGFloat = 79 - static let cellWidth: CGFloat = 72 - } - - private var height: CGFloat { - UX.cellHeight + UX.collectionViewVerticalMargin + UX.collectionViewHorizontalMargin + static let minimumLineSpacing: CGFloat = 16 } private var collectionView: UICollectionView private var menuData: [MenuSection] private var theme: Theme? - override init(frame: CGRect) { + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal - layout.itemSize = CGSize(width: UX.cellWidth, height: UX.cellHeight) - layout.sectionInset = UIEdgeInsets( - top: UX.collectionViewVerticalMargin, - left: UX.collectionViewHorizontalMargin, - bottom: UX.collectionViewVerticalMargin, - right: UX.collectionViewVerticalMargin) + layout.minimumInteritemSpacing = 0 + layout.minimumLineSpacing = UX.minimumLineSpacing collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.showsHorizontalScrollIndicator = false menuData = [] - super.init(frame: .zero) + super.init(style: style, reuseIdentifier: reuseIdentifier) setupView() } @@ -59,8 +50,7 @@ final class MenuCollectionView: UIView, collectionView.topAnchor.constraint(equalTo: self.topAnchor), collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor), collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor), - collectionView.heightAnchor.constraint(equalToConstant: height) + collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor) ]) } @@ -83,7 +73,7 @@ final class MenuCollectionView: UIView, _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int { - guard let section = menuData.first(where: { $0.isTopTabsSection }) else { return 0 } + guard let section = menuData.first(where: { $0.isHorizontalTabsSection }) else { return 0 } return section.options.count } @@ -98,7 +88,7 @@ final class MenuCollectionView: UIView, return UICollectionViewCell() } - guard let section = menuData.first(where: { $0.isTopTabsSection }) else { return UICollectionViewCell() } + guard let section = menuData.first(where: { $0.isHorizontalTabsSection }) else { return UICollectionViewCell() } cell.configureCellWith(model: section.options[indexPath.row]) if let theme { cell.applyTheme(theme: theme) } return cell @@ -106,11 +96,21 @@ final class MenuCollectionView: UIView, func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: false) - guard let section = menuData.first(where: { $0.isTopTabsSection }), + guard let section = menuData.first(where: { $0.isHorizontalTabsSection }), let action = section.options[indexPath.row].action else { return } action() } + func collectionView(_ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + guard let section = menuData.first(where: { $0.isHorizontalTabsSection }) else { return .zero } + let itemCount = section.options.count + let width = (collectionView.bounds.width - CGFloat(itemCount - 1) * UX.minimumLineSpacing) / CGFloat(itemCount) + let height = collectionView.bounds.height + return CGSize(width: width, height: height) + } + func reloadCollectionView(with data: [MenuSection]) { menuData = data collectionView.reloadData() diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignMainView.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignMainView.swift index 8f44f60c17e98..e8857a5060ef5 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignMainView.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignMainView.swift @@ -7,13 +7,12 @@ import UIKit import ComponentLibrary public final class MenuRedesignMainView: UIView, - ThemeApplicable { + ThemeApplicable { private struct UX { static let headerTopMargin: CGFloat = 15 } // MARK: - UI Elements - private var collectionView: MenuCollectionView = .build() private var tableView: MenuRedesignTableView = .build() // MARK: - Initializers @@ -28,36 +27,28 @@ public final class MenuRedesignMainView: UIView, // MARK: - UI Setup private func setupView() { - self.addSubview(collectionView) self.addSubview(tableView) - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: self.topAnchor), - collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor), - - tableView.topAnchor.constraint(equalTo: collectionView.bottomAnchor), + tableView.topAnchor.constraint(equalTo: self.topAnchor, constant: UX.headerTopMargin), tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor), tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor) ]) } - public func setupAccessibilityIdentifiers(menuA11yId: String, menuA11yLabel: String) { - collectionView.setupAccessibilityIdentifiers(menuA11yId: menuA11yId, menuA11yLabel: menuA11yLabel) + public func setupAccessibilityIdentifiers(menuA11yId: String, + menuA11yLabel: String) { tableView.setupAccessibilityIdentifiers(menuA11yId: menuA11yId, menuA11yLabel: menuA11yLabel) } // MARK: - Interface public func reloadDataView(with data: [MenuSection]) { - collectionView.reloadCollectionView(with: data) tableView.reloadTableView(with: data) } // MARK: - ThemeApplicable public func applyTheme(theme: Theme) { backgroundColor = .clear - collectionView.applyTheme(theme: theme) tableView.applyTheme(theme: theme) } } diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift index 8a52586120bfc..7d364e3249394 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift @@ -13,7 +13,13 @@ final class MenuRedesignTableView: UIView, private struct UX { static let topPadding: CGFloat = 12 static let tableViewMargin: CGFloat = 16 - static let distanceBetweenSections: CGFloat = 32 + static let distanceBetweenSections: CGFloat = 16 + + // Menu Redesign horizontal squares cells sizes + static let iconSize: CGFloat = 24 + static let contentViewSpacing: CGFloat = 4 + static let contentViewTopMargin: CGFloat = 12 + static let contentViewBottomMargin: CGFloat = 8 } private var tableView: UITableView @@ -58,6 +64,7 @@ final class MenuRedesignTableView: UIView, tableView.register(MenuRedesignCell.self, forCellReuseIdentifier: MenuRedesignCell.cellIdentifier) tableView.register(MenuInfoCell.self, forCellReuseIdentifier: MenuInfoCell.cellIdentifier) tableView.register(MenuAccountCell.self, forCellReuseIdentifier: MenuAccountCell.cellIdentifier) + tableView.register(MenuCollectionView.self, forCellReuseIdentifier: MenuCollectionView.cellIdentifier) } func setupAccessibilityIdentifiers(menuA11yId: String, menuA11yLabel: String) { @@ -77,11 +84,29 @@ final class MenuRedesignTableView: UIView, return section == 0 ? UX.topPadding : UX.distanceBetweenSections } + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + if menuData[indexPath.section].isHorizontalTabsSection { + let label = UILabel() + label.font = FXFontStyles.Regular.caption1.scaledFont() + label.text = menuData[indexPath.section].options.first?.title + label.sizeToFit() + let textHeight = label.frame.height + let iconHeight: CGFloat = UX.iconSize + let spacing: CGFloat = UX.contentViewSpacing + let topMargin: CGFloat = UX.contentViewTopMargin + let bottomMargin: CGFloat = UX.contentViewBottomMargin + return topMargin + iconHeight + spacing + textHeight + bottomMargin + } + return UITableView.automaticDimension + } + func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int { - if let isExpanded = menuData[section].isExpanded, isExpanded { + if menuData[section].isHorizontalTabsSection { + return 1 + } else if let isExpanded = menuData[section].isExpanded, isExpanded { return menuData[section].options.count } else { return menuData[section].options.count(where: { !$0.isOptional }) @@ -92,6 +117,17 @@ final class MenuRedesignTableView: UIView, _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { + if menuData[indexPath.section].isHorizontalTabsSection { + guard let cell = tableView.dequeueReusableCell( + withIdentifier: MenuCollectionView.cellIdentifier, + for: indexPath) as? MenuCollectionView else { + return UITableViewCell() + } + cell.reloadCollectionView(with: menuData) + if let theme { cell.applyTheme(theme: theme) } + return cell + } + let rowOption = menuData[indexPath.section].options[indexPath.row] if rowOption.iconImage != nil || rowOption.needsReAuth != nil { @@ -160,10 +196,10 @@ final class MenuRedesignTableView: UIView, func reloadTableView(with data: [MenuSection]) { // We ignore first section because it is handled in MenuCollectionView - if let firstSection = data.first, firstSection.isTopTabsSection { + if let firstSection = data.first, firstSection.isHorizontalTabsSection { tableView.showsVerticalScrollIndicator = false menuData = data - menuData.remove(at: 0) + menuData.removeAll(where: { $0.isHorizontalTabsSection }) } else { menuData = data } diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift index b78be80dea632..cf13fe7f2e979 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift @@ -8,16 +8,27 @@ import UIKit // TODO: FXIOS-12302 Create the UI for different accessibility sizes, for horizontal options section final class MenuSquareCell: UICollectionViewCell, ReusableCell, ThemeApplicable { private struct UX { - static let iconSize: CGFloat = 20 - static let backgroundIconViewCornerRadius: CGFloat = 12 + static let iconSize: CGFloat = 24 + static let backgroundViewCornerRadius: CGFloat = 12 static let horizontalMargin: CGFloat = 6 - static let verticalMargin: CGFloat = 6 + static let contentViewSpacing: CGFloat = 4 + static let contentViewTopMargin: CGFloat = 12 + static let contentViewBottomMargin: CGFloat = 8 + static let contentViewHorizontalMargin: CGFloat = 4 } // MARK: - UI Elements - private var backgroundIconView: UIView = .build() + private var backgroundContentView: UIView = .build() - private var icon: UIImageView = .build() + private var contentStackView: UIStackView = .build { stack in + stack.axis = .vertical + stack.spacing = UX.contentViewSpacing + stack.distribution = .fillProportionally + } + + private var icon: UIImageView = .build { view in + view.contentMode = .scaleAspectFit + } private var titleLabel: UILabel = .build { label in label.font = FXFontStyles.Regular.caption2.scaledFont() @@ -34,13 +45,15 @@ final class MenuSquareCell: UICollectionViewCell, ReusableCell, ThemeApplicable setupView() } - required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) not yet supported") } + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) not yet supported") + } func configureCellWith(model: MenuElement) { self.model = model self.titleLabel.text = model.title self.icon.image = UIImage(named: model.iconName)?.withRenderingMode(.alwaysTemplate) - self.backgroundIconView.layer.cornerRadius = UX.backgroundIconViewCornerRadius + self.backgroundContentView.layer.cornerRadius = UX.backgroundViewCornerRadius self.isAccessibilityElement = true self.isUserInteractionEnabled = !model.isEnabled ? false : true self.accessibilityIdentifier = model.a11yId @@ -50,39 +63,42 @@ final class MenuSquareCell: UICollectionViewCell, ReusableCell, ThemeApplicable } private func setupView() { - self.addSubview(backgroundIconView) - self.addSubview(titleLabel) - self.backgroundIconView.addSubview(icon) + self.addSubview(backgroundContentView) + backgroundContentView.addSubview(contentStackView) + contentStackView.addArrangedSubview(icon) + contentStackView.addArrangedSubview(titleLabel) NSLayoutConstraint.activate([ - backgroundIconView.topAnchor.constraint(equalTo: self.topAnchor), - backgroundIconView.leadingAnchor.constraint( - equalTo: self.leadingAnchor, - constant: UX.horizontalMargin), - backgroundIconView.trailingAnchor.constraint( - equalTo: self.trailingAnchor, - constant: -UX.horizontalMargin), - - icon.centerYAnchor.constraint(equalTo: backgroundIconView.centerYAnchor), - icon.centerXAnchor.constraint(equalTo: backgroundIconView.centerXAnchor), - icon.widthAnchor.constraint(equalToConstant: UX.iconSize), - icon.heightAnchor.constraint(equalToConstant: UX.iconSize), + backgroundContentView.topAnchor.constraint(equalTo: self.topAnchor), + backgroundContentView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + backgroundContentView.trailingAnchor.constraint(equalTo: self.trailingAnchor), + backgroundContentView.bottomAnchor.constraint(equalTo: self.bottomAnchor), - titleLabel.topAnchor.constraint(equalTo: backgroundIconView.bottomAnchor, constant: UX.verticalMargin), - titleLabel.leadingAnchor.constraint( - equalTo: self.leadingAnchor, - constant: UX.horizontalMargin), - titleLabel.trailingAnchor.constraint( - equalTo: self.trailingAnchor, - constant: -UX.horizontalMargin), - titleLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor) + contentStackView.topAnchor.constraint( + equalTo: backgroundContentView.topAnchor, + constant: UX.contentViewTopMargin + ), + contentStackView.leadingAnchor.constraint( + equalTo: backgroundContentView.leadingAnchor, + constant: UX.contentViewHorizontalMargin + ), + contentStackView.trailingAnchor.constraint( + equalTo: backgroundContentView.trailingAnchor, + constant: -UX.contentViewHorizontalMargin + ), + contentStackView.bottomAnchor.constraint( + equalTo: backgroundContentView.bottomAnchor, + constant: -UX.contentViewBottomMargin + ), + icon.heightAnchor.constraint(equalToConstant: UX.iconSize) ]) } // MARK: - Theme Applicable func applyTheme(theme: Theme) { backgroundColor = .clear - backgroundIconView.backgroundColor = theme.colors.layer2 + contentStackView.backgroundColor = .clear + backgroundContentView.backgroundColor = theme.colors.layer2 icon.tintColor = theme.colors.iconPrimary - titleLabel.textColor = theme.colors.textPrimary + titleLabel.textColor = theme.colors.textSecondary } } diff --git a/BrowserKit/Sources/MenuKit/MenuSection.swift b/BrowserKit/Sources/MenuKit/MenuSection.swift index d79b902e9e945..f36702ac7eef2 100644 --- a/BrowserKit/Sources/MenuKit/MenuSection.swift +++ b/BrowserKit/Sources/MenuKit/MenuSection.swift @@ -5,12 +5,12 @@ import Foundation public struct MenuSection: Equatable { - public let isTopTabsSection: Bool + public let isHorizontalTabsSection: Bool public let isExpanded: Bool? public let options: [MenuElement] - public init(isTopTabsSection: Bool = false, isExpanded: Bool? = false, options: [MenuElement]) { - self.isTopTabsSection = isTopTabsSection + public init(isHorizontalTabsSection: Bool = false, isExpanded: Bool? = false, options: [MenuElement]) { + self.isHorizontalTabsSection = isHorizontalTabsSection self.isExpanded = isExpanded self.options = options } diff --git a/firefox-ios/Client/Assets/Images.xcassets/bookmarkTrayLarge.imageset/Contents.json b/firefox-ios/Client/Assets/Images.xcassets/bookmarkTrayLarge.imageset/Contents.json new file mode 100644 index 0000000000000..401b9c1609d8b --- /dev/null +++ b/firefox-ios/Client/Assets/Images.xcassets/bookmarkTrayLarge.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "bookmarkTrayLarge.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/firefox-ios/Client/Assets/Images.xcassets/bookmarkTrayLarge.imageset/bookmarkTrayLarge.pdf b/firefox-ios/Client/Assets/Images.xcassets/bookmarkTrayLarge.imageset/bookmarkTrayLarge.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c5c9c1a4a9e12e3129760a6b8fa14ec6b89c72e7 GIT binary patch literal 3528 zcmb7{O>bLO5Qg{nSDa0y7Ae=~`$MWK(Uc+th>)_YSQzKF4RUOVT~zq>JTu3>cG@f; zi^_dH-}BC#@sq1puP@wKhQS$Yw!i&881wwOdGTVHHaGI$!Jp%cAEwRw{Ra~OuSIIN zzn_+O!{XKS*X?S${`RH0d~^Q4)o%E6aHG{fV~-y$=1=o?Vf0C)Od4A*DS5NH8)mUD zKkQbU!Pzl|*laT{j@3ICnzYd_ z$Jen1XV!xsOLoae0ZY!APix7^SLeKdImJ{n*#+E|YT6j9w+$hSU7OV+l^aS73YbAE%aEke4O&oLe$i! zOVNX26Pgg4^o60?VWXLfGiY0@~x2sR2rvVnjxzhRPCB z+DCi^AYzO)iakH3NL*g#w!tVZDIgO{p#fuVj&_S&PS6d=Wz7~SROTN@vi9jIW`U?M~)3b#egDN>b8KA*zKy3br2(R1rYN*?jHznrUJ1==uGQH<5m#(&I05Ncb1>7=I~i0+Wa)Ao8+*A}CM&T_(Ci31 zwlZ`+=A&L|*h<)gpR~42%Cshk!dL?vA|gS+D!T@ zLk1GFS3T?rl|-iT1fU03S{5#|eN$-g;y^0d`#hyknbObPlLwB+shkL!s3;5wCmyfl z#Vd(~I^o&r*aBk(HXVY*6Q=C&@T%$X)Lw4p!$(>6)!N1+sx4Av?!kO%O`0O~X2r?8 zGR#_9oUjOVtR2NxJ?5MAb^3W0MUPS9eF34nv|Qq3qI5&BNwn@@lu|ai+zf0SrMt(q zu0Sh`NgKrjC$CZ8VAN zKmYF&h%2SFhL8Vp1Ie0_yXiuPxoh2Vo*Yq%)xq`Yn!0!tJn5f{^48bV^zmUjc(xs$ zAYeBtE9KyQ##m+y#1}B@G3(0U&^WWeg)o~qxjF;qtgw56&oaQ#VF`32H^8IuGew=P@moK-U%-hoqKA+T{ zleBm1^yz5ndfIJ2-mj*ek>3Z`2XmL(&3@Y8QGOg8!K>vxm`Mld>?edk^G}P#_qR8M zY9xbebM$k${fMsnBK{r7nI(?wGfR@-ZgzM|{BpTpuD9LPJW`@`m4*;=w?tS-)ay;E_@1|$w q>Fv$4UX;c4cDpx{uoign=Jub7==C?(@_w)BaP;Tl$&+t>di^i@ddl+v literal 0 HcmV?d00001 diff --git a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift index 19348185cc16b..a32bd6af66e9c 100644 --- a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift +++ b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift @@ -17,6 +17,7 @@ struct MainMenuConfigurationUtility: Equatable, FeatureFlaggable { static let tools = StandardImageIdentifiers.Large.tool static let save = StandardImageIdentifiers.Large.save static let bookmarks = StandardImageIdentifiers.Large.bookmarkTrayFill + static let bookmarksTray = StandardImageIdentifiers.Large.bookmarkTray static let history = StandardImageIdentifiers.Large.history static let downloads = StandardImageIdentifiers.Large.download static let passwords = StandardImageIdentifiers.Large.login @@ -111,8 +112,12 @@ struct MainMenuConfigurationUtility: Equatable, FeatureFlaggable { ) } } else { - menuSections.insert(getTopTabsSection(with: uuid, tabInfo: tabInfo), at: 0) - menuSections.append(getSiteSection(with: uuid, tabInfo: tabInfo, isExpanded: isExpanded)) + if tabInfo.isHomepage { + menuSections.append(getNewTabSection(with: uuid, tabInfo: tabInfo)) + menuSections.append(getHorizontalTabsSection(with: uuid, tabInfo: tabInfo)) + } else { + menuSections.append(getSiteSection(with: uuid, tabInfo: tabInfo, isExpanded: isExpanded)) + } } return menuSections @@ -252,10 +257,10 @@ struct MainMenuConfigurationUtility: Equatable, FeatureFlaggable { } // TODO: FXIOS-12306 Update MainMenuConfigurationUtility based on the final design - // MARK: - Top Tabs Section - private func getTopTabsSection(with uuid: WindowUUID, tabInfo: MainMenuTabInfo) -> MenuSection { + // MARK: - Horizontal Tabs Section + private func getHorizontalTabsSection(with uuid: WindowUUID, tabInfo: MainMenuTabInfo) -> MenuSection { return MenuSection( - isTopTabsSection: true, + isHorizontalTabsSection: true, options: [ MenuElement( title: .MainMenu.PanelLinkSection.History, @@ -278,7 +283,7 @@ struct MainMenuConfigurationUtility: Equatable, FeatureFlaggable { ), MenuElement( title: .MainMenu.PanelLinkSection.Bookmarks, - iconName: Icons.bookmarks, + iconName: Icons.bookmarksTray, isEnabled: true, isActive: false, a11yLabel: .MainMenu.PanelLinkSection.AccessibilityLabels.Bookmarks, From 8dc91ecfdd2cf6151f2afa3af55fcdd61d6ff4a8 Mon Sep 17 00:00:00 2001 From: Alexandru Farcasanu Date: Tue, 10 Jun 2025 09:49:46 +0300 Subject: [PATCH 2/4] Did a quick refactor for the code --- ... => MenuCollectionViewContainerCell.swift} | 22 +++++++++++-------- .../MenuRedesign/MenuRedesignTableView.swift | 8 +++---- .../MainMenuConfigurationUtility.swift | 10 ++++----- 3 files changed, 21 insertions(+), 19 deletions(-) rename BrowserKit/Sources/MenuKit/MenuRedesign/{MenuCollectionView.swift => MenuCollectionViewContainerCell.swift} (84%) diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionView.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionViewContainerCell.swift similarity index 84% rename from BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionView.swift rename to BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionViewContainerCell.swift index 0ec2a6f21ea67..b08319c691d35 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionView.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionViewContainerCell.swift @@ -5,7 +5,7 @@ import UIKit import Common -final class MenuCollectionView: UITableViewCell, +final class MenuCollectionViewContainerCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, @@ -19,6 +19,10 @@ final class MenuCollectionView: UITableViewCell, private var menuData: [MenuSection] private var theme: Theme? + private var horizontalTabsSection: MenuSection? { + return menuData.first(where: { $0.isHorizontalTabsSection }) + } + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal @@ -73,8 +77,8 @@ final class MenuCollectionView: UITableViewCell, _ collectionView: UICollectionView, numberOfItemsInSection section: Int ) -> Int { - guard let section = menuData.first(where: { $0.isHorizontalTabsSection }) else { return 0 } - return section.options.count + guard let horizontalTabsSection else { return 0 } + return horizontalTabsSection.options.count } func collectionView( @@ -88,24 +92,24 @@ final class MenuCollectionView: UITableViewCell, return UICollectionViewCell() } - guard let section = menuData.first(where: { $0.isHorizontalTabsSection }) else { return UICollectionViewCell() } - cell.configureCellWith(model: section.options[indexPath.row]) + guard let horizontalTabsSection else { return UICollectionViewCell() } + cell.configureCellWith(model: horizontalTabsSection.options[indexPath.row]) if let theme { cell.applyTheme(theme: theme) } return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { collectionView.deselectItem(at: indexPath, animated: false) - guard let section = menuData.first(where: { $0.isHorizontalTabsSection }), - let action = section.options[indexPath.row].action else { return } + guard let horizontalTabsSection, + let action = horizontalTabsSection.options[indexPath.row].action else { return } action() } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - guard let section = menuData.first(where: { $0.isHorizontalTabsSection }) else { return .zero } - let itemCount = section.options.count + guard let horizontalTabsSection else { return .zero } + let itemCount = horizontalTabsSection.options.count let width = (collectionView.bounds.width - CGFloat(itemCount - 1) * UX.minimumLineSpacing) / CGFloat(itemCount) let height = collectionView.bounds.height return CGSize(width: width, height: height) diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift index 7d364e3249394..736748fc721f0 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift @@ -64,7 +64,7 @@ final class MenuRedesignTableView: UIView, tableView.register(MenuRedesignCell.self, forCellReuseIdentifier: MenuRedesignCell.cellIdentifier) tableView.register(MenuInfoCell.self, forCellReuseIdentifier: MenuInfoCell.cellIdentifier) tableView.register(MenuAccountCell.self, forCellReuseIdentifier: MenuAccountCell.cellIdentifier) - tableView.register(MenuCollectionView.self, forCellReuseIdentifier: MenuCollectionView.cellIdentifier) + tableView.register(MenuCollectionViewContainerCell.self, forCellReuseIdentifier: MenuCollectionViewContainerCell.cellIdentifier) } func setupAccessibilityIdentifiers(menuA11yId: String, menuA11yLabel: String) { @@ -119,8 +119,8 @@ final class MenuRedesignTableView: UIView, ) -> UITableViewCell { if menuData[indexPath.section].isHorizontalTabsSection { guard let cell = tableView.dequeueReusableCell( - withIdentifier: MenuCollectionView.cellIdentifier, - for: indexPath) as? MenuCollectionView else { + withIdentifier: MenuCollectionViewContainerCell.cellIdentifier, + for: indexPath) as? MenuCollectionViewContainerCell else { return UITableViewCell() } cell.reloadCollectionView(with: menuData) @@ -195,7 +195,7 @@ final class MenuRedesignTableView: UIView, } func reloadTableView(with data: [MenuSection]) { - // We ignore first section because it is handled in MenuCollectionView + // We ignore first section because it is handled in MenuCollectionViewContainerCell if let firstSection = data.first, firstSection.isHorizontalTabsSection { tableView.showsVerticalScrollIndicator = false menuData = data diff --git a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift index a32bd6af66e9c..7de637ced6298 100644 --- a/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift +++ b/firefox-ios/Client/Frontend/Browser/MainMenu/MainMenuConfigurationUtility.swift @@ -111,13 +111,11 @@ struct MainMenuConfigurationUtility: Equatable, FeatureFlaggable { at: 1 ) } + } else if tabInfo.isHomepage { + menuSections.append(getNewTabSection(with: uuid, tabInfo: tabInfo)) + menuSections.append(getHorizontalTabsSection(with: uuid, tabInfo: tabInfo)) } else { - if tabInfo.isHomepage { - menuSections.append(getNewTabSection(with: uuid, tabInfo: tabInfo)) - menuSections.append(getHorizontalTabsSection(with: uuid, tabInfo: tabInfo)) - } else { - menuSections.append(getSiteSection(with: uuid, tabInfo: tabInfo, isExpanded: isExpanded)) - } + menuSections.append(getSiteSection(with: uuid, tabInfo: tabInfo, isExpanded: isExpanded)) } return menuSections From b966eaff362080c780536aafd1d45c73080d52a4 Mon Sep 17 00:00:00 2001 From: Alexandru Farcasanu Date: Tue, 10 Jun 2025 09:58:52 +0300 Subject: [PATCH 3/4] Fixed SwiftLint warning --- .../Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift index 736748fc721f0..75872824a0cdf 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift @@ -64,7 +64,8 @@ final class MenuRedesignTableView: UIView, tableView.register(MenuRedesignCell.self, forCellReuseIdentifier: MenuRedesignCell.cellIdentifier) tableView.register(MenuInfoCell.self, forCellReuseIdentifier: MenuInfoCell.cellIdentifier) tableView.register(MenuAccountCell.self, forCellReuseIdentifier: MenuAccountCell.cellIdentifier) - tableView.register(MenuCollectionViewContainerCell.self, forCellReuseIdentifier: MenuCollectionViewContainerCell.cellIdentifier) + tableView.register(MenuCollectionViewContainerCell.self, + forCellReuseIdentifier: MenuCollectionViewContainerCell.cellIdentifier) } func setupAccessibilityIdentifiers(menuA11yId: String, menuA11yLabel: String) { From 9c5dcd5556077c18f82fce9f12cdcc421c8d5966 Mon Sep 17 00:00:00 2001 From: Alexandru Farcasanu Date: Wed, 11 Jun 2025 10:27:22 +0300 Subject: [PATCH 4/4] Refactored Horizontal Cells, from CollectionView to StackView --- .../MenuCollectionViewContainerCell.swift | 129 ------------------ .../MenuRedesign/MenuRedesignTableView.swift | 34 +---- .../MenuKit/MenuRedesign/MenuSquareCell.swift | 2 +- .../MenuSquaresViewContentCell.swift | 67 +++++++++ 4 files changed, 74 insertions(+), 158 deletions(-) delete mode 100644 BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionViewContainerCell.swift create mode 100644 BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquaresViewContentCell.swift diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionViewContainerCell.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionViewContainerCell.swift deleted file mode 100644 index b08319c691d35..0000000000000 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuCollectionViewContainerCell.swift +++ /dev/null @@ -1,129 +0,0 @@ -// 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 UIKit -import Common - -final class MenuCollectionViewContainerCell: UITableViewCell, - UICollectionViewDelegate, - UICollectionViewDataSource, - UICollectionViewDelegateFlowLayout, - ReusableCell, - ThemeApplicable { - private struct UX { - static let minimumLineSpacing: CGFloat = 16 - } - - private var collectionView: UICollectionView - private var menuData: [MenuSection] - private var theme: Theme? - - private var horizontalTabsSection: MenuSection? { - return menuData.first(where: { $0.isHorizontalTabsSection }) - } - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .horizontal - layout.minimumInteritemSpacing = 0 - layout.minimumLineSpacing = UX.minimumLineSpacing - - collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) - collectionView.showsHorizontalScrollIndicator = false - - menuData = [] - super.init(style: style, reuseIdentifier: reuseIdentifier) - setupView() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupView() { - setupCollectionView() - setupUI() - } - - private func setupUI() { - collectionView.translatesAutoresizingMaskIntoConstraints = false - self.addSubview(collectionView) - - NSLayoutConstraint.activate([ - collectionView.topAnchor.constraint(equalTo: self.topAnchor), - collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor), - collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor), - collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor) - ]) - } - - private func setupCollectionView() { - collectionView.delegate = self - collectionView.dataSource = self - collectionView.register( - MenuSquareCell.self, - forCellWithReuseIdentifier: MenuSquareCell.cellIdentifier - ) - } - - func setupAccessibilityIdentifiers(menuA11yId: String, menuA11yLabel: String) { - collectionView.accessibilityIdentifier = menuA11yId - collectionView.accessibilityLabel = menuA11yLabel - } - - // MARK: - UICollectionView Methods - func collectionView( - _ collectionView: UICollectionView, - numberOfItemsInSection section: Int - ) -> Int { - guard let horizontalTabsSection else { return 0 } - return horizontalTabsSection.options.count - } - - func collectionView( - _ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath - ) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: MenuSquareCell.cellIdentifier, - for: indexPath - ) as? MenuSquareCell else { - return UICollectionViewCell() - } - - guard let horizontalTabsSection else { return UICollectionViewCell() } - cell.configureCellWith(model: horizontalTabsSection.options[indexPath.row]) - if let theme { cell.applyTheme(theme: theme) } - return cell - } - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - collectionView.deselectItem(at: indexPath, animated: false) - guard let horizontalTabsSection, - let action = horizontalTabsSection.options[indexPath.row].action else { return } - action() - } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize { - guard let horizontalTabsSection else { return .zero } - let itemCount = horizontalTabsSection.options.count - let width = (collectionView.bounds.width - CGFloat(itemCount - 1) * UX.minimumLineSpacing) / CGFloat(itemCount) - let height = collectionView.bounds.height - return CGSize(width: width, height: height) - } - - func reloadCollectionView(with data: [MenuSection]) { - menuData = data - collectionView.reloadData() - } - - // MARK: - Theme Applicable - func applyTheme(theme: Theme) { - self.theme = theme - backgroundColor = .clear - collectionView.backgroundColor = .clear - } -} diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift index 75872824a0cdf..f776bd962d621 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuRedesignTableView.swift @@ -14,12 +14,6 @@ final class MenuRedesignTableView: UIView, static let topPadding: CGFloat = 12 static let tableViewMargin: CGFloat = 16 static let distanceBetweenSections: CGFloat = 16 - - // Menu Redesign horizontal squares cells sizes - static let iconSize: CGFloat = 24 - static let contentViewSpacing: CGFloat = 4 - static let contentViewTopMargin: CGFloat = 12 - static let contentViewBottomMargin: CGFloat = 8 } private var tableView: UITableView @@ -64,8 +58,8 @@ final class MenuRedesignTableView: UIView, tableView.register(MenuRedesignCell.self, forCellReuseIdentifier: MenuRedesignCell.cellIdentifier) tableView.register(MenuInfoCell.self, forCellReuseIdentifier: MenuInfoCell.cellIdentifier) tableView.register(MenuAccountCell.self, forCellReuseIdentifier: MenuAccountCell.cellIdentifier) - tableView.register(MenuCollectionViewContainerCell.self, - forCellReuseIdentifier: MenuCollectionViewContainerCell.cellIdentifier) + tableView.register(MenuSquaresViewContentCell.self, + forCellReuseIdentifier: MenuSquaresViewContentCell.cellIdentifier) } func setupAccessibilityIdentifiers(menuA11yId: String, menuA11yLabel: String) { @@ -85,22 +79,6 @@ final class MenuRedesignTableView: UIView, return section == 0 ? UX.topPadding : UX.distanceBetweenSections } - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - if menuData[indexPath.section].isHorizontalTabsSection { - let label = UILabel() - label.font = FXFontStyles.Regular.caption1.scaledFont() - label.text = menuData[indexPath.section].options.first?.title - label.sizeToFit() - let textHeight = label.frame.height - let iconHeight: CGFloat = UX.iconSize - let spacing: CGFloat = UX.contentViewSpacing - let topMargin: CGFloat = UX.contentViewTopMargin - let bottomMargin: CGFloat = UX.contentViewBottomMargin - return topMargin + iconHeight + spacing + textHeight + bottomMargin - } - return UITableView.automaticDimension - } - func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int @@ -120,12 +98,12 @@ final class MenuRedesignTableView: UIView, ) -> UITableViewCell { if menuData[indexPath.section].isHorizontalTabsSection { guard let cell = tableView.dequeueReusableCell( - withIdentifier: MenuCollectionViewContainerCell.cellIdentifier, - for: indexPath) as? MenuCollectionViewContainerCell else { + withIdentifier: MenuSquaresViewContentCell.cellIdentifier, + for: indexPath) as? MenuSquaresViewContentCell else { return UITableViewCell() } - cell.reloadCollectionView(with: menuData) if let theme { cell.applyTheme(theme: theme) } + cell.reloadData(with: menuData) return cell } @@ -196,7 +174,7 @@ final class MenuRedesignTableView: UIView, } func reloadTableView(with data: [MenuSection]) { - // We ignore first section because it is handled in MenuCollectionViewContainerCell + // We ignore first section because it is handled in MenuSquaresViewContentCell if let firstSection = data.first, firstSection.isHorizontalTabsSection { tableView.showsVerticalScrollIndicator = false menuData = data diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift index cf13fe7f2e979..11a4a5e957a80 100644 --- a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquareCell.swift @@ -6,7 +6,7 @@ import Common import UIKit // TODO: FXIOS-12302 Create the UI for different accessibility sizes, for horizontal options section -final class MenuSquareCell: UICollectionViewCell, ReusableCell, ThemeApplicable { +final class MenuSquareView: UIView, ThemeApplicable { private struct UX { static let iconSize: CGFloat = 24 static let backgroundViewCornerRadius: CGFloat = 12 diff --git a/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquaresViewContentCell.swift b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquaresViewContentCell.swift new file mode 100644 index 0000000000000..58251d9946abb --- /dev/null +++ b/BrowserKit/Sources/MenuKit/MenuRedesign/MenuSquaresViewContentCell.swift @@ -0,0 +1,67 @@ +// 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 UIKit +import Common + +final class MenuSquaresViewContentCell: UITableViewCell, ReusableCell, ThemeApplicable { + private struct UX { + static let contentViewSpacing: CGFloat = 16 + } + + private var contentStackView: UIStackView = .build { stack in + stack.axis = .horizontal + stack.spacing = UX.contentViewSpacing + stack.distribution = .fillEqually + } + + private var menuData: [MenuSection] + private var theme: Theme? + + private var horizontalTabsSection: MenuSection? { + return menuData.first(where: { $0.isHorizontalTabsSection }) + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + menuData = [] + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupUI() { + self.addSubview(contentStackView) + + NSLayoutConstraint.activate([ + contentStackView.topAnchor.constraint(equalTo: self.topAnchor), + contentStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor), + contentStackView.leadingAnchor.constraint(equalTo: self.leadingAnchor), + contentStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor) + ]) + } + + func reloadData(with data: [MenuSection]) { + menuData = data + contentStackView.removeAllArrangedViews() + guard let horizontalTabsSection else { return } + for option in horizontalTabsSection.options { + let squareView: MenuSquareView = .build { [weak self] view in + guard let self else { return } + view.configureCellWith(model: option) + if let theme { view.applyTheme(theme: theme) } + } + contentStackView.addArrangedSubview(squareView) + } + } + + // MARK: - Theme Applicable + func applyTheme(theme: Theme) { + self.theme = theme + backgroundColor = .clear + contentStackView.backgroundColor = .clear + } +}