Skip to content

Commit

Permalink
Add FXIOS-10913 [Homepage] [Top Sites] full layout configuration for …
Browse files Browse the repository at this point in the history
…top sites (#23918)
  • Loading branch information
cyndichin authored Dec 26, 2024
1 parent 4bd067d commit c0a82b0
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 22 deletions.
12 changes: 6 additions & 6 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1052,15 +1052,14 @@
8AE1E1D927B1BD380024C45E /* UIStackViewExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE1E1D827B1BD380024C45E /* UIStackViewExtensionsTests.swift */; };
8AE1E1DB27B1C1320024C45E /* SearchBarSettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE1E1DA27B1C1320024C45E /* SearchBarSettingsViewModelTests.swift */; };
8AE459922CEFB1DC002D6679 /* InactiveTabsTelemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE459912CEFB1DC002D6679 /* InactiveTabsTelemetry.swift */; };
8AE80BAD2891957C00BC12EA /* TopSitesDimensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BAC2891957C00BC12EA /* TopSitesDimensionTests.swift */; };
8AE80BAD2891957C00BC12EA /* LegacyTopSitesDimensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BAC2891957C00BC12EA /* LegacyTopSitesDimensionTests.swift */; };
8AE80BAF2891960300BC12EA /* MockTraitCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BAE2891960300BC12EA /* MockTraitCollection.swift */; };
8AE80BB62891AEA100BC12EA /* MockDispatchGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BB42891AE6700BC12EA /* MockDispatchGroup.swift */; };
8AE80BB82891BE0700BC12EA /* JumpBackInDataAdaptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BB72891BE0700BC12EA /* JumpBackInDataAdaptor.swift */; };
8AE80BBA2891C0C300BC12EA /* JumpBackInSectionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BB92891C0C300BC12EA /* JumpBackInSectionLayout.swift */; };
8AE80BBC2891C20D00BC12EA /* JumpBackInList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BBB2891C20D00BC12EA /* JumpBackInList.swift */; };
8AE80BBE2891C21A00BC12EA /* JumpBackInSyncedTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE80BBD2891C21A00BC12EA /* JumpBackInSyncedTab.swift */; };
8AE938192CD91D5A0020E6CF /* TopSiteState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE938182CD91D5A0020E6CF /* TopSiteState.swift */; };
8AE9381B2CD91FDB0020E6CF /* TopSitesSectionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE9381A2CD91FDB0020E6CF /* TopSitesSectionStateTests.swift */; };
8AE9381D2CD920310020E6CF /* TopSiteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE9381C2CD920310020E6CF /* TopSiteCell.swift */; };
8AE9FD262CF66301001053EE /* UnifiedAdsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE9FD252CF662FF001053EE /* UnifiedAdsProvider.swift */; };
8AEAD9F32C3D7B3E001A2C5A /* FeatureFlagsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AEAD9F22C3D7B3E001A2C5A /* FeatureFlagsSettings.swift */; };
Expand All @@ -1073,6 +1072,7 @@
8AEE62C92756BA34003207D1 /* LoginsHelper.js in Resources */ = {isa = PBXBuildFile; fileRef = 8AEE62C62756BA34003207D1 /* LoginsHelper.js */; };
8AEE62CA2756BA34003207D1 /* TrackingProtectionStats.js in Resources */ = {isa = PBXBuildFile; fileRef = 8AEE62C72756BA34003207D1 /* TrackingProtectionStats.js */; };
8AEE62CB2756BA34003207D1 /* DownloadHelper.js in Resources */ = {isa = PBXBuildFile; fileRef = 8AEE62C82756BA34003207D1 /* DownloadHelper.js */; };
8AEF41602D15D6290013925D /* TopSitesSectionStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE9381A2CD91FDB0020E6CF /* TopSitesSectionStateTests.swift */; };
8AF10D8A29D713F50086351D /* LaunchScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF10D8929D713F50086351D /* LaunchScreenViewModelTests.swift */; };
8AF10D8F29D774090086351D /* SceneSetupHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF10D8E29D774090086351D /* SceneSetupHelper.swift */; };
8AF10D9129D7761A0086351D /* MockLaunchScreenManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AF10D9029D776190086351D /* MockLaunchScreenManager.swift */; };
Expand Down Expand Up @@ -7836,7 +7836,7 @@
8AE1E1D827B1BD380024C45E /* UIStackViewExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackViewExtensionsTests.swift; sourceTree = "<group>"; };
8AE1E1DA27B1C1320024C45E /* SearchBarSettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarSettingsViewModelTests.swift; sourceTree = "<group>"; };
8AE459912CEFB1DC002D6679 /* InactiveTabsTelemetry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InactiveTabsTelemetry.swift; sourceTree = "<group>"; };
8AE80BAC2891957C00BC12EA /* TopSitesDimensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSitesDimensionTests.swift; sourceTree = "<group>"; };
8AE80BAC2891957C00BC12EA /* LegacyTopSitesDimensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyTopSitesDimensionTests.swift; sourceTree = "<group>"; };
8AE80BAE2891960300BC12EA /* MockTraitCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTraitCollection.swift; sourceTree = "<group>"; };
8AE80BB42891AE6700BC12EA /* MockDispatchGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDispatchGroup.swift; sourceTree = "<group>"; };
8AE80BB72891BE0700BC12EA /* JumpBackInDataAdaptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JumpBackInDataAdaptor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -12182,7 +12182,7 @@
8A7A93ED2810ADF2005E7E1B /* ContileProviderTests.swift */,
961577932A39008100391E8D /* SponsoredTileDataUtilityTests.swift */,
8A33221E27DFE318008F809E /* TopSitesDataAdaptorTests.swift */,
8AE80BAC2891957C00BC12EA /* TopSitesDimensionTests.swift */,
8AE80BAC2891957C00BC12EA /* LegacyTopSitesDimensionTests.swift */,
3B6F40171DC7849C00656CC6 /* TopSitesViewModelTests.swift */,
);
path = TopSites;
Expand Down Expand Up @@ -15915,7 +15915,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8AE9381B2CD91FDB0020E6CF /* TopSitesSectionStateTests.swift in Sources */,
4590912E2A2E4F7700061F0C /* AutopushTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -17238,6 +17237,7 @@
E18259E329B2A51B00E6BE76 /* MockNotificationManager.swift in Sources */,
96A5F736298D8EDF00234E5F /* MockSearchEngineProvider.swift in Sources */,
8A5D1CA02A30C9D7005AD35C /* MockAppSettingsDelegate.swift in Sources */,
8AEF41602D15D6290013925D /* TopSitesSectionStateTests.swift in Sources */,
1D7B789F2AE088930011E9F2 /* EventQueueTests.swift in Sources */,
21A1C3C72996AFF800181B7C /* OverlayModeManagerTests.swift in Sources */,
ED07C0F52CCB020B006C0627 /* SearchEngineSelectionMiddlewareTests.swift in Sources */,
Expand Down Expand Up @@ -17500,7 +17500,7 @@
8AFC4E472CAF53E100C54B43 /* RemoteDataTypeTests.swift in Sources */,
8A7AF0C72C9A119A009691B5 /* TabPeekStateTests.swift in Sources */,
0AFF7F642C7784D600265214 /* MockDataCleaner.swift in Sources */,
8AE80BAD2891957C00BC12EA /* TopSitesDimensionTests.swift in Sources */,
8AE80BAD2891957C00BC12EA /* LegacyTopSitesDimensionTests.swift in Sources */,
D82ED2641FEB3C420059570B /* DefaultSearchPrefsTests.swift in Sources */,
1D74FF502B2797EA00FF01D0 /* WindowManagerTests.swift in Sources */,
CA24B53B24ABFE5D0093848C /* PasswordManagerDataSourceHelperTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ final class HomepageDiffableDataSource:
snapshot.appendSections([.header, .topSites, .pocket(textColor), .customizeHomepage])
snapshot.appendItems([.header(textColor)], toSection: .header)

let topSites: [HomeItem] = state.topSitesState.topSitesData.compactMap { .topSite($0, textColor) }
let topSites = getTopSites(with: state.topSitesState, and: textColor)
snapshot.appendItems(topSites, toSection: .topSites)

let stories: [HomeItem] = state.pocketState.pocketData.compactMap { .pocket($0) }
Expand All @@ -68,4 +68,19 @@ final class HomepageDiffableDataSource:

apply(snapshot, animatingDifferences: true)
}

/// Gets the proper amount of top sites based on layout configuration
/// which is determined by the number of rows and number of tiles per row
/// - Parameters:
/// - topSiteState: state object for top site section
/// - textColor: text color from wallpaper configuration
private func getTopSites(
with topSitesState: TopSitesSectionState,
and textColor: TextColor?
) -> [HomepageDiffableDataSource.HomeItem] {
guard topSitesState.numberOfTilesPerRow != 0 else { return [] }
let topSites: [HomeItem] = topSitesState.topSitesData.compactMap { .topSite($0, textColor) }
let filterTopSites = topSites.prefix(Int(topSitesState.numberOfRows) * topSitesState.numberOfTilesPerRow)
return Array(filterTopSites)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ final class HomepageSectionLayoutProvider {
}

private var logger: Logger
private var windowUUID: WindowUUID
private var dimensionImplementation: TopSitesDimensionImplementation

init(logger: Logger = DefaultLogger.shared) {
init(windowUUID: WindowUUID, logger: Logger = DefaultLogger.shared) {
self.windowUUID = windowUUID
self.logger = logger
self.dimensionImplementation = TopSitesDimensionImplementation(windowUUID: windowUUID)
}

func createCompositionalLayout() -> UICollectionViewCompositionalLayout {
Expand Down Expand Up @@ -166,11 +170,11 @@ final class HomepageSectionLayoutProvider {
return section
}

func createTopSitesSectionLayout(
private func createTopSitesSectionLayout(
for traitCollection: UITraitCollection,
availableWidth: CGFloat
) -> NSCollectionLayoutSection {
let numberOfTilesPerRow = TopSitesDimensionImplementation().getNumberOfTilesPerRow(
let numberOfTilesPerRow = dimensionImplementation.getNumberOfTilesPerRow(
availableWidth: availableWidth,
leadingInset: UX.leadingInset(
traitCollection: traitCollection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ final class HomepageViewController: UIViewController,
private var dataSource: HomepageDiffableDataSource?
// TODO: FXIOS-10541 will handle scrolling for wallpaper and other scroll issues
private lazy var wallpaperView: WallpaperBackgroundView = .build { _ in }
private var layoutConfiguration = HomepageSectionLayoutProvider().createCompositionalLayout()
private var overlayManager: OverlayModeManager
private var logger: Logger
private var homepageState: HomepageState
Expand Down Expand Up @@ -252,6 +251,7 @@ final class HomepageViewController: UIViewController,
}

private func configureCollectionView() {
let layoutConfiguration = HomepageSectionLayoutProvider(windowUUID: windowUUID).createCompositionalLayout()
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layoutConfiguration)

HomepageItem.cellTypes.forEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,27 @@ import Redux

final class TopSitesAction: Action {
var topSites: [TopSiteState]?
var numberOfRows: Int?
var numberOfTilesPerRow: Int?

init(
topSites: [TopSiteState]? = nil,
numberOfRows: Int? = nil,
numberOfTilesPerRow: Int? = nil,
windowUUID: WindowUUID,
actionType: any ActionType
) {
self.topSites = topSites
self.numberOfRows = numberOfRows
self.numberOfTilesPerRow = numberOfTilesPerRow
super.init(windowUUID: windowUUID, actionType: actionType)
}
}

enum TopSitesActionType: ActionType {
case fetchTopSites
case updatedNumberOfRows
case updatedNumberOfTilesPerRow
}

enum TopSitesMiddlewareActionType: ActionType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,35 @@
// 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/

struct TopSitesDimensionImplementation {
/// Get the number of tiles (top sites) per row the user will see. This depends on the UI interface the user has.
import Common

class TopSitesDimensionImplementation {
/// The update count of number of tiles per row based on device layout
/// After updating the value, the top sites state should be updated respectively
private var numberOfTilesPerRow: Int? {
willSet {
guard newValue != numberOfTilesPerRow else { return }
store.dispatch(
TopSitesAction(
numberOfTilesPerRow: newValue,
windowUUID: self.windowUUID,
actionType: TopSitesActionType.updatedNumberOfTilesPerRow
)
)
}
}

private let windowUUID: WindowUUID
private let queue: DispatchQueueInterface
init(windowUUID: WindowUUID, queue: DispatchQueueInterface = DispatchQueue.main) {
self.windowUUID = windowUUID
self.queue = queue
}

/// Updates the number of tiles (top sites) per row the user will see. This depends on the UI interface the user has.
/// - Parameter availableWidth: available width size depending on device
/// - Parameter leadingInset: padding for top site section
/// - Parameter cellWidth: width of individual top site tiles
/// - Returns: The number of tiles per row the user will see, which is based on layout.
func getNumberOfTilesPerRow(availableWidth: CGFloat, leadingInset: CGFloat, cellWidth: CGFloat) -> Int {
var availableWidth = availableWidth - leadingInset * 2
var numberOfTiles = 0
Expand All @@ -17,6 +40,13 @@ struct TopSitesDimensionImplementation {
availableWidth = availableWidth - cellWidth - HomepageSectionLayoutProvider.UX.standardSpacing
}
let minCardsConstant = HomepageSectionLayoutProvider.UX.TopSitesConstants.minCards
return numberOfTiles < minCardsConstant ? minCardsConstant : numberOfTiles
let tilesPerRowCount = numberOfTiles < minCardsConstant ? minCardsConstant : numberOfTiles

// TODO: FXIOS-10972 - Investigate a better way to solve the crash issue that is resolved by adding this
queue.async {
self.numberOfTilesPerRow = tilesPerRowCount
}

return tilesPerRowCount
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,40 @@
import Common
import Foundation
import Redux
import Shared

/// State for the top sites section that is used in the homepage
/// The state does not only contain the top sites list, but needs to also know about the number of rows
/// and tiles per row in order to only show a specific amount of the top sites data.
struct TopSitesSectionState: StateType, Equatable {
var windowUUID: WindowUUID
var topSitesData: [TopSiteState]
var numberOfRows: Int
var numberOfTilesPerRow: Int

init(profile: Profile = AppContainer.shared.resolve(), windowUUID: WindowUUID) {
let preferredNumberOfRows = profile.prefs.intForKey(PrefsKeys.NumberOfTopSiteRows)
let defaultNumberOfRows = TopSitesRowCountSettingsController.defaultNumberOfRows
let numberOfRows = Int(preferredNumberOfRows ?? defaultNumberOfRows)

init(windowUUID: WindowUUID) {
self.init(
windowUUID: windowUUID,
topSitesData: []
topSitesData: [],
numberOfRows: numberOfRows,
numberOfTilesPerRow: 0
)
}

private init(
windowUUID: WindowUUID,
topSitesData: [TopSiteState]
topSitesData: [TopSiteState],
numberOfRows: Int,
numberOfTilesPerRow: Int
) {
self.windowUUID = windowUUID
self.topSitesData = topSitesData
self.numberOfRows = numberOfRows
self.numberOfTilesPerRow = numberOfTilesPerRow
}

static let reducer: Reducer<Self> = { state, action in
Expand All @@ -42,7 +57,35 @@ struct TopSitesSectionState: StateType, Equatable {

return TopSitesSectionState(
windowUUID: state.windowUUID,
topSitesData: sites
topSitesData: sites,
numberOfRows: state.numberOfRows,
numberOfTilesPerRow: state.numberOfTilesPerRow
)
case TopSitesActionType.updatedNumberOfRows:
guard let topSitesAction = action as? TopSitesAction,
let numberOfRows = topSitesAction.numberOfRows
else {
return defaultState(from: state)
}

return TopSitesSectionState(
windowUUID: state.windowUUID,
topSitesData: state.topSitesData,
numberOfRows: numberOfRows,
numberOfTilesPerRow: state.numberOfTilesPerRow
)
case TopSitesActionType.updatedNumberOfTilesPerRow:
guard let topSitesAction = action as? TopSitesAction,
let numberOfTilesPerRow = topSitesAction.numberOfTilesPerRow
else {
return defaultState(from: state)
}

return TopSitesSectionState(
windowUUID: state.windowUUID,
topSitesData: state.topSitesData,
numberOfRows: state.numberOfRows,
numberOfTilesPerRow: numberOfTilesPerRow
)
default:
return defaultState(from: state)
Expand All @@ -52,7 +95,9 @@ struct TopSitesSectionState: StateType, Equatable {
static func defaultState(from state: TopSitesSectionState) -> TopSitesSectionState {
return TopSitesSectionState(
windowUUID: state.windowUUID,
topSitesData: state.topSitesData
topSitesData: state.topSitesData,
numberOfRows: state.numberOfRows,
numberOfTilesPerRow: state.numberOfTilesPerRow
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ extension TopSitesSettingsViewController {
override var status: NSAttributedString {
let defaultValue = TopSitesRowCountSettingsController.defaultNumberOfRows
let numberOfRows = profile?.prefs.intForKey(PrefsKeys.NumberOfTopSiteRows) ?? defaultValue

store.dispatch(
TopSitesAction(
numberOfRows: Int(numberOfRows),
windowUUID: self.windowUUID,
actionType: TopSitesActionType.updatedNumberOfRows
)
)
return NSAttributedString(string: String(format: "%d", numberOfRows))
}

Expand Down
Loading

0 comments on commit c0a82b0

Please sign in to comment.