Skip to content

Commit

Permalink
Add FXIOS-9746 Bookmarks panel empty states (#23267)
Browse files Browse the repository at this point in the history
  • Loading branch information
MattLichtenstein authored Nov 21, 2024
1 parent 4e8df7f commit 1322e73
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 1 deletion.
4 changes: 4 additions & 0 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,7 @@
C4F3B29A1CFCF93A00966259 /* ButtonToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F3B2991CFCF93A00966259 /* ButtonToast.swift */; };
C706CBEF2C3F0FDE00DC65F1 /* CreditCardSettingsViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C706CBEE2C3F0FDE00DC65F1 /* CreditCardSettingsViewControllerTests.swift */; };
C787D8C32C1CB77900940123 /* FirefoxAccountSignInViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C787D8C22C1CB77900940123 /* FirefoxAccountSignInViewControllerTests.swift */; };
C7CDBEFD2CE6926900826A92 /* BookmarksFolderEmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7CDBEFC2CE6926900826A92 /* BookmarksFolderEmptyStateView.swift */; };
C80685D126A0C93900DCD895 /* UserResearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80685D026A0C93900DCD895 /* UserResearch.swift */; };
C807CCCC28367446008E6A5A /* FeatureFlagManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C807CCCB28367446008E6A5A /* FeatureFlagManagerTests.swift */; };
C80C11EE28B3C8B80062922A /* WallpaperMetadataTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80C11ED28B3C8B80062922A /* WallpaperMetadataTrackerTests.swift */; };
Expand Down Expand Up @@ -8177,6 +8178,7 @@
C787D8C22C1CB77900940123 /* FirefoxAccountSignInViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirefoxAccountSignInViewControllerTests.swift; sourceTree = "<group>"; };
C7A6413B89FE82B94F735FFD /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/ClearHistoryConfirm.strings; sourceTree = "<group>"; };
C7C54345A164D550CF2BC575 /* kn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kn; path = kn.lproj/3DTouchActions.strings; sourceTree = "<group>"; };
C7CDBEFC2CE6926900826A92 /* BookmarksFolderEmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksFolderEmptyStateView.swift; sourceTree = "<group>"; };
C7D341F8BA6A9E44518C11C8 /* oc */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = oc; path = oc.lproj/3DTouchActions.strings; sourceTree = "<group>"; };
C7DB49358A6A84AA7391EEE6 /* ta */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ta; path = ta.lproj/Intro.strings; sourceTree = "<group>"; };
C80685D026A0C93900DCD895 /* UserResearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserResearch.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -11168,6 +11170,7 @@
0B8BF3732CA2EB3300E9812D /* Edit Bookmark */,
0BBC50962CA1F86F00CB7248 /* Legacy */,
0BBC50942CA1F7F900CB7248 /* BookmarksRefactorFeatureFlagProvider.swift */,
C7CDBEFC2CE6926900826A92 /* BookmarksFolderEmptyStateView.swift */,
0B8BF36F2CA2D60B00E9812D /* BookmarksViewController.swift */,
);
path = Bookmarks;
Expand Down Expand Up @@ -16095,6 +16098,7 @@
C82A94F2269F68ED00624AA7 /* LegacyFeatureFlagsManager.swift in Sources */,
C8610DAA2A0EBF7100B79FF1 /* OnboardingCardDelegate.swift in Sources */,
3BB50E111D6274CD004B33DF /* TopSiteItemCell.swift in Sources */,
C7CDBEFD2CE6926900826A92 /* BookmarksFolderEmptyStateView.swift in Sources */,
819656192C80ECFE00E62323 /* MainMenuMiddleware.swift in Sources */,
E127313D28B6AD99006F39D2 /* WallpaperSettingsViewModel.swift in Sources */,
8A4593C72BF7BECA002758DE /* MicrosurveyTableViewCell.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions firefox-ios/Client/Application/ImageIdentifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public struct ImageIdentifiers {
public static let logoVisa = "logo_visa"
public static let menuBadge = "menuBadge"
public static let menuWarningMask = "warning-mask"
public static let noBookmarksInFolder = "noBookmarksInFolder"
public static let noBookmarksInRoot = "noBookmarksInRoot"
public static let qrCodeScanBorder = "qrcode-scanBorder"
public static let qrCodeScanLine = "qrcode-scanLine"
public static let shoppingNoAnalysisImage = "shoppingNoAnalysisImage"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "MobileBookmarks_folder.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "noBookmarks.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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 Foundation
import Common

final class BookmarksFolderEmptyStateView: UIView, ThemeApplicable {
private struct UX {
static let a11yTopMargin: CGFloat = 16
static let TitleTopMargin: CGFloat = 16
static let BodyTopMargin: CGFloat = 8
static let ContentLeftRightMargins: CGFloat = 16
static let StackViewWidthMultiplier: CGFloat = 0.9
static let imageWidth: CGFloat = 200
}

private lazy var logoImage: UIImageView = .build { imageView in
imageView.contentMode = .scaleAspectFit
}

private lazy var titleLabel: UILabel = .build { label in
label.textAlignment = .center
label.font = FXFontStyles.Bold.headline.scaledFont()
label.numberOfLines = 0
label.adjustsFontForContentSizeCategory = true
}

private lazy var bodyLabel: UILabel = .build { label in
label.textAlignment = .center
label.font = FXFontStyles.Regular.body.scaledFont()
label.numberOfLines = 0
label.adjustsFontForContentSizeCategory = true
}

private lazy var stackViewWrapper: UIStackView = .build { stackView in
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.spacing = 0
}

override init(frame: CGRect = .zero) {
super.init(frame: frame)

setupLayout()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func configure(isRoot: Bool) {
titleLabel.text = isRoot ? .Bookmarks.EmptyState.Root.Title : .Bookmarks.EmptyState.Nested.Title
bodyLabel.text = isRoot ? .Bookmarks.EmptyState.Root.Body : .Bookmarks.EmptyState.Nested.Body
logoImage.image = UIImage(named: isRoot ? ImageIdentifiers.noBookmarksInRoot : ImageIdentifiers.noBookmarksInFolder)
}

private func setupLayout() {
stackViewWrapper.addArrangedSubview(logoImage)
stackViewWrapper.setCustomSpacing(UX.TitleTopMargin, after: logoImage)
stackViewWrapper.addArrangedSubview(titleLabel)
stackViewWrapper.setCustomSpacing(UX.BodyTopMargin, after: titleLabel)
stackViewWrapper.addArrangedSubview(bodyLabel)
addSubview(stackViewWrapper)

let aspectRatio = (logoImage.image?.size.height ?? 1) / (logoImage.image?.size.width ?? 1)
NSLayoutConstraint.activate([
stackViewWrapper.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: UX.a11yTopMargin),
stackViewWrapper.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
stackViewWrapper.centerXAnchor.constraint(equalTo: centerXAnchor),
stackViewWrapper.centerYAnchor.constraint(equalTo: centerYAnchor),
stackViewWrapper.widthAnchor.constraint(equalTo: widthAnchor, multiplier: UX.StackViewWidthMultiplier),

titleLabel.leadingAnchor.constraint(
equalTo: stackViewWrapper.leadingAnchor, constant: UX.ContentLeftRightMargins),
titleLabel.trailingAnchor.constraint(
equalTo: stackViewWrapper.trailingAnchor, constant: -UX.ContentLeftRightMargins),

bodyLabel.leadingAnchor.constraint(
equalTo: stackViewWrapper.leadingAnchor, constant: UX.ContentLeftRightMargins),
bodyLabel.trailingAnchor.constraint(
equalTo: stackViewWrapper.trailingAnchor, constant: -UX.ContentLeftRightMargins),

logoImage.widthAnchor.constraint(equalToConstant: UX.imageWidth),
logoImage.heightAnchor.constraint(equalTo: logoImage.widthAnchor, multiplier: aspectRatio)
])
}

// MARK: ThemeApplicable
func applyTheme(theme: Theme) {
titleLabel.textColor = theme.colors.textPrimary
bodyLabel.textColor = theme.colors.textPrimary
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class BookmarksViewController: SiteTableViewController,
return button
}()

private lazy var emptyStateView: BookmarksFolderEmptyStateView = .build()

private lazy var a11yEmptyStateScrollView: UIScrollView = .build()

// MARK: - Init

init(viewModel: BookmarksPanelViewModel,
Expand Down Expand Up @@ -119,6 +123,8 @@ class BookmarksViewController: SiteTableViewController,
tableView.accessibilityIdentifier = AccessibilityIdentifiers.LibraryPanels.BookmarksPanel.tableView
tableView.allowsSelectionDuringEditing = true
tableView.dragInteractionEnabled = false

setupEmptyStateView()
}

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
Expand All @@ -137,6 +143,7 @@ class BookmarksViewController: SiteTableViewController,
if self?.viewModel.shouldFlashRow ?? false {
self?.flashRow()
}
self?.updateEmptyState()
}
}

Expand Down Expand Up @@ -185,6 +192,7 @@ class BookmarksViewController: SiteTableViewController,
self.tableView.insertRows(at: [indexPath], with: .automatic)
self.tableView.endUpdates()

self.updateEmptyState()
self.flashRow(at: indexPath)
}
}
Expand Down Expand Up @@ -240,6 +248,7 @@ class BookmarksViewController: SiteTableViewController,
viewModel.bookmarkNodes.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
tableView.endUpdates()
updateEmptyState()
}

// MARK: Button Actions helpers
Expand Down Expand Up @@ -306,6 +315,14 @@ class BookmarksViewController: SiteTableViewController,
}
}

private func updateEmptyState() {
a11yEmptyStateScrollView.isHidden = !viewModel.bookmarkNodes.isEmpty
if !a11yEmptyStateScrollView.isHidden {
let isRoot = viewModel.bookmarkFolderGUID == BookmarkRoots.MobileFolderGUID
emptyStateView.configure(isRoot: isRoot)
}
}

// MARK: - Long press

@objc
Expand All @@ -329,6 +346,31 @@ class BookmarksViewController: SiteTableViewController,
})
}

// MARK: - UI Setup
private func setupEmptyStateView() {
view.addSubview(a11yEmptyStateScrollView)
a11yEmptyStateScrollView.addSubview(emptyStateView)
a11yEmptyStateScrollView.isHidden = true
NSLayoutConstraint.activate(
[
a11yEmptyStateScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
a11yEmptyStateScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
a11yEmptyStateScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
a11yEmptyStateScrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),

emptyStateView.leadingAnchor.constraint(equalTo: a11yEmptyStateScrollView.contentLayoutGuide.leadingAnchor),
emptyStateView.trailingAnchor.constraint(
equalTo: a11yEmptyStateScrollView.contentLayoutGuide.trailingAnchor),
emptyStateView.topAnchor.constraint(equalTo: a11yEmptyStateScrollView.contentLayoutGuide.topAnchor),
emptyStateView.bottomAnchor.constraint(equalTo: a11yEmptyStateScrollView.contentLayoutGuide.bottomAnchor),
emptyStateView.widthAnchor.constraint(equalTo: a11yEmptyStateScrollView.frameLayoutGuide.widthAnchor),
emptyStateView.heightAnchor.constraint(
greaterThanOrEqualTo: a11yEmptyStateScrollView.frameLayoutGuide.heightAnchor),
]
)
emptyStateView.applyTheme(theme: currentTheme())
}

// MARK: - UITableViewDataSource | UITableViewDelegate

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
Expand Down
29 changes: 28 additions & 1 deletion firefox-ios/Client/Frontend/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ extension String {
}
}

// MARK: - Bookmarks Menu
// MARK: - Bookmarks Panel
extension String {
public struct Bookmarks {
public struct Menu {
Expand Down Expand Up @@ -201,6 +201,33 @@ extension String {
value: "Delete Bookmark",
comment: "The title for the Delete Bookmark button, in the Edit Bookmark popup screen which is summoned from the main menu's Save submenu, which will delete the currently bookmarked site from the user's bookmarks.")
}

public struct EmptyState {
public struct Root {
public static let Title = MZLocalizedString(
key: "Bookmarks.EmptyState.Root.Title.v135",
tableName: "Bookmarks",
value: "No bookmarks yet",
comment: "The title for the placeholder screen shown when there are no saved bookmarks, located at the root level of the bookmarks panel within the libray modal")
public static let Body = MZLocalizedString(
key: "Bookmarks.EmptyState.Root.Body.v135",
tableName: "Bookmarks",
value: "Save sites as you browse. We’ll also grab bookmarks from other synced devices.",
comment: "The body text for the placeholder screen shown when there are no saved bookmarks, located at the root level of the bookmarks panel within the libray modal")
}
public struct Nested {
public static let Title = MZLocalizedString(
key: "Bookmarks.EmptyState.Nested.Title.v135",
tableName: "Bookmarks",
value: "This folder is empty",
comment: "The title for the placeholder screen shown when there are no saved bookmarks, located within a nested subfolder of the bookmarks panel within the libray modal")
public static let Body = MZLocalizedString(
key: "Bookmarks.EmptyState.Nested.Body.v135",
tableName: "Bookmarks",
value: "Add bookmarks as you browse so you can find your favorite sites later.",
comment: "The body text for the placeholder screen shown when there are no saved bookmarks, located within a nested subfolder of the bookmarks panel within the libray modal")
}
}
}
}

Expand Down

0 comments on commit 1322e73

Please sign in to comment.