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-10318 [Menu] Populate account header with data & redux #22718

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
35 changes: 32 additions & 3 deletions BrowserKit/Sources/ComponentLibrary/Headers/HeaderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public final class HeaderView: UIView, ThemeApplicable {
static let headerLabelDistance: CGFloat = 2
static let separatorHeight: CGFloat = 1
static let closeButtonSize: CGFloat = 30
static let warningIconSize: CGFloat = 24
}

public var closeButtonCallback: (() -> Void)?
Expand All @@ -36,6 +37,7 @@ public final class HeaderView: UIView, ThemeApplicable {
}

private let titleLabel: UILabel = .build { label in
label.font = FXFontStyles.Regular.body.scaledFont()
label.numberOfLines = UX.headerLinesLimit
label.adjustsFontForContentSizeCategory = true
}
Expand All @@ -46,6 +48,8 @@ public final class HeaderView: UIView, ThemeApplicable {
label.adjustsFontForContentSizeCategory = true
}

private lazy var warningIconView: UIImageView = .build()

private lazy var closeButton: CloseButton = .build { button in
button.addTarget(self, action: #selector(self.closeButtonTapped), for: .touchUpInside)
}
Expand Down Expand Up @@ -75,13 +79,15 @@ public final class HeaderView: UIView, ThemeApplicable {
private func setupViews() {
headerLabelsContainer.addArrangedSubview(titleLabel)
headerLabelsContainer.addArrangedSubview(subtitleLabel)
addSubviews(mainButton, iconMask, favicon, headerLabelsContainer, closeButton, horizontalLine)
addSubviews(iconMask, favicon, headerLabelsContainer, mainButton, closeButton, warningIconView, horizontalLine)
warningIconView.isHidden = true
}

private func updateLayout(isAccessibilityCategory: Bool, isWebsiteIcon: Bool) {
removeConstraints(constraints)
favicon.removeConstraints(favicon.constraints)
closeButton.removeConstraints(closeButton.constraints)
warningIconView.removeConstraints(warningIconView.constraints)
iconMask.removeConstraints(iconMask.constraints)
viewConstraints.removeAll()
viewConstraints.append(contentsOf: [
Expand All @@ -103,10 +109,11 @@ public final class HeaderView: UIView, ThemeApplicable {
constant: UX.siteDomainLabelsVerticalSpacing
),
headerLabelsContainer.trailingAnchor.constraint(
equalTo: closeButton.leadingAnchor,
equalTo: warningIconView.leadingAnchor,
constant: -UX.horizontalMargin
),

warningIconView.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -UX.horizontalMargin),
closeButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -UX.horizontalMargin),

horizontalLine.leadingAnchor.constraint(equalTo: leadingAnchor),
Expand All @@ -132,6 +139,10 @@ public final class HeaderView: UIView, ThemeApplicable {
viewConstraints.append(closeButton.widthAnchor.constraint(equalToConstant: closeButtonSizes))
closeButton.layer.cornerRadius = 0.5 * closeButtonSizes

let warningIconSizes = isAccessibilityCategory ? UX.largeFaviconImageSize : UX.warningIconSize
viewConstraints.append(warningIconView.heightAnchor.constraint(equalToConstant: warningIconSizes))
viewConstraints.append(warningIconView.widthAnchor.constraint(equalToConstant: warningIconSizes))

let maskButtonSizes = isAccessibilityCategory ? UX.largeFaviconImageSize : UX.maskFaviconImageSize
viewConstraints.append(iconMask.heightAnchor.constraint(equalToConstant: maskButtonSizes))
viewConstraints.append(iconMask.widthAnchor.constraint(equalToConstant: maskButtonSizes))
Expand All @@ -140,9 +151,11 @@ public final class HeaderView: UIView, ThemeApplicable {
if isAccessibilityCategory {
viewConstraints.append(favicon.topAnchor.constraint(equalTo: headerLabelsContainer.topAnchor))
viewConstraints.append(closeButton.topAnchor.constraint(equalTo: headerLabelsContainer.topAnchor))
viewConstraints.append(warningIconView.topAnchor.constraint(equalTo: headerLabelsContainer.topAnchor))
} else {
viewConstraints.append(favicon.centerYAnchor.constraint(equalTo: centerYAnchor))
viewConstraints.append(closeButton.centerYAnchor.constraint(equalTo: centerYAnchor))
viewConstraints.append(warningIconView.centerYAnchor.constraint(equalTo: centerYAnchor))
}
NSLayoutConstraint.activate(viewConstraints)
}
Expand Down Expand Up @@ -172,12 +185,28 @@ public final class HeaderView: UIView, ThemeApplicable {
}

public func setupDetails(subtitle: String, title: String, icon: UIImage?) {
titleLabel.font = FXFontStyles.Regular.body.scaledFont()
if let icon { favicon.manuallySetImage(icon) }
subtitleLabel.text = subtitle
titleLabel.text = title
}

public func setupDetails(subtitle: String, title: String, icon: UIImage?, warningIcon: String?, theme: Theme) {
titleLabel.text = title
subtitleLabel.text = subtitle
subtitleLabel.textColor = theme.colors.textWarning
if let icon {
favicon.manuallySetImage(icon)
let isAccessibilityCategory = UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory
let maskButtonSizes = isAccessibilityCategory ? UX.largeFaviconImageSize : UX.smallFaviconImageSize
favicon.layer.cornerRadius = 0.5 * maskButtonSizes
}
if let warningIcon {
warningIconView.tintColor = theme.colors.iconWarning
warningIconView.isHidden = false
warningIconView.image = UIImage(named: warningIcon)?.withRenderingMode(.alwaysTemplate)
}
}

public func setIconTheme(with theme: Theme) {
iconMask.backgroundColor = theme.colors.layer2
favicon.tintColor = theme.colors.iconSecondary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,13 @@ class BrowserCoordinator: BaseCoordinator,
return navigationController
}

func showSignInView(fxaParameters: FxASignInViewParameters?) {
guard let fxaParameters else { return }
browserViewController.presentSignInViewController(fxaParameters.launchParameters,
flowType: fxaParameters.flowType,
referringPage: fxaParameters.referringPage)
}

// MARK: - SearchEngineSelectionCoordinatorDelegate

func showSearchEngineSelection(forSourceView sourceView: UIView) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ protocol MainMenuCoordinatorDelegate: AnyObject {
func showLibraryPanel(_ panel: Route.HomepanelSection)
func showSettings(at destination: Route.SettingsSection)
func showFindInPage()
func showSignInView(fxaParameters: FxASignInViewParameters?)
func updateZoomPageBarVisibility()
func showShareSheet(with url: URL?)
}
Expand Down Expand Up @@ -81,6 +82,13 @@ class MainMenuCoordinator: BaseCoordinator, FeatureFlaggable {
self.navigationHandler?.showSettings(at: .password)
case .settings:
self.navigationHandler?.showSettings(at: .general)
case .syncSignIn:
let fxaParameters = FxASignInViewParameters(
launchParameters: FxALaunchParams(entrypoint: .browserMenu, query: [:]),
flowType: .emailLoginFlow,
referringPage: .appMenu
)
self.navigationHandler?.showSignInView(fxaParameters: fxaParameters)
case .shareSheet:
self.navigationHandler?.showShareSheet(with: destination.url)
case .zoom:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import Common
import MenuKit
import Shared
import Redux
import Account

struct AccountData: Equatable {
let title: String
let subtitle: String?
let warningIcon: String?
let iconURL: URL?
}

struct MainMenuTabInfo: Equatable {
let tabID: TabUUID
Expand All @@ -27,6 +35,8 @@ struct MainMenuState: ScreenState, Equatable {

var shouldDismiss: Bool

var accountData: AccountData?

var navigationDestination: MenuNavigationDestination?
var currentTabInfo: MainMenuTabInfo?
var currentSubmenuView: MainMenuDetailsViewType?
Expand All @@ -49,7 +59,8 @@ struct MainMenuState: ScreenState, Equatable {
currentTabInfo: mainMenuState.currentTabInfo,
submenuDestination: mainMenuState.currentSubmenuView,
navigationDestination: mainMenuState.navigationDestination,
shouldDismiss: mainMenuState.shouldDismiss
shouldDismiss: mainMenuState.shouldDismiss,
accountData: mainMenuState.accountData
)
}

Expand All @@ -60,7 +71,8 @@ struct MainMenuState: ScreenState, Equatable {
currentTabInfo: nil,
submenuDestination: nil,
navigationDestination: nil,
shouldDismiss: false
shouldDismiss: false,
accountData: nil
)
}

Expand All @@ -70,14 +82,16 @@ struct MainMenuState: ScreenState, Equatable {
currentTabInfo: MainMenuTabInfo?,
submenuDestination: MainMenuDetailsViewType? = nil,
navigationDestination: MenuNavigationDestination? = nil,
shouldDismiss: Bool = false
shouldDismiss: Bool = false,
accountData: AccountData? = nil
adudenamedruby marked this conversation as resolved.
Show resolved Hide resolved
) {
self.windowUUID = windowUUID
self.menuElements = menuElements
self.currentSubmenuView = submenuDestination
self.currentTabInfo = currentTabInfo
self.navigationDestination = navigationDestination
self.shouldDismiss = shouldDismiss
self.accountData = accountData
}

static let reducer: Reducer<Self> = { state, action in
Expand All @@ -90,6 +104,13 @@ struct MainMenuState: ScreenState, Equatable {
}

switch action.actionType {
case MainMenuActionType.viewDidLoad:
return MainMenuState(
windowUUID: state.windowUUID,
menuElements: state.menuElements,
currentTabInfo: state.currentTabInfo,
accountData: state.getAccountData()
)
case MainMenuActionType.updateCurrentTabInfo:
guard let action = action as? MainMenuAction,
let currentTabInfo = action.currentTabInfo
Expand Down Expand Up @@ -136,4 +157,30 @@ struct MainMenuState: ScreenState, Equatable {
)
}
}

private func getAccountData() -> AccountData? {
let rustAccount = RustFirefoxAccounts.shared
let needsReAuth = rustAccount.accountNeedsReauth()

guard let userProfile = rustAccount.userProfile else { return nil }

let title: String = {
if needsReAuth { return .MainMenu.Account.SyncErrorTitle }
return userProfile.displayName ?? userProfile.email
}()

let subtitle: String? = needsReAuth ? .MainMenu.Account.SyncErrorDescription : nil
let warningIcon: String? = needsReAuth ? StandardImageIdentifiers.Large.warningFill : nil

var iconURL: URL?
if let str = rustAccount.userProfile?.avatarUrl,
let url = URL(string: str, invalidCharacters: false) {
iconURL = url
}

return AccountData(title: title,
subtitle: subtitle,
warningIcon: warningIcon,
iconURL: iconURL)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum MainMenuNavigationDestination: Equatable, CaseIterable {
case newPrivateTab
case passwords
case settings
case syncSignIn
case shareSheet
case zoom
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import UIKit
import Shared
import Redux
import MenuKit
import SiteImageView

class MainMenuViewController: UIViewController,
UIAdaptivePresentationControllerDelegate,
Expand Down Expand Up @@ -89,7 +90,24 @@ class MainMenuViewController: UIViewController,
)

menuContent.accountHeaderView.closeButtonCallback = { [weak self] in
self?.coordinator?.dismissMenuModal(animated: true)
guard let self else { return }
store.dispatch(
MainMenuAction(
windowUUID: self.windowUUID,
actionType: MainMenuActionType.tapCloseMenu
)
)
}

menuContent.accountHeaderView.mainButtonCallback = { [weak self] in
guard let self else { return }
store.dispatch(
MainMenuAction(
windowUUID: self.windowUUID,
actionType: MainMenuActionType.tapNavigateToDestination,
navigationDestination: MenuNavigationDestination(.syncSignIn)
)
)
}

setupAccessibilityIdentifiers()
Expand Down Expand Up @@ -223,6 +241,17 @@ class MainMenuViewController: UIViewController,
func newState(state: MainMenuState) {
menuState = state

if let accountData = menuState.accountData {
if let iconURL = accountData.iconURL {
GeneralizedImageFetcher().getImageFor(url: iconURL) { [weak self] image in
adudenamedruby marked this conversation as resolved.
Show resolved Hide resolved
guard let self else { return }
self.updateHeaderWith(accountData: accountData, icon: image)
}
} else {
updateHeaderWith(accountData: accountData, icon: nil)
}
}

if menuState.currentSubmenuView != nil {
coordinator?.showDetailViewController()
return
Expand All @@ -248,6 +277,14 @@ class MainMenuViewController: UIViewController,
menuContent.applyTheme(theme: theme)
}

private func updateHeaderWith(accountData: AccountData, icon: UIImage?) {
menuContent.accountHeaderView.setupDetails(subtitle: accountData.subtitle ?? "",
title: accountData.title,
icon: icon,
warningIcon: accountData.warningIcon,
theme: themeManager.getCurrentTheme(for: windowUUID))
}

// MARK: - A11y
// In iOS 15 modals with a large detent read content underneath the modal
// in voice over. To prevent this we manually turn this off.
Expand Down
2 changes: 1 addition & 1 deletion firefox-ios/RustFxA/FxASignInViewParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import Foundation

/// Used to determine the sign in flow, used mostly with deeplinks
struct FxASignInViewParameters {
struct FxASignInViewParameters: Equatable {
var launchParameters: FxALaunchParams
var flowType: FxAPageType
var referringPage: ReferringPage
Expand Down