Skip to content

Commit

Permalink
Refactor FXIOS-10541 [Homepage Rebuild] Add scrolling logic to the ne…
Browse files Browse the repository at this point in the history
…w homepage (#23758)

Add scrolling logic to new homepage
  • Loading branch information
Cramsden authored Dec 16, 2024
1 parent 1cb2623 commit 8ac1e19
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ class BrowserCoordinator: BaseCoordinator,
return
}
self.homepageViewController = homepageController
homepageController.scrollToTop()
}

func showPrivateHomepage(overlayManager: OverlayModeManager) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import Foundation
import Common
import Redux
import Shared

final class HomepageViewController: UIViewController,
UICollectionViewDelegate,
FeatureFlaggable,
ContentContainable,
Themeable,
Notifiable,
Expand Down Expand Up @@ -46,6 +48,7 @@ final class HomepageViewController: UIViewController,
private var overlayManager: OverlayModeManager
private var logger: Logger
private var homepageState: HomepageState
private var lastContentOffsetY: CGFloat = 0

private var currentTheme: Theme {
themeManager.getCurrentTheme(for: windowUUID)
Expand Down Expand Up @@ -123,7 +126,25 @@ final class HomepageViewController: UIViewController,
wallpaperView.updateImageForOrientationChange()
}

// called when the homepage is displayed to make sure it's scrolled to top
func scrollToTop(animated: Bool = false) {
collectionView?.setContentOffset(.zero, animated: animated)
if let collectionView = collectionView {
handleScroll(collectionView, isUserInteraction: false)
}
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
handleScroll(scrollView, isUserInteraction: true)
}

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
lastContentOffsetY = scrollView.contentOffset.y
handleToolbarStateOnScroll()
}

private func handleScroll(_ scrollView: UIScrollView, isUserInteraction: Bool) {
// We only handle status bar overlay alpha if there's a wallpaper applied on the homepage
if homepageState.wallpaperState.wallpaperConfiguration.hasImage {
let theme = themeManager.getCurrentTheme(for: windowUUID)
statusBarScrollDelegate?.scrollViewDidScroll(
Expand All @@ -132,6 +153,37 @@ final class HomepageViewController: UIViewController,
theme: theme
)
}
// this action controls the address toolbar's border position, and to prevent spamming redux with actions for every
// change in content offset, we keep track of lastContentOffsetY to know if the border needs to be updated
if (lastContentOffsetY > 0 && scrollView.contentOffset.y <= 0) ||
(lastContentOffsetY <= 0 && scrollView.contentOffset.y > 0) {
lastContentOffsetY = scrollView.contentOffset.y
store.dispatch(
GeneralBrowserMiddlewareAction(
scrollOffset: scrollView.contentOffset,
windowUUID: windowUUID,
actionType: GeneralBrowserMiddlewareActionType.websiteDidScroll))
}
}

private func handleToolbarStateOnScroll() {
// TODO: FXIOS-10877 This logic will be handled by toolbar state, the homepage will just dispatch the action
let toolbarState = store.state.screenState(ToolbarState.self, for: .toolbar, window: windowUUID)

// Only dispatch action when user is in edit mode to avoid having the toolbar re-displayed
if featureFlags.isFeatureEnabled(.toolbarRefactor, checking: .buildOnly),
let toolbarState,
toolbarState.addressToolbar.isEditing {
// When the user scrolls the homepage (not overlaid on a webpage when searching) we cancel edit mode
// On a website we just dismiss the keyboard
if toolbarState.addressToolbar.url == nil {
let action = ToolbarAction(windowUUID: windowUUID, actionType: ToolbarActionType.cancelEdit)
store.dispatch(action)
} else {
let action = ToolbarAction(windowUUID: windowUUID, actionType: ToolbarActionType.hideKeyboard)
store.dispatch(action)
}
}
}

// MARK: - Redux
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@ import Common

@testable import Client

final class HomepageViewControllerTests: XCTestCase {
final class HomepageViewControllerTests: XCTestCase, StoreTestUtility {
let windowUUID: WindowUUID = .XCTestDefaultUUID
var mockNotificationCenter: MockNotificationCenter?
var mockThemeManager: MockThemeManager?
var mockStore: MockStoreForMiddleware<AppState>!

override func setUp() {
super.setUp()
DependencyHelperMock().bootstrapDependencies()
setupStore()
}

override func tearDown() {
mockNotificationCenter = nil
mockThemeManager = nil
DependencyHelperMock().reset()
resetStore()
super.tearDown()
}

Expand Down Expand Up @@ -97,6 +100,50 @@ final class HomepageViewControllerTests: XCTestCase {
XCTAssertEqual(mockStatusBarScrollDelegate.savedScrollView, scrollView)
}

func test_scrollToTop_updatesStatusBarScrollDelegate_andSetsCollectionViewOffset() {
let mockStatusBarScrollDelegate = MockStatusBarScrollDelegate()
let homepageVC = createSubject(statusBarScrollDelegate: mockStatusBarScrollDelegate)
let wallpaperConfiguration = WallpaperConfiguration(hasImage: true)
let newState = HomepageState.reducer(
HomepageState(windowUUID: .XCTestDefaultUUID),
WallpaperAction(
wallpaperConfiguration: wallpaperConfiguration,
windowUUID: .XCTestDefaultUUID,
actionType: WallpaperMiddlewareActionType.wallpaperDidInitialize
)
)

guard let collectionView = homepageVC.view.subviews.first(where: {
$0 is UICollectionView
}) as? UICollectionView else {
XCTFail()
return
}

homepageVC.newState(state: newState)
homepageVC.scrollToTop()

XCTAssertEqual(collectionView.contentOffset, .zero)
XCTAssertEqual(mockStatusBarScrollDelegate.savedScrollView, collectionView)
}

func test_scrollViewDidScroll_TriggersGeneralBrowserMiddlewareAction() throws {
let mockStatusBarScrollDelegate = MockStatusBarScrollDelegate()
let homepageVC = createSubject(statusBarScrollDelegate: mockStatusBarScrollDelegate)
let scrollView = UIScrollView()
scrollView.contentOffset.y = 10

homepageVC.scrollViewDidScroll(scrollView)

let actionCalled = try XCTUnwrap(
mockStore.dispatchedActions.first(where: {
$0 is GeneralBrowserMiddlewareAction
}) as? GeneralBrowserMiddlewareAction
)
let actionType = try XCTUnwrap(actionCalled.actionType as? GeneralBrowserMiddlewareActionType)
XCTAssertEqual(actionType, GeneralBrowserMiddlewareActionType.websiteDidScroll)
}

private func createSubject(statusBarScrollDelegate: StatusBarScrollDelegate? = nil) -> HomepageViewController {
let notificationCenter = MockNotificationCenter()
let themeManager = MockThemeManager()
Expand All @@ -113,4 +160,17 @@ final class HomepageViewControllerTests: XCTestCase {
trackForMemoryLeaks(homepageViewController)
return homepageViewController
}

func setupAppState() -> Client.AppState {
return AppState()
}

func setupStore() {
mockStore = MockStoreForMiddleware(state: setupAppState())
StoreTestUtilityHelper.setupStore(with: mockStore)
}

func resetStore() {
StoreTestUtilityHelper.resetStore()
}
}

0 comments on commit 8ac1e19

Please sign in to comment.