Skip to content

Commit

Permalink
Add FXIOS-10676 Pull refresh refactor feature flag and fix pull refre…
Browse files Browse the repository at this point in the history
…sh issues (#23326)

* Added pull to refresh refactor feature and restored old UIRefreshControl.

* Refactor TabScrollController with feature flag manager

* Refactor TabScrollController to inject Pull refresh feature

* Fix wallpaper background to clip top part

---------

Co-authored-by: Filippo <[email protected]>
  • Loading branch information
FilippoZazzeroni and Filippo authored Nov 26, 2024
1 parent ad0a142 commit f1892ff
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 56 deletions.
24 changes: 20 additions & 4 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,16 @@
8A3233FE28627446003E1C33 /* LocalDesktopFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A3233FD28627446003E1C33 /* LocalDesktopFolder.swift */; };
8A32DD5028B419B300D57C60 /* HomepageMessageCardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A32DD4F28B419B300D57C60 /* HomepageMessageCardViewModelTests.swift */; };
8A33221F27DFE318008F809E /* TopSitesDataAdaptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A33221E27DFE318008F809E /* TopSitesDataAdaptorTests.swift */; };
8A33222227DFE658008F809E /* NimbusMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A33222127DFE658008F809E /* NimbusMock.swift */; };
8A3345612BA499B7008C52AB /* disconnect-block-fingerprinting.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A3345572BA499B6008C52AB /* disconnect-block-fingerprinting.json */; };
8A3345622BA499B7008C52AB /* disconnect-block-advertising.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A3345582BA499B6008C52AB /* disconnect-block-advertising.json */; };
8A3345632BA499B7008C52AB /* disconnect-block-cookies-content.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A3345592BA499B6008C52AB /* disconnect-block-cookies-content.json */; };
8A3345642BA499B7008C52AB /* disconnect-block-analytics.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455A2BA499B6008C52AB /* disconnect-block-analytics.json */; };
8A3345652BA499B7008C52AB /* disconnect-block-cookies-advertising.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455B2BA499B7008C52AB /* disconnect-block-cookies-advertising.json */; };
8A3345662BA499B7008C52AB /* disconnect-block-content.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455C2BA499B7008C52AB /* disconnect-block-content.json */; };
8A3345672BA499B7008C52AB /* disconnect-block-cookies-analytics.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455D2BA499B7008C52AB /* disconnect-block-cookies-analytics.json */; };
8A3345682BA499B7008C52AB /* disconnect-block-social.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455E2BA499B7008C52AB /* disconnect-block-social.json */; };
8A3345692BA499B7008C52AB /* disconnect-block-cookies-social.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A33455F2BA499B7008C52AB /* disconnect-block-cookies-social.json */; };
8A33456A2BA499B7008C52AB /* disconnect-block-cryptomining.json in Resources */ = {isa = PBXBuildFile; fileRef = 8A3345602BA499B7008C52AB /* disconnect-block-cryptomining.json */; };
8A359EF32A1FD449004A5BB7 /* AdjustWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A359EF22A1FD449004A5BB7 /* AdjustWrapper.swift */; };
8A359EF62A1FE840004A5BB7 /* MockAdjustWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A359EF52A1FE840004A5BB7 /* MockAdjustWrapper.swift */; };
8A36AC2C2886F27F00CDC0AD /* MockTabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A36AC2B2886F27F00CDC0AD /* MockTabManager.swift */; };
Expand Down Expand Up @@ -7331,7 +7340,16 @@
8A3233FD28627446003E1C33 /* LocalDesktopFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalDesktopFolder.swift; sourceTree = "<group>"; };
8A32DD4F28B419B300D57C60 /* HomepageMessageCardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomepageMessageCardViewModelTests.swift; sourceTree = "<group>"; };
8A33221E27DFE318008F809E /* TopSitesDataAdaptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSitesDataAdaptorTests.swift; sourceTree = "<group>"; };
8A33222127DFE658008F809E /* NimbusMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbusMock.swift; sourceTree = "<group>"; };
8A3345572BA499B6008C52AB /* disconnect-block-fingerprinting.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-fingerprinting.json"; path = "../../../ContentBlockingLists/disconnect-block-fingerprinting.json"; sourceTree = "<group>"; };
8A3345582BA499B6008C52AB /* disconnect-block-advertising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-advertising.json"; path = "../../../ContentBlockingLists/disconnect-block-advertising.json"; sourceTree = "<group>"; };
8A3345592BA499B6008C52AB /* disconnect-block-cookies-content.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-cookies-content.json"; path = "../../../ContentBlockingLists/disconnect-block-cookies-content.json"; sourceTree = "<group>"; };
8A33455A2BA499B6008C52AB /* disconnect-block-analytics.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-analytics.json"; path = "../../../ContentBlockingLists/disconnect-block-analytics.json"; sourceTree = "<group>"; };
8A33455B2BA499B7008C52AB /* disconnect-block-cookies-advertising.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-cookies-advertising.json"; path = "../../../ContentBlockingLists/disconnect-block-cookies-advertising.json"; sourceTree = "<group>"; };
8A33455C2BA499B7008C52AB /* disconnect-block-content.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-content.json"; path = "../../../ContentBlockingLists/disconnect-block-content.json"; sourceTree = "<group>"; };
8A33455D2BA499B7008C52AB /* disconnect-block-cookies-analytics.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-cookies-analytics.json"; path = "../../../ContentBlockingLists/disconnect-block-cookies-analytics.json"; sourceTree = "<group>"; };
8A33455E2BA499B7008C52AB /* disconnect-block-social.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-social.json"; path = "../../../ContentBlockingLists/disconnect-block-social.json"; sourceTree = "<group>"; };
8A33455F2BA499B7008C52AB /* disconnect-block-cookies-social.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-cookies-social.json"; path = "../../../ContentBlockingLists/disconnect-block-cookies-social.json"; sourceTree = "<group>"; };
8A3345602BA499B7008C52AB /* disconnect-block-cryptomining.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = "disconnect-block-cryptomining.json"; path = "../../../ContentBlockingLists/disconnect-block-cryptomining.json"; sourceTree = "<group>"; };
8A359EF22A1FD449004A5BB7 /* AdjustWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdjustWrapper.swift; sourceTree = "<group>"; };
8A359EF52A1FE840004A5BB7 /* MockAdjustWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAdjustWrapper.swift; sourceTree = "<group>"; };
8A36AC2B2886F27F00CDC0AD /* MockTabManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTabManager.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -11330,7 +11348,6 @@
8A33222027DFE64C008F809E /* TestingHelperClasses */ = {
isa = PBXGroup;
children = (
8A33222127DFE658008F809E /* NimbusMock.swift */,
8A6B77CB2811C468001110D2 /* URLProtocolStub.swift */,
);
path = TestingHelperClasses;
Expand Down Expand Up @@ -17209,7 +17226,6 @@
215349062886007900FADB4D /* GleanPlumbMessageStoreTests.swift in Sources */,
2173326A29CCF901007F20C7 /* UIPanGestureRecognizerMock.swift in Sources */,
5A9A09D628B01FD500B6F51E /* MockURLBarView.swift in Sources */,
8A33222227DFE658008F809E /* NimbusMock.swift in Sources */,
0B7FC3D32CAE811F005C5CCE /* DefaultBookmarksSaverTests.swift in Sources */,
8A8629E72880B7330096DDB1 /* LegacyBookmarksPanelTests.swift in Sources */,
61A1644A2CE7BE8A001D6058 /* WallpaperMiddlewareTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Shared
import Common

// MARK: - Protocol
protocol FeatureFlaggable { }
protocol FeatureFlaggable {}

extension FeatureFlaggable {
var featureFlags: LegacyFeatureFlagsManager {
Expand Down
2 changes: 2 additions & 0 deletions firefox-ios/Client/FeatureFlags/NimbusFlaggableFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum NimbusFeatureFlagID: String, CaseIterable {
case nightMode
case passwordGenerator
case preferSwitchToOpenTabOverDuplicate
case pullToRefreshRefactor
case reduxSearchSettings
case closeRemoteTabs
case reportSiteIssue
Expand Down Expand Up @@ -119,6 +120,7 @@ struct NimbusFlaggableFeature: HasNimbusSearchBar {
.nightMode,
.passwordGenerator,
.preferSwitchToOpenTabOverDuplicate,
.pullToRefreshRefactor,
.reduxSearchSettings,
.reportSiteIssue,
.feltPrivacySimplifiedUI,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,13 @@ class BrowserViewController: UIViewController,
private lazy var privacyWindowHelper = PrivacyWindowHelper()
var keyboardBackdrop: UIView?

lazy var scrollController = TabScrollingController(windowUUID: windowUUID)
lazy var scrollController = TabScrollingController(
windowUUID: windowUUID,
isPullToRefreshRefactorEnabled: featureFlags.isFeatureEnabled(
.pullToRefreshRefactor,
checking: .buildOnly
)
)
private var keyboardState: KeyboardState?
var pendingToast: Toast? // A toast that might be waiting for BVC to appear before displaying
var downloadToast: DownloadToast? // A toast that is showing the combined download progress
Expand Down
68 changes: 45 additions & 23 deletions firefox-ios/Client/Frontend/Browser/TabScrollController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import Common

private let ToolbarBaseAnimationDuration: CGFloat = 0.2
class TabScrollingController: NSObject,
FeatureFlaggable,
SearchBarLocationProvider,
Themeable {
private struct UX {
Expand Down Expand Up @@ -39,16 +38,8 @@ class TabScrollingController: NSObject,
self.scrollView?.addGestureRecognizer(panGesture)
scrollView?.delegate = self
scrollView?.keyboardDismissMode = .onDrag
configureRefreshControl()
tab?.onLoading = { [weak self] in
guard let self else { return }
if tabIsLoading() {
pullToRefreshView?.stopObservingContentScroll()
pullToRefreshView?.removeFromSuperview()
} else {
configureRefreshControl()
}
}
configureRefreshControl(isEnabled: true)
setupTabOnLoadingCallback()
}
}

Expand All @@ -58,7 +49,6 @@ class TabScrollingController: NSObject,

weak var zoomPageBar: ZoomPageBar?
private var observedScrollViews = WeakList<UIScrollView>()
private var webViewIsLoadingObserver: NSKeyValueObservation?

var overKeyboardContainerConstraint: Constraint?
var bottomContainerConstraint: Constraint?
Expand Down Expand Up @@ -118,6 +108,7 @@ class TabScrollingController: NSObject,
$0 is PullRefreshView
}) as? PullRefreshView
}
private let isPullToRefreshRefactorEnabled: Bool
var contentOffset: CGPoint { return scrollView?.contentOffset ?? .zero }
private var scrollViewHeight: CGFloat { return scrollView?.frame.height ?? 0 }
private var topScrollHeight: CGFloat { header?.frame.height ?? 0 }
Expand Down Expand Up @@ -150,29 +141,32 @@ class TabScrollingController: NSObject,
}

deinit {
webViewIsLoadingObserver?.invalidate()
logger.log("TabScrollController deallocating", level: .info, category: .lifecycle)
observedScrollViews.forEach({ stopObserving(scrollView: $0) })
guard let themeObserver else { return }
notificationCenter.removeObserver(themeObserver)
}

init(windowUUID: WindowUUID,
isPullToRefreshRefactorEnabled: Bool,
themeManager: ThemeManager = AppContainer.shared.resolve(),
notificationCenter: NotificationProtocol = NotificationCenter.default,
logger: Logger = DefaultLogger.shared) {
self.themeManager = themeManager
self.windowUUID = windowUUID
self.notificationCenter = notificationCenter
self.logger = logger
self.isPullToRefreshRefactorEnabled = isPullToRefreshRefactorEnabled
super.init()
setupNotifications()
}

func traitCollectionDidChange() {
pullToRefreshView?.stopObservingContentScroll()
pullToRefreshView?.removeFromSuperview()
configureRefreshControl()
if isPullToRefreshRefactorEnabled {
pullToRefreshView?.stopObservingContentScroll()
pullToRefreshView?.removeFromSuperview()
configureRefreshControl(isEnabled: true)
}
}

private func setupNotifications() {
Expand All @@ -182,6 +176,21 @@ class TabScrollingController: NSObject,
object: nil)
}

private func setupTabOnLoadingCallback() {
if isPullToRefreshRefactorEnabled {
tab?.onLoading = { [weak self] in
guard let self else { return }
// If we are in the home page delete pull to refresh so it doesn't show on the background
if tabIsLoading() || (tab?.isFxHomeTab ?? false) {
pullToRefreshView?.stopObservingContentScroll()
pullToRefreshView?.removeFromSuperview()
} else {
configureRefreshControl(isEnabled: true)
}
}
}
}

@objc
private func applicationWillTerminate(_ notification: Notification) {
// Ensures that we immediately de-register KVO observations for content size changes in
Expand Down Expand Up @@ -316,7 +325,16 @@ class TabScrollingController: NSObject,

// MARK: - Private
private extension TabScrollingController {
func configureRefreshControl() {
private func configureRefreshControl(isEnabled: Bool) {
if isPullToRefreshRefactorEnabled {
configureNewRefreshControl()
} else {
scrollView?.refreshControl = isEnabled ? UIRefreshControl() : nil
scrollView?.refreshControl?.addTarget(self, action: #selector(reload), for: .valueChanged)
}
}

private func configureNewRefreshControl() {
guard let scrollView,
let webView = tab?.webView,
!scrollView.subviews.contains(where: { $0 is PullRefreshView })
Expand All @@ -326,10 +344,10 @@ private extension TabScrollingController {
}

let refresh = PullRefreshView(parentScrollView: self.scrollView,
isPotraitOrientation: UIWindow.isPortrait) {
self.reload()
isPotraitOrientation: UIWindow.isPortrait) { [weak self] in
self?.reload()
}
self.scrollView?.addSubview(refresh)
scrollView.addSubview(refresh)
refresh.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
refresh.leadingAnchor.constraint(equalTo: webView.leadingAnchor),
Expand Down Expand Up @@ -587,13 +605,17 @@ extension TabScrollingController: UIScrollViewDelegate {
}

func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
pullToRefreshView?.stopObservingContentScroll()
pullToRefreshView?.removeFromSuperview()
if isPullToRefreshRefactorEnabled {
pullToRefreshView?.stopObservingContentScroll()
pullToRefreshView?.removeFromSuperview()
} else {
configureRefreshControl(isEnabled: false)
}
self.isUserZoom = true
}

func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
configureRefreshControl()
configureRefreshControl(isEnabled: true)
self.isUserZoom = false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ class PullRefreshView: UIView,
let shrinkFactor = computeShrinkingFactor()
let progressContainerViewPadding = UX.progressViewPadding * shrinkFactor
let progressContainerViewSize = UX.progressViewAnimatedBackgroundSize * shrinkFactor

if let scrollView, scrollView.contentOffset.y != 0 {
let threshold = UX.blinkProgressViewStandardThreshold * shrinkFactor
let initialRotationAngle = -(scrollView.contentOffset.y) / threshold
progressView.transform = CGAffineTransform(rotationAngle: initialRotationAngle)
}

NSLayoutConstraint.activate([
progressContainerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -progressContainerViewPadding),
progressContainerView.centerXAnchor.constraint(equalTo: centerXAnchor),
Expand Down Expand Up @@ -133,7 +140,6 @@ class PullRefreshView: UIView,
self.progressContainerView.backgroundColor = .clear
self.progressContainerView.transform = .identity
self.progressView.transform = .identity
TelemetryWrapper.shared.recordEvent(category: .action, method: .pull, object: .reload)
self.onRefreshCallback()
})
}
Expand Down Expand Up @@ -196,6 +202,7 @@ class PullRefreshView: UIView,

func stopObservingContentScroll() {
scrollObserver?.invalidate()
scrollObserver = nil
}

func updateEasterEggForOrientationChange(isPotrait: Bool) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class LegacyHomepageViewController:
private var overlayManager: OverlayModeManager
private var userDefaults: UserDefaultsInterface
private lazy var wallpaperView: LegacyWallpaperBackgroundView = .build { _ in }
private var wallpaperViewTopConstraint: NSLayoutConstraint?
private var jumpBackInContextualHintViewController: ContextualHintViewController
private var syncTabContextualHintViewController: ContextualHintViewController
private var collectionView: UICollectionView! = nil
Expand Down Expand Up @@ -192,6 +193,11 @@ class LegacyHomepageViewController:
if UIDevice.current.userInterfaceIdiom == .pad {
reloadOnRotation(newSize: size)
}

coordinator.animate { sin_ in
let wallpaperTopConstant: CGFloat = UIWindow.keyWindow?.safeAreaInsets.top ?? self.statusBarFrame?.height ?? 0
self.wallpaperViewTopConstraint?.constant = -wallpaperTopConstant
}
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
Expand Down Expand Up @@ -277,9 +283,11 @@ class LegacyHomepageViewController:

// Constraint so wallpaper appears under the status bar
let wallpaperTopConstant: CGFloat = UIWindow.keyWindow?.safeAreaInsets.top ?? statusBarFrame?.height ?? 0
wallpaperViewTopConstraint = wallpaperView.topAnchor.constraint(equalTo: view.topAnchor,
constant: -wallpaperTopConstant)

wallpaperViewTopConstraint?.isActive = true
NSLayoutConstraint.activate([
wallpaperView.topAnchor.constraint(equalTo: view.topAnchor, constant: -wallpaperTopConstant),
wallpaperView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
wallpaperView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
wallpaperView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
Expand Down
7 changes: 7 additions & 0 deletions firefox-ios/Client/Nimbus/NimbusFeatureFlagLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ final class NimbusFeatureFlagLayer {
case .preferSwitchToOpenTabOverDuplicate:
return checkPreferSwitchToOpenTabOverDuplicate(from: nimbus)

case .pullToRefreshRefactor:
return checkPullToRefreshFeature(from: nimbus)

case .reduxSearchSettings:
return checkReduxSearchSettingsFeature(from: nimbus)

Expand Down Expand Up @@ -309,6 +312,10 @@ final class NimbusFeatureFlagLayer {
return nimbus.features.homescreenFeature.value().preferSwitchToOpenTab
}

private func checkPullToRefreshFeature(from nimbus: FxNimbus) -> Bool {
return nimbus.features.pullToRefreshRefactorFeature.value().enabled
}

private func checkAddressAutofillEditing(from nimbus: FxNimbus) -> Bool {
let config = nimbus.features.addressAutofillEdit.value()

Expand Down
Loading

0 comments on commit f1892ff

Please sign in to comment.