diff --git a/firefox-ios/Client.xcodeproj/project.pbxproj b/firefox-ios/Client.xcodeproj/project.pbxproj index 20b57d27deb1..368578867690 100644 --- a/firefox-ios/Client.xcodeproj/project.pbxproj +++ b/firefox-ios/Client.xcodeproj/project.pbxproj @@ -581,6 +581,12 @@ 602B3D6729B0E1DB0066DEF8 /* ConversionValueUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 602B3D6629B0E1DB0066DEF8 /* ConversionValueUtil.swift */; }; 60CE80C12667780D004026C7 /* CredentialListPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CE80C02667780C004026C7 /* CredentialListPresenter.swift */; }; 60D71AEC26AAF45E00355588 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D71AEB26AAF45E00355588 /* UIColorExtension.swift */; }; + 612194E32CE507CF001664BB /* WallpaperBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612194E22CE507CF001664BB /* WallpaperBackgroundView.swift */; }; + 612194E62CE50A93001664BB /* WallpaperAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612194E52CE50A93001664BB /* WallpaperAction.swift */; }; + 612194E82CE50A9B001664BB /* WallpaperState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612194E72CE50A9B001664BB /* WallpaperState.swift */; }; + 619FE8932CE6595B004F83E2 /* WallpaperMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 619FE8922CE6595B004F83E2 /* WallpaperMiddleware.swift */; }; + 61A164492CE7BE84001D6058 /* WallpaperStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A164452CE7BE1A001D6058 /* WallpaperStateTests.swift */; }; + 61A1644A2CE7BE8A001D6058 /* WallpaperMiddlewareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61A164472CE7BE3D001D6058 /* WallpaperMiddlewareTests.swift */; }; 630FE1342C7FB42500D9D6B2 /* NativeErrorPageMockModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630FE1302C7FB42500D9D6B2 /* NativeErrorPageMockModel.swift */; }; 630FE1352C7FB42500D9D6B2 /* NativeErrorPageViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 630FE1322C7FB42500D9D6B2 /* NativeErrorPageViewControllerTests.swift */; }; 631A369F2CC0A4FE0044DFEB /* NativeErrorPageMiddleware.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631A369E2CC0A4FE0044DFEB /* NativeErrorPageMiddleware.swift */; }; @@ -1581,7 +1587,7 @@ DF940A0C2A96352B00C1497D /* FakespotSettingsCardViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF940A0A2A96316D00C1497D /* FakespotSettingsCardViewModelTests.swift */; }; DFA51481275FFEE500266AA0 /* HistoryHighlightsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA51480275FFEE500266AA0 /* HistoryHighlightsManager.swift */; }; DFA51484276103A000266AA0 /* HistoryHighlightsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA514822761012D00266AA0 /* HistoryHighlightsManagerTests.swift */; }; - DFACBF7F277B5F7B003D5F41 /* WallpaperBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFACBF7E277B5F7B003D5F41 /* WallpaperBackgroundView.swift */; }; + DFACBF7F277B5F7B003D5F41 /* LegacyWallpaperBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFACBF7E277B5F7B003D5F41 /* LegacyWallpaperBackgroundView.swift */; }; DFACBF81277B916B003D5F41 /* ConfigurableGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFACBF80277B916B003D5F41 /* ConfigurableGradientView.swift */; }; DFACBF85277B9B5B003D5F41 /* TopSitesRowCountSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFACBF84277B9B5B003D5F41 /* TopSitesRowCountSettingsController.swift */; }; DFACDFAA274D489B00A94EEC /* HistoryHighlightsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFACDFA9274D489B00A94EEC /* HistoryHighlightsViewModel.swift */; }; @@ -6879,6 +6885,12 @@ 60CE80C02667780C004026C7 /* CredentialListPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialListPresenter.swift; sourceTree = ""; }; 60D71AEB26AAF45E00355588 /* UIColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; }; 60E04CE89D47E52E0A886EAD /* hi-IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "hi-IN"; path = "hi-IN.lproj/Search.strings"; sourceTree = ""; }; + 612194E22CE507CF001664BB /* WallpaperBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperBackgroundView.swift; sourceTree = ""; }; + 612194E52CE50A93001664BB /* WallpaperAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperAction.swift; sourceTree = ""; }; + 612194E72CE50A9B001664BB /* WallpaperState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperState.swift; sourceTree = ""; }; + 619FE8922CE6595B004F83E2 /* WallpaperMiddleware.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperMiddleware.swift; sourceTree = ""; }; + 61A164452CE7BE1A001D6058 /* WallpaperStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperStateTests.swift; sourceTree = ""; }; + 61A164472CE7BE3D001D6058 /* WallpaperMiddlewareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperMiddlewareTests.swift; sourceTree = ""; }; 61AE456BAD6B0041485EFF1E /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Shared.strings; sourceTree = ""; }; 61B340508D4D86B6519A165C /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = "id.lproj/Default Browser.strings"; sourceTree = ""; }; 61DA4B5AB7DA505B9C992F95 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/ClearPrivateDataConfirm.strings; sourceTree = ""; }; @@ -9029,7 +9041,7 @@ DF940A0A2A96316D00C1497D /* FakespotSettingsCardViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakespotSettingsCardViewModelTests.swift; sourceTree = ""; }; DFA51480275FFEE500266AA0 /* HistoryHighlightsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryHighlightsManager.swift; sourceTree = ""; }; DFA514822761012D00266AA0 /* HistoryHighlightsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryHighlightsManagerTests.swift; sourceTree = ""; }; - DFACBF7E277B5F7B003D5F41 /* WallpaperBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WallpaperBackgroundView.swift; sourceTree = ""; }; + DFACBF7E277B5F7B003D5F41 /* LegacyWallpaperBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyWallpaperBackgroundView.swift; sourceTree = ""; }; DFACBF80277B916B003D5F41 /* ConfigurableGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurableGradientView.swift; sourceTree = ""; }; DFACBF84277B9B5B003D5F41 /* TopSitesRowCountSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopSitesRowCountSettingsController.swift; sourceTree = ""; }; DFACDFA9274D489B00A94EEC /* HistoryHighlightsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryHighlightsViewModel.swift; sourceTree = ""; }; @@ -10905,6 +10917,34 @@ path = Generated; sourceTree = ""; }; + 612194E12CE50784001664BB /* Wallpaper */ = { + isa = PBXGroup; + children = ( + 612194E42CE50A7B001664BB /* Redux */, + 612194E22CE507CF001664BB /* WallpaperBackgroundView.swift */, + ); + path = Wallpaper; + sourceTree = ""; + }; + 612194E42CE50A7B001664BB /* Redux */ = { + isa = PBXGroup; + children = ( + 612194E52CE50A93001664BB /* WallpaperAction.swift */, + 612194E72CE50A9B001664BB /* WallpaperState.swift */, + 619FE8922CE6595B004F83E2 /* WallpaperMiddleware.swift */, + ); + path = Redux; + sourceTree = ""; + }; + 61A164442CE7BDEF001D6058 /* Wallpaper */ = { + isa = PBXGroup; + children = ( + 61A164452CE7BE1A001D6058 /* WallpaperStateTests.swift */, + 61A164472CE7BE3D001D6058 /* WallpaperMiddlewareTests.swift */, + ); + path = Wallpaper; + sourceTree = ""; + }; 630FE1312C7FB42500D9D6B2 /* Mock */ = { isa = PBXGroup; children = ( @@ -11142,6 +11182,7 @@ 8A00BD862CAB3FE500680AF9 /* HomepageViewControllerTests.swift */, 8AA0A6662CAC745300AC7EB3 /* HomepageDiffableDataSourceTests.swift */, 8A6B79992CDBCE2E003C3077 /* TopSitesManagerTests.swift */, + 61A164442CE7BDEF001D6058 /* Wallpaper */, ); path = "Homepage Rebuild"; sourceTree = ""; @@ -13565,7 +13606,7 @@ E1FF93E028A2E13600E6360E /* UI */ = { isa = PBXGroup; children = ( - DFACBF7E277B5F7B003D5F41 /* WallpaperBackgroundView.swift */, + DFACBF7E277B5F7B003D5F41 /* LegacyWallpaperBackgroundView.swift */, E1FF93E128A2E55700E6360E /* WallpaperSelectorViewController.swift */, E1FF93E328A2E74600E6360E /* WallpaperSelectorViewModel.swift */, E19B38B228A42D5D00D8C541 /* WallpaperCollectionViewCell.swift */, @@ -14297,6 +14338,7 @@ isa = PBXGroup; children = ( 8A7D08E12CAAF79F0035999C /* Homepage Rebuild */, + 612194E12CE50784001664BB /* Wallpaper */, 8A0A1BA12B22010200E8706F /* PrivateHome */, C834ACD028D3ACA900203AD1 /* Blurrable.swift */, 8AB5958628412B620090F4AE /* CustomizeHome */, @@ -16173,7 +16215,7 @@ DF529E9F2AA86FF4003C5373 /* FakespotReviewQualityCardView.swift in Sources */, 1D0BA05C24F46A0400D731B5 /* TopSitesProvider.swift in Sources */, 0BF0DB941A8545800039F300 /* URLBarView.swift in Sources */, - DFACBF7F277B5F7B003D5F41 /* WallpaperBackgroundView.swift in Sources */, + DFACBF7F277B5F7B003D5F41 /* LegacyWallpaperBackgroundView.swift in Sources */, 8A7D08E32CAAF7C30035999C /* HomepageViewController.swift in Sources */, D01017F5219CB6BD009CBB5A /* DownloadContentScript.swift in Sources */, 8A093D832A4B68940099ABA5 /* PrivacySettingsDelegate.swift in Sources */, @@ -16388,6 +16430,7 @@ 8A5D1CB02A30D740005AD35C /* SearchBarSetting.swift in Sources */, EB9854FF2422686F0040F24B /* AppDelegate+PushNotifications.swift in Sources */, DFACBF85277B9B5B003D5F41 /* TopSitesRowCountSettingsController.swift in Sources */, + 612194E62CE50A93001664BB /* WallpaperAction.swift in Sources */, 9614BF4428AD1C6700D3F7EA /* AccountSyncHandler.swift in Sources */, 282DA4731A68C1E700A406E2 /* OpenSearchParser.swift in Sources */, 8A13FA8D2AD834FA007527AB /* BackgroundTabLoader.swift in Sources */, @@ -16583,6 +16626,7 @@ E1CEC2022A28C3F100B177D5 /* LoginDetailCenteredTableViewCell.swift in Sources */, C2D71B972A384F40003DEC7A /* ThemedSubtitleTableViewCell.swift in Sources */, 8A83B7462A264FA0002FF9AC /* SettingsCoordinator.swift in Sources */, + 619FE8932CE6595B004F83E2 /* WallpaperMiddleware.swift in Sources */, A9072B801D07B34100459960 /* NoImageModeHelper.swift in Sources */, 8A97E6EA2C58487900F94793 /* AddressLocaleFeatureValidator.swift in Sources */, AB3DB0C92B596739001D32CB /* AppStartupTelemetry.swift in Sources */, @@ -16740,6 +16784,7 @@ 9636D92C27F9E50100771F5E /* GleanPlumbMessageStore.swift in Sources */, 213B67A627CE682B000542F5 /* StartAtHomeHelper.swift in Sources */, 8A8629E2288096C40096DDB1 /* BookmarksFolderCell.swift in Sources */, + 612194E82CE50A9B001664BB /* WallpaperState.swift in Sources */, DAE6DF1B29AD78DA0094BD1B /* BrowserViewController+ZoomPage.swift in Sources */, 0EC57D082CA6FCA5002E3F04 /* PasswordGeneratorHeaderView.swift in Sources */, 8AB8574A27D97CE90075C173 /* HomePanelDelegate.swift in Sources */, @@ -16902,6 +16947,7 @@ 1D558A5A2BEE7D07001EF527 /* WindowSimpleTabsCoordinator.swift in Sources */, E6327A641BF6438E008D12E0 /* DebugSettingsBundleOptions.swift in Sources */, F85C7EDD27109241004BDBA4 /* PasswordManagerOnboardingViewController.swift in Sources */, + 612194E32CE507CF001664BB /* WallpaperBackgroundView.swift in Sources */, D8FDEB57220CFE970069A582 /* UIImage+ImageEffects.m in Sources */, 0BDDB3462CA6E43A00D501DF /* BookmarksSaver.swift in Sources */, EBB89506219398E500EB91A0 /* TabContentBlocker.swift in Sources */, @@ -17152,6 +17198,7 @@ 8A5BD95A28788A3D000FE773 /* TopSitesHelperTests.swift in Sources */, C29B64832AD69C3E00F3244B /* MockQRCodeParentCoordinator.swift in Sources */, C8B41E0F29F0357300FE218A /* NimbusOnboardingFeatureLayerTests.swift in Sources */, + 61A164492CE7BE84001D6058 /* WallpaperStateTests.swift in Sources */, 8A6B799D2CDBDAE4003C3077 /* MockContileProvider.swift in Sources */, 5AF6254728A58AC100A90253 /* MockHistoryHighlightsDataAdaptor.swift in Sources */, 8A7A26E329D4ACF300EA76F1 /* SceneCoordinatorTests.swift in Sources */, @@ -17165,6 +17212,7 @@ 8A33222227DFE658008F809E /* NimbusMock.swift in Sources */, 0B7FC3D32CAE811F005C5CCE /* DefaultBookmarksSaverTests.swift in Sources */, 8A8629E72880B7330096DDB1 /* LegacyBookmarksPanelTests.swift in Sources */, + 61A1644A2CE7BE8A001D6058 /* WallpaperMiddlewareTests.swift in Sources */, C8B394362A0ED55D00700E49 /* MockOnboardingCardDelegate.swift in Sources */, 8A5604F829DF0D2600035CA3 /* BrowserCoordinatorTests.swift in Sources */, C787D8C32C1CB77900940123 /* FirefoxAccountSignInViewControllerTests.swift in Sources */, diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift index b7c557193622..1c52cc33cd70 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/HomepageViewController.swift @@ -30,6 +30,8 @@ final class HomepageViewController: UIViewController, // MARK: - Private variables private var collectionView: UICollectionView? 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 logger: Logger private var homepageState: HomepageState @@ -72,6 +74,7 @@ final class HomepageViewController: UIViewController, // MARK: - View lifecycle override func viewDidLoad() { super.viewDidLoad() + configureWallpaperView() setupLayout() configureCollectionView() configureDataSource() @@ -87,6 +90,11 @@ final class HomepageViewController: UIViewController, applyTheme() } + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + wallpaperView.updateImageForOrientationChange() + } + // MARK: - Redux func subscribeToRedux() { let action = ScreenAction( @@ -109,6 +117,7 @@ final class HomepageViewController: UIViewController, func newState(state: HomepageState) { homepageState = state + wallpaperView.wallpaperState = state.wallpaperState dataSource?.applyInitialSnapshot(state: state) } @@ -128,6 +137,28 @@ final class HomepageViewController: UIViewController, } // MARK: - Layout + var statusBarFrame: CGRect? { + guard let keyWindow = UIWindow.keyWindow else { return nil } + + return keyWindow.windowScene?.statusBarManager?.statusBarFrame + } + + func configureWallpaperView() { + view.addSubview(wallpaperView) + + // Constraint so wallpaper appears under the status bar + let wallpaperTopConstant: CGFloat = UIWindow.keyWindow?.safeAreaInsets.top ?? statusBarFrame?.height ?? 0 + + 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) + ]) + + view.sendSubviewToBack(wallpaperView) + } + private func setupLayout() { guard let collectionView else { logger.log( diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift index 15842bdbe5c7..2dcc174727c8 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/Redux/HomepageState.swift @@ -12,6 +12,7 @@ struct HomepageState: ScreenState, Equatable { var headerState: HeaderState var topSitesState: TopSitesSectionState var pocketState: PocketState + var wallpaperState: WallpaperState init(appState: AppState, uuid: WindowUUID) { guard let homepageState = store.state.screenState( @@ -27,7 +28,8 @@ struct HomepageState: ScreenState, Equatable { windowUUID: homepageState.windowUUID, headerState: homepageState.headerState, topSitesState: homepageState.topSitesState, - pocketState: homepageState.pocketState + pocketState: homepageState.pocketState, + wallpaperState: homepageState.wallpaperState ) } @@ -36,7 +38,8 @@ struct HomepageState: ScreenState, Equatable { windowUUID: windowUUID, headerState: HeaderState(windowUUID: windowUUID), topSitesState: TopSitesSectionState(windowUUID: windowUUID), - pocketState: PocketState(windowUUID: windowUUID) + pocketState: PocketState(windowUUID: windowUUID), + wallpaperState: WallpaperState(windowUUID: windowUUID) ) } @@ -44,12 +47,14 @@ struct HomepageState: ScreenState, Equatable { windowUUID: WindowUUID, headerState: HeaderState, topSitesState: TopSitesSectionState, - pocketState: PocketState + pocketState: PocketState, + wallpaperState: WallpaperState ) { self.windowUUID = windowUUID self.headerState = headerState self.topSitesState = topSitesState self.pocketState = pocketState + self.wallpaperState = wallpaperState } static let reducer: Reducer = { state, action in @@ -64,7 +69,8 @@ struct HomepageState: ScreenState, Equatable { windowUUID: state.windowUUID, headerState: HeaderState.reducer(state.headerState, action), topSitesState: TopSitesSectionState.reducer(state.topSitesState, action), - pocketState: PocketState.reducer(state.pocketState, action) + pocketState: PocketState.reducer(state.pocketState, action), + wallpaperState: WallpaperState.reducer(state.wallpaperState, action) ) default: return defaultState(from: state, action: action) @@ -75,18 +81,21 @@ struct HomepageState: ScreenState, Equatable { var headerState = state.headerState var pocketState = state.pocketState var topSitesState = state.topSitesState + var wallpaperState = state.wallpaperState if let action { headerState = HeaderState.reducer(state.headerState, action) pocketState = PocketState.reducer(state.pocketState, action) topSitesState = TopSitesSectionState.reducer(state.topSitesState, action) + wallpaperState = WallpaperState.reducer(state.wallpaperState, action) } return HomepageState( windowUUID: state.windowUUID, headerState: headerState, topSitesState: topSitesState, - pocketState: pocketState + pocketState: pocketState, + wallpaperState: wallpaperState ) } diff --git a/firefox-ios/Client/Frontend/Home/LegacyHomepageViewController.swift b/firefox-ios/Client/Frontend/Home/LegacyHomepageViewController.swift index dc29bf97097b..aae0526d7f0f 100644 --- a/firefox-ios/Client/Frontend/Home/LegacyHomepageViewController.swift +++ b/firefox-ios/Client/Frontend/Home/LegacyHomepageViewController.swift @@ -39,7 +39,7 @@ class LegacyHomepageViewController: private var tabManager: TabManager private var overlayManager: OverlayModeManager private var userDefaults: UserDefaultsInterface - private lazy var wallpaperView: WallpaperBackgroundView = .build { _ in } + private lazy var wallpaperView: LegacyWallpaperBackgroundView = .build { _ in } private var jumpBackInContextualHintViewController: ContextualHintViewController private var syncTabContextualHintViewController: ContextualHintViewController private var collectionView: UICollectionView! = nil diff --git a/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperAction.swift b/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperAction.swift new file mode 100644 index 000000000000..f73f6600c975 --- /dev/null +++ b/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperAction.swift @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import Foundation +import Redux +import Common + +final class WallpaperAction: Action { + let wallpaperConfiguration: WallpaperConfiguration + + init(wallpaperConfiguration: WallpaperConfiguration, + windowUUID: WindowUUID, + actionType: any ActionType + ) { + self.wallpaperConfiguration = wallpaperConfiguration + super.init(windowUUID: windowUUID, actionType: actionType) + } +} + +enum WallpaperActionType: ActionType { + case wallpaperSelected +} + +enum WallpaperMiddlewareActionType: ActionType { + case wallpaperDidInitialize + case wallpaperDidChange +} diff --git a/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperMiddleware.swift b/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperMiddleware.swift new file mode 100644 index 000000000000..edd840cced17 --- /dev/null +++ b/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperMiddleware.swift @@ -0,0 +1,60 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import Common +import Redux + +class WallpaperMiddleware { + private let wallpaperManager: WallpaperManagerInterface + private var wallpaperConfiguration: WallpaperConfiguration { + let currentWallpaper = wallpaperManager.currentWallpaper + return WallpaperConfiguration( + landscapeImage: currentWallpaper.landscape, + portraitImage: currentWallpaper.portrait, + textColor: currentWallpaper.textColor, + cardColor: currentWallpaper.cardColor, + logoTextColor: currentWallpaper.logoTextColor + ) + } + + init(wallpaperManager: WallpaperManagerInterface = WallpaperManager()) { + self.wallpaperManager = wallpaperManager + } + + lazy var wallpaperProvider: Middleware = { state, action in + if let action = action as? HomepageAction { + self.resolveHomepageAction(action: action, state: state) + } else if let action = action as? WallpaperAction { + self.resolveWallpaperAction(action: action, state: state) + } + } + + private func resolveHomepageAction(action: HomepageAction, state: AppState) { + switch action.actionType { + case HomepageActionType.initialize: + let action = WallpaperAction( + wallpaperConfiguration: wallpaperConfiguration, + windowUUID: action.windowUUID, + actionType: WallpaperMiddlewareActionType.wallpaperDidInitialize + ) + store.dispatch(action) + default: + break + } + } + + private func resolveWallpaperAction(action: WallpaperAction, state: AppState) { + switch action.actionType { + case WallpaperActionType.wallpaperSelected: + let action = WallpaperAction( + wallpaperConfiguration: wallpaperConfiguration, + windowUUID: action.windowUUID, + actionType: WallpaperMiddlewareActionType.wallpaperDidChange + ) + store.dispatch(action) + default: + break + } + } +} diff --git a/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperState.swift b/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperState.swift new file mode 100644 index 000000000000..97a146dede3a --- /dev/null +++ b/firefox-ios/Client/Frontend/Home/Wallpaper/Redux/WallpaperState.swift @@ -0,0 +1,65 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ +import Redux +import Common + +struct WallpaperState: ScreenState, Equatable { + var windowUUID: WindowUUID + let wallpaperConfiguration: WallpaperConfiguration + + init(windowUUID: WindowUUID, wallpaperConfiguration: WallpaperConfiguration = WallpaperConfiguration()) { + self.windowUUID = windowUUID + self.wallpaperConfiguration = wallpaperConfiguration + } + + static let reducer: Reducer = { state, action in + guard action.windowUUID == .unavailable || action.windowUUID == state.windowUUID else { + return defaultState(from: state) + } + + switch action.actionType { + case WallpaperMiddlewareActionType.wallpaperDidInitialize, + WallpaperMiddlewareActionType.wallpaperDidChange: + return handleWallpaperAction(action: action, state: state) + default: + return defaultState(from: state) + } + } + + private static func handleWallpaperAction(action: Action, state: WallpaperState) -> WallpaperState { + guard let wallpaperAction = action as? WallpaperAction else { return defaultState(from: state) } + return WallpaperState( + windowUUID: state.windowUUID, + wallpaperConfiguration: wallpaperAction.wallpaperConfiguration + ) + } + + static func defaultState(from state: WallpaperState) -> WallpaperState { + return WallpaperState( + windowUUID: state.windowUUID, wallpaperConfiguration: state.wallpaperConfiguration + ) + } +} + +struct WallpaperConfiguration: Equatable { + var landscapeImage: UIImage? + var portraitImage: UIImage? + var textColor: UIColor? + var cardColor: UIColor? + var logoTextColor: UIColor? + + init( + landscapeImage: UIImage? = nil, + portraitImage: UIImage? = nil, + textColor: UIColor? = nil, + cardColor: UIColor? = nil, + logoTextColor: UIColor? = nil + ) { + self.landscapeImage = landscapeImage + self.portraitImage = portraitImage + self.textColor = textColor + self.cardColor = cardColor + self.logoTextColor = logoTextColor + } + } diff --git a/firefox-ios/Client/Frontend/Home/Wallpaper/WallpaperBackgroundView.swift b/firefox-ios/Client/Frontend/Home/Wallpaper/WallpaperBackgroundView.swift new file mode 100644 index 000000000000..72f9a4ddcf43 --- /dev/null +++ b/firefox-ios/Client/Frontend/Home/Wallpaper/WallpaperBackgroundView.swift @@ -0,0 +1,68 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import UIKit +import Common +import Redux + +class WallpaperBackgroundView: UIView { + // MARK: - UI Elements + private lazy var pictureView: UIImageView = .build { imageView in + imageView.image = nil + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + } + + // MARK: - Variables + var wallpaperState: WallpaperState? { + didSet { + updateImageToCurrentWallpaper() + } + } + + // MARK: - Initializers & Setup + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupView() { + backgroundColor = .clear + addSubview(pictureView) + + NSLayoutConstraint.activate([ + pictureView.leadingAnchor.constraint(equalTo: leadingAnchor), + pictureView.topAnchor.constraint(equalTo: topAnchor), + pictureView.bottomAnchor.constraint(equalTo: bottomAnchor), + pictureView.trailingAnchor.constraint(equalTo: trailingAnchor), + ]) + } + + // MARK: - Methods + public func updateImageForOrientationChange() { + updateImageToCurrentWallpaper() + } + + private func updateImageToCurrentWallpaper() { + guard let state = wallpaperState else { return } + ensureMainThread { + let currentWallpaperImage = self.currentWallpaperImage(from: state) + UIView.animate(withDuration: 0.3) { + self.pictureView.image = currentWallpaperImage + } + } + } + + private func currentWallpaperImage(from wallpaperState: WallpaperState) -> UIImage? { + let isLandscape = UIDevice.current.orientation.isLandscape + return isLandscape ? + wallpaperState.wallpaperConfiguration.landscapeImage : + wallpaperState.wallpaperConfiguration.landscapeImage + } +} diff --git a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Interface/WallpaperManager.swift b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Interface/WallpaperManager.swift index 6f326129eeea..e7352fc91ae9 100644 --- a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Interface/WallpaperManager.swift +++ b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Interface/WallpaperManager.swift @@ -199,10 +199,7 @@ class WallpaperManager: WallpaperManagerInterface, FeatureFlaggable { } private func addDefaultWallpaper(to availableCollections: [WallpaperCollection]) -> [WallpaperCollection] { - let defaultWallpaper = [Wallpaper(id: "fxDefault", - textColor: nil, - cardColor: nil, - logoTextColor: nil)] + let defaultWallpaper = [Wallpaper.defaultWallpaper] if availableCollections.isEmpty { return [WallpaperCollection(id: "classic-firefox", diff --git a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Models/Wallpaper.swift b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Models/Wallpaper.swift index c1909114897f..cc9028914542 100644 --- a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Models/Wallpaper.swift +++ b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Models/Wallpaper.swift @@ -49,12 +49,19 @@ struct Wallpaper: Equatable { var thumbnailID: String { return "\(id)\(fileId.thumbnail)" } var portraitID: String { return "\(id)\(deviceVersionID)\(fileId.portrait)" } var landscapeID: String { return "\(id)\(deviceVersionID)\(fileId.landscape)" } - private var deviceVersionID: String { - return UIDevice.current.userInterfaceIdiom == .pad ? fileId.iPad : fileId.iPhone + + // TODO: This is actually just a nil wallpaper. Make this clearer in FXIOS-10633 + static var defaultWallpaper: Wallpaper { + return Wallpaper( + id: Wallpaper.defaultWallpaperName, + textColor: nil, + cardColor: nil, + logoTextColor: nil + ) } var type: WallpaperType { - return id == "fxDefault" ? .defaultWallpaper : .other + return id == Wallpaper.defaultWallpaperName ? .defaultWallpaper : .other } var needsToFetchResources: Bool { @@ -74,6 +81,11 @@ struct Wallpaper: Equatable { return fetchResourceFor(imageType: .landscape) } + private static var defaultWallpaperName = "fxDefault" + private var deviceVersionID: String { + return UIDevice.current.userInterfaceIdiom == .pad ? fileId.iPad : fileId.iPhone + } + // MARK: - Helper functions private func fetchResourceFor(imageType: ImageTypeID) -> UIImage? { // If it's a default (empty) wallpaper diff --git a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/UI/WallpaperBackgroundView.swift b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/UI/LegacyWallpaperBackgroundView.swift similarity index 96% rename from firefox-ios/Client/Frontend/Home/Wallpapers/v1/UI/WallpaperBackgroundView.swift rename to firefox-ios/Client/Frontend/Home/Wallpapers/v1/UI/LegacyWallpaperBackgroundView.swift index 51a19bb640f9..fd0a4e470f19 100644 --- a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/UI/WallpaperBackgroundView.swift +++ b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/UI/LegacyWallpaperBackgroundView.swift @@ -5,7 +5,7 @@ import UIKit import Common -class WallpaperBackgroundView: UIView { +class LegacyWallpaperBackgroundView: UIView { // MARK: - UI Elements private lazy var pictureView: UIImageView = .build { imageView in imageView.image = nil @@ -69,7 +69,7 @@ class WallpaperBackgroundView: UIView { } // MARK: - Notifiable -extension WallpaperBackgroundView: Notifiable { +extension LegacyWallpaperBackgroundView: Notifiable { func handleNotifications(_ notification: Notification) { switch notification.name { case .WallpaperDidChange: updateImageToCurrentWallpaper() diff --git a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Utilities/WallpaperStorageUtility.swift b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Utilities/WallpaperStorageUtility.swift index 7d31bed27617..047b364a4718 100644 --- a/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Utilities/WallpaperStorageUtility.swift +++ b/firefox-ios/Client/Frontend/Home/Wallpapers/v1/Utilities/WallpaperStorageUtility.swift @@ -97,10 +97,7 @@ struct WallpaperStorageUtility: WallpaperMetadataCodableProtocol { } } - return Wallpaper(id: "fxDefault", - textColor: nil, - cardColor: nil, - logoTextColor: nil) + return Wallpaper.defaultWallpaper } public func fetchImageNamed(_ name: String) throws -> UIImage? { diff --git a/firefox-ios/Client/Redux/GlobalState/AppState.swift b/firefox-ios/Client/Redux/GlobalState/AppState.swift index fad9aeb87c10..7f5e3b76158e 100644 --- a/firefox-ios/Client/Redux/GlobalState/AppState.swift +++ b/firefox-ios/Client/Redux/GlobalState/AppState.swift @@ -74,7 +74,8 @@ let middlewares = [ TrackingProtectionMiddleware().trackingProtectionProvider, PasswordGeneratorMiddleware().passwordGeneratorProvider, PocketMiddleware().pocketSectionProvider, - NativeErrorPageMiddleware().nativeErrorPageProvider + NativeErrorPageMiddleware().nativeErrorPageProvider, + WallpaperMiddleware().wallpaperProvider ] // In order for us to mock and test the middlewares easier, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Wallpaper/WallpaperMiddlewareTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Wallpaper/WallpaperMiddlewareTests.swift new file mode 100644 index 000000000000..ed21d6915307 --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Wallpaper/WallpaperMiddlewareTests.swift @@ -0,0 +1,74 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import Redux +import XCTest + +@testable import Client + +class WallpaperMiddlewareTests: XCTestCase, StoreTestUtility { + var mockStore: MockStoreForMiddleware! + let wallpaperManager = WallpaperManagerMock() + var appState: AppState! + + override func setUp() { + super.setUp() + DependencyHelperMock().bootstrapDependencies() + + setupStore() + } + + override func tearDown() { + resetStore() + super.tearDown() + } + + func test_hompageAction_returnsWallpaperManagerWallpaper() throws { + let subject = WallpaperMiddleware(wallpaperManager: wallpaperManager) + let action = HomepageAction(windowUUID: .XCTestDefaultUUID, actionType: HomepageActionType.initialize) + + subject.wallpaperProvider(appState, action) + + let actionCalled = try XCTUnwrap(mockStore.dispatchedActions.first as? WallpaperAction) + let actionType = try XCTUnwrap(actionCalled.actionType as? WallpaperMiddlewareActionType) + + XCTAssertEqual(mockStore.dispatchedActions.count, 1) + XCTAssertEqual(actionType, WallpaperMiddlewareActionType.wallpaperDidInitialize) + XCTAssertEqual(actionCalled.wallpaperConfiguration.cardColor, .purple) + } + + func test_wallpaperAction_returnsWallpaperManagerWallpaper() throws { + let subject = WallpaperMiddleware(wallpaperManager: wallpaperManager) + let testWallpaper = WallpaperConfiguration() + let action = WallpaperAction( + wallpaperConfiguration: testWallpaper, + windowUUID: .XCTestDefaultUUID, + actionType: WallpaperActionType.wallpaperSelected + ) + + subject.wallpaperProvider(appState, action) + + let actionCalled = try XCTUnwrap(mockStore.dispatchedActions.first as? WallpaperAction) + let actionType = try XCTUnwrap(actionCalled.actionType as? WallpaperMiddlewareActionType) + + XCTAssertEqual(mockStore.dispatchedActions.count, 1) + XCTAssertEqual(actionType, WallpaperMiddlewareActionType.wallpaperDidChange) + // TODO: FXIOS-10522 will make this test more meaningful by actually configuring the new current wallpaper + XCTAssertEqual(actionCalled.wallpaperConfiguration.cardColor, .purple) + } + + func setupAppState() -> Client.AppState { + appState = AppState() + return appState + } + + func setupStore() { + mockStore = MockStoreForMiddleware(state: setupAppState()) + StoreTestUtilityHelper.setupStore(with: mockStore) + } + + func resetStore() { + StoreTestUtilityHelper.resetStore() + } +} diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Wallpaper/WallpaperStateTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Wallpaper/WallpaperStateTests.swift new file mode 100644 index 000000000000..82e43e5d5926 --- /dev/null +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/Wallpaper/WallpaperStateTests.swift @@ -0,0 +1,90 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// 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/ + +import Redux +import XCTest + +@testable import Client + +final class WallpaperStateTests: XCTestCase { + let image = UIImage.templateImageNamed(ImageIdentifiers.logo) + + func test_initialState_returnsExpectedState() { + let initialState = createSubject() + + XCTAssertEqual(initialState.windowUUID, .XCTestDefaultUUID) + XCTAssertNil(initialState.wallpaperConfiguration.landscapeImage) + XCTAssertNil(initialState.wallpaperConfiguration.portraitImage) + XCTAssertNil(initialState.wallpaperConfiguration.textColor) + XCTAssertNil(initialState.wallpaperConfiguration.cardColor) + XCTAssertNil(initialState.wallpaperConfiguration.logoTextColor) + } + + func test_wallpaperDidInitialize_returnsExpectedState() { + let initialState = createSubject() + let reducer = headerReducer() + + let testWallpaper = WallpaperConfiguration( + landscapeImage: image, + portraitImage: image, + textColor: .black, + cardColor: .black, + logoTextColor: .black + ) + + let newState = reducer( + initialState, + WallpaperAction( + wallpaperConfiguration: testWallpaper, + windowUUID: .XCTestDefaultUUID, + actionType: WallpaperMiddlewareActionType.wallpaperDidInitialize + ) + ) + + XCTAssertEqual(newState.windowUUID, .XCTestDefaultUUID) + XCTAssertEqual(newState.wallpaperConfiguration.landscapeImage, image) + XCTAssertEqual(newState.wallpaperConfiguration.portraitImage, image) + XCTAssertEqual(newState.wallpaperConfiguration.textColor, .black) + XCTAssertEqual(newState.wallpaperConfiguration.cardColor, .black) + XCTAssertEqual(newState.wallpaperConfiguration.logoTextColor, .black) + } + + func test_wallpaperDidChange_returnsExpectedState() { + let initialState = createSubject() + let reducer = headerReducer() + + let testWallpaper = WallpaperConfiguration( + landscapeImage: image, + portraitImage: image, + textColor: .black, + cardColor: .black, + logoTextColor: .black + ) + + let newState = reducer( + initialState, + WallpaperAction( + wallpaperConfiguration: testWallpaper, + windowUUID: .XCTestDefaultUUID, + actionType: WallpaperMiddlewareActionType.wallpaperDidChange + ) + ) + + XCTAssertEqual(newState.windowUUID, .XCTestDefaultUUID) + XCTAssertEqual(newState.wallpaperConfiguration.landscapeImage, image) + XCTAssertEqual(newState.wallpaperConfiguration.portraitImage, image) + XCTAssertEqual(newState.wallpaperConfiguration.textColor, .black) + XCTAssertEqual(newState.wallpaperConfiguration.cardColor, .black) + XCTAssertEqual(newState.wallpaperConfiguration.logoTextColor, .black) + } + + // MARK: - Private + private func createSubject() -> WallpaperState { + return WallpaperState(windowUUID: .XCTestDefaultUUID) + } + + private func headerReducer() -> Reducer { + return WallpaperState.reducer + } +} diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Utils/StoreTestUtility.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Utils/StoreTestUtility.swift index 024d3d8b74f4..cff940f0a874 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Utils/StoreTestUtility.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Utils/StoreTestUtility.swift @@ -5,6 +5,7 @@ import Foundation import Redux @testable import Client +import XCTest protocol StoreTestUtility { func setupAppState() -> AppState