Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FXIOS-10007 [Bookmarks Evolution] Add sign in button to empty state #23442

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ public struct AccessibilityIdentifiers {
static let tableView = "Bookmarks List"
static let titleTextField = "BookmarkDetail.titleTextField"
static let urlTextField = "BookmarkDetail.urlTextField"

struct EmptyState {
static let signInButton = "BookmarksPanel.RootEmptyState.signInButton"
}
}

struct HistoryPanel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ protocol BookmarksCoordinatorDelegate: AnyObject, LibraryPanelCoordinatorDelegat
parentBookmarkFolder: FxBookmarkNode,
updatePanelState: ((LibraryPanelSubState) -> Void)?
)

func showSignIn()
}

extension BookmarksCoordinatorDelegate {
Expand All @@ -36,12 +38,15 @@ extension BookmarksCoordinatorDelegate {

class BookmarksCoordinator: BaseCoordinator,
BookmarksCoordinatorDelegate,
BookmarksRefactorFeatureFlagProvider {
QRCodeNavigationHandler,
BookmarksRefactorFeatureFlagProvider,
ParentCoordinatorDelegate {
// MARK: - Properties

private let profile: Profile
private weak var parentCoordinator: LibraryCoordinatorDelegate?
private weak var navigationHandler: LibraryNavigationHandler?
private var fxAccountViewController: FirefoxAccountSignInViewController?
private let windowUUID: WindowUUID

// MARK: - Initializers
Expand Down Expand Up @@ -115,10 +120,44 @@ class BookmarksCoordinator: BaseCoordinator,
}
}

func showSignIn() {
let controller = makeSignInController()
router.present(controller)
}

func shareLibraryItem(url: URL, sourceView: UIView) {
navigationHandler?.shareLibraryItem(url: url, sourceView: sourceView)
}

// MARK: - QRCodeNavigationHandler

func showQRCode(delegate: QRCodeViewControllerDelegate, rootNavigationController: UINavigationController?) {
var coordinator: QRCodeCoordinator
if let qrCodeCoordinator = childCoordinators.first(where: { $0 is QRCodeCoordinator }) as? QRCodeCoordinator {
coordinator = qrCodeCoordinator
} else {
if rootNavigationController != nil {
coordinator = QRCodeCoordinator(
parentCoordinator: self,
router: DefaultRouter(navigationController: rootNavigationController!)
)
} else {
coordinator = QRCodeCoordinator(
parentCoordinator: self,
router: router
)
}
add(child: coordinator)
}
coordinator.showQRCode(delegate: delegate)
}

// MARK: - ParentCoordinatorDelegate

func didFinish(from childCoordinator: Coordinator) {
remove(child: childCoordinator)
}

// MARK: - Factory

private func makeDetailController(for type: BookmarkNodeType, parentFolder: FxBookmarkNode) -> UIViewController {
Expand Down Expand Up @@ -181,6 +220,25 @@ class BookmarksCoordinator: BaseCoordinator,
return controller
}

private func makeSignInController() -> UIViewController {
let fxaParams = FxALaunchParams(entrypoint: .libraryPanel, query: [:])
let viewController = FirefoxAccountSignInViewController(profile: profile,
parentType: .library,
deepLinkParams: fxaParams,
windowUUID: windowUUID)
viewController.qrCodeNavigationHandler = self
let buttonItem = UIBarButtonItem(
title: .CloseButtonTitle,
style: .plain,
target: self,
action: #selector(dismissFxAViewController)
)
viewController.navigationItem.leftBarButtonItem = buttonItem
let navController = ThemedNavigationController(rootViewController: viewController, windowUUID: windowUUID)
fxAccountViewController = viewController
return navController
}

private func reloadLastBookmarksController() {
guard let rootBookmarkController = router.navigationController.viewControllers.last
as? BookmarksViewController
Expand All @@ -195,4 +253,10 @@ class BookmarksCoordinator: BaseCoordinator,
let backBarButton = UIBarButtonItem(title: title)
router.navigationController.viewControllers.last?.navigationItem.backBarButtonItem = backBarButton
}

@objc
private func dismissFxAViewController() {
fxAccountViewController?.dismissVC()
fxAccountViewController = nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@

import Foundation
import Common
import ComponentLibrary

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 titleTopMargin: CGFloat = 16
static let bodyTopMargin: CGFloat = 8
static let buttonTopMargin: CGFloat = 16
static let contentLeftRightMargins: CGFloat = 16
static let stackViewWidthMultiplier: CGFloat = 0.9
static let imageWidth: CGFloat = 200
static let signInButtonMaxWidth: CGFloat = 306
}

var signInAction: (() -> Void)?

private lazy var logoImage: UIImageView = .build { imageView in
imageView.contentMode = .scaleAspectFit
}
Expand All @@ -33,6 +38,15 @@ final class BookmarksFolderEmptyStateView: UIView, ThemeApplicable {
label.adjustsFontForContentSizeCategory = true
}

private lazy var signInButton: PrimaryRoundedButton = .build { button in
let viewModel = PrimaryRoundedButtonViewModel(
title: .Bookmarks.EmptyState.Root.ButtonTitle,
a11yIdentifier: AccessibilityIdentifiers.LibraryPanels.BookmarksPanel.EmptyState.signInButton
)
button.configure(viewModel: viewModel)
button.addTarget(self, action: #selector(self.didTapSignIn), for: .touchUpInside)
}

private lazy var stackViewWrapper: UIStackView = .build { stackView in
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
Expand All @@ -50,18 +64,21 @@ final class BookmarksFolderEmptyStateView: UIView, ThemeApplicable {
fatalError("init(coder:) has not been implemented")
}

func configure(isRoot: Bool) {
func configure(isRoot: Bool, isSignedIn: 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)
signInButton.isHidden = !isRoot || isSignedIn
}

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

let aspectRatio = (logoImage.image?.size.height ?? 1) / (logoImage.image?.size.width ?? 1)
Expand All @@ -70,26 +87,35 @@ final class BookmarksFolderEmptyStateView: UIView, ThemeApplicable {
stackViewWrapper.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
stackViewWrapper.centerXAnchor.constraint(equalTo: centerXAnchor),
stackViewWrapper.centerYAnchor.constraint(equalTo: centerYAnchor),
stackViewWrapper.widthAnchor.constraint(equalTo: widthAnchor, multiplier: UX.StackViewWidthMultiplier),
stackViewWrapper.widthAnchor.constraint(equalTo: widthAnchor, multiplier: UX.stackViewWidthMultiplier),

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

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

signInButton.widthAnchor.constraint(equalToConstant: UX.signInButtonMaxWidth),

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

// MARK: Actions
@objc
private func didTapSignIn() {
signInAction?()
}

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

private lazy var emptyStateView: BookmarksFolderEmptyStateView = .build()
private lazy var emptyStateView: BookmarksFolderEmptyStateView = .build { emptyStateView in
emptyStateView.signInAction = { [weak self] in
self?.bookmarkCoordinatorDelegate?.showSignIn()
}
}

private lazy var a11yEmptyStateScrollView: UIScrollView = .build()

Expand All @@ -98,7 +102,7 @@ class BookmarksViewController: SiteTableViewController,
self.bookmarksHandler = viewModel.profile.places
super.init(profile: viewModel.profile, windowUUID: windowUUID)

setupNotifications(forObserver: self, observing: [.FirefoxAccountChanged])
setupNotifications(forObserver: self, observing: [.FirefoxAccountChanged, .ProfileDidFinishSyncing])

tableView.register(cellType: OneLineTableViewCell.self)
tableView.register(cellType: SeparatorTableViewCell.self)
Expand Down Expand Up @@ -139,11 +143,13 @@ class BookmarksViewController: SiteTableViewController,

override func reloadData() {
viewModel.reloadData { [weak self] in
self?.tableView.reloadData()
if self?.viewModel.shouldFlashRow ?? false {
self?.flashRow()
DispatchQueue.main.async {
self?.tableView.reloadData()
if self?.viewModel.shouldFlashRow ?? false {
self?.flashRow()
}
self?.updateEmptyState()
}
self?.updateEmptyState()
}
}

Expand Down Expand Up @@ -319,7 +325,8 @@ class BookmarksViewController: SiteTableViewController,
a11yEmptyStateScrollView.isHidden = !viewModel.bookmarkNodes.isEmpty
if !a11yEmptyStateScrollView.isHidden {
let isRoot = viewModel.bookmarkFolderGUID == BookmarkRoots.MobileFolderGUID
emptyStateView.configure(isRoot: isRoot)
let isSignedIn = profile.hasAccount()
emptyStateView.configure(isRoot: isRoot, isSignedIn: isSignedIn)
}
}

Expand Down Expand Up @@ -573,7 +580,7 @@ extension BookmarksViewController: LibraryPanelContextMenu {
extension BookmarksViewController: Notifiable {
func handleNotifications(_ notification: Notification) {
switch notification.name {
case .FirefoxAccountChanged:
case .FirefoxAccountChanged, .ProfileDidFinishSyncing:
reloadData()
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,16 @@ class BookmarksPanelViewModel: BookmarksRefactorFeatureFlagProvider {
// Create a local "Desktop bookmarks" folder only if there exists a bookmark in one of it's nested
// subfolders
self.bookmarksHandler.countBookmarksInTrees(folderGuids: BookmarkRoots.DesktopRoots.map { $0 }) { result in
DispatchQueue.main.async {
switch result {
case .success(let bookmarkCount):
switch result {
case .success(let bookmarkCount):
if bookmarkCount > 0 || !self.isBookmarkRefactorEnabled {
let desktopFolder = LocalDesktopFolder()
self.bookmarkNodes.insert(desktopFolder, at: 0)
}
case .failure(let error):
case .failure(let error):
self.logger.log("Error counting bookmarks: \(error)", level: .debug, category: .library)
}
completion()
}
completion()
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions firefox-ios/Client/Frontend/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ extension String {
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 static let ButtonTitle = MZLocalizedString(
key: "Bookmarks.EmptyState.Root.ButtonTitle.v135",
tableName: "Bookmarks",
value: "Sign in to Sync",
comment: "The button title for the sign in button on the placeholder screen shown when there are no saved bookmarks, located at the root level of the bookmarks panel within the library modal. This button triggers the sign in flow, allowing users to sign in to their Mozilla Account to sync data")
}
public struct Nested {
public static let Title = MZLocalizedString(
Expand Down
1 change: 1 addition & 0 deletions firefox-ios/Client/Telemetry/ReferringPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ enum ReferringPage: Equatable {
case settings
case none
case tabTray
case library
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum FxASignInParentType {
case onboarding
case upgrade
case tabTray
case library
}

/// ViewController handling Sign In through QR Code or Email address
Expand Down Expand Up @@ -147,6 +148,9 @@ class FirefoxAccountSignInViewController: UIViewController, Themeable {
case .tabTray:
self.telemetryObject = .tabTray
self.fxaDismissStyle = .popToTabTray
case .library:
self.telemetryObject = .libraryPanel
self.fxaDismissStyle = .dismiss
}
self.logger = logger
self.notificationCenter = notificationCenter
Expand Down Expand Up @@ -361,6 +365,9 @@ extension FirefoxAccountSignInViewController {
case .tabTray:
parentType = .tabTray
object = .tabTray
case .library:
parentType = .library
object = .libraryPanel
}

let signInVC = FirefoxAccountSignInViewController(
Expand Down
Loading
Loading