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 support for background customization in HTML New Tab Page #3711

Merged
merged 77 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 72 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
91738a9
Add NewTabPageNextStepsCardsClient
ayoy Nov 29, 2024
4f1af62
Wrap cards in NextStepsData struct
ayoy Nov 30, 2024
067c72d
Remove Next Steps Cards from HTML NTP customize menu
ayoy Dec 1, 2024
cfef8b0
Implement willDisplayCardsPublisher and send a pixel when addToDock i…
ayoy Dec 2, 2024
bde24fb
Add NewTabPageNextStepsCardsClientTests with basic tests
ayoy Dec 2, 2024
bcefaa5
Add tests for willDisplayCardsPublisher
ayoy Dec 3, 2024
af1b485
Merge branch 'main' into dominik/next-steps
ayoy Dec 3, 2024
bd67285
Fix ContinueSetUpCardsTests
ayoy Dec 3, 2024
023ea19
Fix NewTabPageConfigurationClientTests
ayoy Dec 3, 2024
3dd6e23
Hide Next Steps customization option from Appearance settings when HT…
ayoy Dec 3, 2024
1c207c6
Add newTabPageWebViewDidAppear notification
ayoy Dec 3, 2024
12f7a08
Merge branch 'main' into dominik/next-steps
ayoy Dec 4, 2024
1e5eafe
Fix hiding default browser card after actioning the system dialog
ayoy Dec 4, 2024
a30c8ff
Add NewTabPage and WebKitExtensions local packages
ayoy Dec 4, 2024
23cb5b4
Move Next Steps cards
ayoy Dec 4, 2024
1b21b41
Move some tests
ayoy Dec 4, 2024
28747cd
Move some tests to the package
ayoy Dec 4, 2024
3eda43a
Move Privacy Stats
ayoy Dec 5, 2024
c369a51
Merge branch 'main' into dominik/htmlntp-package
ayoy Dec 5, 2024
772d49f
Re-add migrating recently visited view expanded state
ayoy Dec 5, 2024
f6394b2
Move Favorites
ayoy Dec 5, 2024
b1cac9b
Re-add migrating favorites view expanded state
ayoy Dec 5, 2024
7bb29be
Move favorites tests
ayoy Dec 5, 2024
f7b854b
Move RMF
ayoy Dec 5, 2024
76f176e
Update project file
ayoy Dec 5, 2024
7726033
Merge branch 'main' into dominik/htmlntp-package
ayoy Dec 5, 2024
bc303e0
Fix SwiftLint violations and compilation failures
ayoy Dec 5, 2024
ad9850d
Fix testThatGetDataReturnsFavoritesFromTheModel
ayoy Dec 5, 2024
1d82c3b
Adjust symbols visibility
ayoy Dec 5, 2024
8437413
Add initial version of the customizer
ayoy Dec 5, 2024
fc4db81
Fix config schema and image URL handling
ayoy Dec 5, 2024
8692de5
Implement setting custom backgrounds
ayoy Dec 6, 2024
96e694a
Implement theme changing and deleting images
ayoy Dec 6, 2024
808fb6d
Merge branch 'main' into dominik/customizer
ayoy Dec 9, 2024
e019142
Merge branch 'main' into dominik/customizer
ayoy Dec 9, 2024
d7cf983
Implement handling uploading images
ayoy Dec 9, 2024
9bd5ca8
Merge branch 'main' into dominik/customizer
ayoy Dec 11, 2024
258db7a
Update a comment in DuckURLSchemeHandler
ayoy Dec 12, 2024
ce783ef
Add NewTabPageDataModel enum to gather all data models
ayoy Dec 12, 2024
dc6977a
Move Configuration models to NewTabPageDataModel
ayoy Dec 12, 2024
ac337c7
Merge branch 'main' into dominik/customizer
ayoy Dec 12, 2024
07457d0
Fix bad merge
ayoy Dec 12, 2024
91486bd
Move Favorites models to NewTabPageDataModel
ayoy Dec 12, 2024
7628b08
Move NextStepsCards models to NewTabPageDataModel
ayoy Dec 12, 2024
9221451
Move PrivacyStats models to NewTabPageDataModel
ayoy Dec 12, 2024
14ea76d
Move RMF models to NewTabPageDataModel
ayoy Dec 12, 2024
9d3b8c0
Update unit tests code
ayoy Dec 12, 2024
ac5bdda
Add tests for NewTabPageCustomBackgroundClient
ayoy Dec 12, 2024
f9e35ea
Add Background.Kind enum
ayoy Dec 12, 2024
ea5769b
Pass last picked color to the NTP
ayoy Dec 12, 2024
6e3303e
Actually send the color to NTP
ayoy Dec 13, 2024
40cc65f
Update maximumNumberOfUserImages to 8
ayoy Dec 18, 2024
74f37a4
Add link opener to NewTabPageConfigurationClient
ayoy Dec 18, 2024
2a2bb22
Add NewTabPageCustomizerDisplayer
ayoy Dec 18, 2024
95684d9
Merge branch 'main' into dominik/customizer
ayoy Jan 6, 2025
ead3f06
Update tests
ayoy Jan 6, 2025
92289a1
Add gradient02.01 gradient (that needs to go between gradient02 and g…
ayoy Jan 7, 2025
aef08a0
Update BSK ref for code review
ayoy Jan 7, 2025
d74d93c
Merge branch 'main' into dominik/customizer
ayoy Jan 7, 2025
edd899f
Update BSK ref
ayoy Jan 7, 2025
43acfe2
Update BSK ref
ayoy Jan 7, 2025
a408caa
Update C-S-S to 7.1.0
ayoy Jan 7, 2025
abd1314
Update gradient 02.01 asset
ayoy Jan 7, 2025
9a45607
Add Freemium PIR banner to HTML New Tab Page (#3706)
ayoy Jan 7, 2025
f351ea9
Merge branch 'dominik/customizer-final' into dominik/customizer
ayoy Jan 7, 2025
f208505
Merge branch 'main' into dominik/customizer
ayoy Jan 7, 2025
854931c
Refactor unit tests to extract MessageHelper into a separate file
ayoy Jan 8, 2025
3c736c2
Update documentation for DuckURLSchemeHandler for user background images
ayoy Jan 8, 2025
0598f79
Add some tests for NewTabPageCustomizationProvider
ayoy Jan 8, 2025
1cdee80
Add remaining tests for NewTabPageCustomizationProvider
ayoy Jan 8, 2025
2d39b28
Stabilize testThatUserImagesPublisherPublishesEvents
ayoy Jan 8, 2025
0c72d99
Add documentation for NewTabPageCustomizerOpener
ayoy Jan 8, 2025
c1924dd
Revert changes to NewTabPageWebViewModel
ayoy Jan 8, 2025
90a0607
Remove MainActor requirement from FreemiumDBPPromotionViewCoordinator…
ayoy Jan 8, 2025
5d173a3
Always call proceedAction on Main Actor
ayoy Jan 8, 2025
6090085
Display native NTP in Fire Window
ayoy Jan 9, 2025
813dd9a
Merge branch 'main' into dominik/customizer
ayoy Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "GradientTone02+01.jpg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
samsymons marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import Foundation

extension PromotionViewModel {
static func freemiumDBPPromotion(proceedAction: @escaping () -> Void,
static func freemiumDBPPromotion(proceedAction: @escaping () async -> Void,
closeAction: @escaping () -> Void) -> PromotionViewModel {

let title = UserText.homePagePromotionFreemiumDBPTitle
Expand All @@ -41,7 +41,7 @@ extension PromotionViewModel {

static func freemiumDBPPromotionScanEngagementResults(resultCount: Int,
brokerCount: Int,
proceedAction: @escaping () -> Void,
proceedAction: @escaping () async -> Void,
closeAction: @escaping () -> Void) -> PromotionViewModel {

var description = ""
Expand All @@ -65,7 +65,7 @@ extension PromotionViewModel {
closeAction: closeAction)
}

static func freemiumDBPPromotionScanEngagementNoResults(proceedAction: @escaping () -> Void,
static func freemiumDBPPromotionScanEngagementNoResults(proceedAction: @escaping () async -> Void,
closeAction: @escaping () -> Void) -> PromotionViewModel {

let description = UserText.homePagePromotionFreemiumDBPPostScanEngagementNoResultsDescription
Expand Down
30 changes: 22 additions & 8 deletions DuckDuckGo/Freemium/DBP/FreemiumDBPPromotionViewCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,14 @@ import Common

/// Default implementation of `FreemiumDBPPromotionViewCoordinating`, responsible for managing
/// the visibility of the promotion and responding to user interactions with the promotion view.
@MainActor
final class FreemiumDBPPromotionViewCoordinator: ObservableObject {

/// Published property that determines whether the promotion is visible on the home page.
@Published var isHomePagePromotionVisible: Bool = false

/// The view model representing the promotion, which updates based on the user's state. Returns `nil` if the feature is not enabled
var viewModel: PromotionViewModel? {
guard freemiumDBPFeature.isAvailable else { return nil }
return createViewModel()
}
@Published
private(set) var viewModel: PromotionViewModel?

/// Stores whether the user has dismissed the home page promotion.
private var didDismissHomePagePromotion: Bool {
Expand Down Expand Up @@ -89,13 +86,14 @@ final class FreemiumDBPPromotionViewCoordinator: ObservableObject {
setInitialPromotionVisibilityState()
subscribeToFeatureAvailabilityUpdates()
observeFreemiumDBPNotifications()
setUpViewModelRefreshing()
}
}

private extension FreemiumDBPPromotionViewCoordinator {

/// Action to be executed when the user proceeds with the promotion (e.g opens DBP)
var proceedAction: () -> Void {
var proceedAction: () async -> Void {
{ [weak self] in
guard let self else { return }

Expand All @@ -107,7 +105,7 @@ private extension FreemiumDBPPromotionViewCoordinator {
self.freemiumDBPExperimentPixelHandler.fire(FreemiumDBPExperimentPixel.newTabScanClick)
})

showFreemiumDBP()
await showFreemiumDBP()
dismissHomePagePromotion()
}
}
Expand All @@ -130,6 +128,7 @@ private extension FreemiumDBPPromotionViewCoordinator {
}

/// Shows the Freemium DBP user interface via the presenter.
@MainActor
func showFreemiumDBP() {
freemiumDBPPresenter.showFreemiumDBPAndSetActivated(windowControllerManager: WindowControllersManager.shared)
}
Expand All @@ -148,7 +147,10 @@ private extension FreemiumDBPPromotionViewCoordinator {
/// Creates the view model for the promotion, updating based on the user's scan results.
///
/// - Returns: The `PromotionViewModel` that represents the current state of the promotion.
func createViewModel() -> PromotionViewModel {
func createViewModel() -> PromotionViewModel? {
guard freemiumDBPFeature.isAvailable, isHomePagePromotionVisible else {
return nil
}

if let results = freemiumDBPUserStateManager.firstScanResults {
if results.matchesCount > 0 {
Expand All @@ -172,12 +174,24 @@ private extension FreemiumDBPPromotionViewCoordinator {
}
}

/// This method defines the entry point to updating `viewModel` which is every change to `isHomePagePromotionVisible`.
func setUpViewModelRefreshing() {
$isHomePagePromotionVisible.dropFirst().asVoid()
.receive(on: DispatchQueue.main)
.sink { [weak self] in
self?.viewModel = self?.createViewModel()
}
.store(in: &cancellables)
}

/// Subscribes to feature availability updates from the `freemiumDBPFeature`'s availability publisher.
///
/// This method listens to the `isAvailablePublisher` of the `freemiumDBPFeature`, which publishes
/// changes to the feature's availability. It performs the following actions when an update is received:
func subscribeToFeatureAvailabilityUpdates() {
freemiumDBPFeature.isAvailablePublisher
.prepend(freemiumDBPFeature.isAvailable)
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] isAvailable in
guard let self else { return }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum GradientBackground: String, Equatable, Identifiable, CaseIterable, ColorSch

case gradient01
case gradient02
case gradient0201 = "gradient02.01"
samsymons marked this conversation as resolved.
Show resolved Hide resolved
case gradient03
case gradient04
case gradient05
Expand All @@ -46,6 +47,8 @@ enum GradientBackground: String, Equatable, Identifiable, CaseIterable, ColorSch
Gradient01()
case .gradient02:
Gradient02()
case .gradient0201:
Gradient0201()
case .gradient03:
Gradient03()
case .gradient04:
Expand All @@ -68,6 +71,8 @@ enum GradientBackground: String, Equatable, Identifiable, CaseIterable, ColorSch
Image(nsImage: .homePageBackgroundGradient01)
case .gradient02:
Image(nsImage: .homePageBackgroundGradient02)
case .gradient0201:
Image(nsImage: .homePageBackgroundGradient0201)
case .gradient03:
Image(nsImage: .homePageBackgroundGradient03)
case .gradient04:
Expand All @@ -83,7 +88,7 @@ enum GradientBackground: String, Equatable, Identifiable, CaseIterable, ColorSch

var colorScheme: ColorScheme {
switch self {
case .gradient01, .gradient02, .gradient03:
case .gradient01, .gradient02, .gradient0201, .gradient03:
.light
case .gradient04, .gradient05, .gradient06, .gradient07:
.dark
Expand Down Expand Up @@ -191,6 +196,35 @@ private struct Gradient02: View {
}
}

@available(macOS 12.0, *)
private struct Gradient0201: View {
samsymons marked this conversation as resolved.
Show resolved Hide resolved
var body: some View {
ZStack {
EllipticalGradient(
colors: [Color(red: 1, green: 0.8, blue: 0.2).opacity(0.8), .clear],
center: UnitPoint(x: 1.04, y: 1.08),
endRadiusFraction: 1
)

EllipticalGradient(
colors: [
Color(red: 1, green: 0.84, blue: 0.36).opacity(0.7),
Color(red: 1, green: 0.84, blue: 0.8).opacity(0.2)
],
center: UnitPoint(x: 0.56, y: 0.5),
endRadiusFraction: 1
)

EllipticalGradient(
colors: [Color(red: 0.95, green: 0.63, blue: 0.54).opacity(0.6), .clear],
center: UnitPoint(x: -0.26, y: 0.5),
endRadiusFraction: 1
)
}
.background(Color(red: 1, green: 0.87, blue: 0.48))
}
}

@available(macOS 12.0, *)
private struct Gradient03: View {
var body: some View {
Expand Down Expand Up @@ -320,6 +354,7 @@ private struct Gradient06: View {
.background(Color(red: 0.07, green: 0.01, blue: 0.21))
}
}

@available(macOS 12.0, *)
private struct Gradient07: View {
var body: some View {
Expand Down Expand Up @@ -358,6 +393,8 @@ private struct Gradient07: View {
.frame(width: 640, height: 400)
GradientBackground.gradient02.view
.frame(width: 640, height: 400)
GradientBackground.gradient0201.view
.frame(width: 640, height: 400)
GradientBackground.gradient03.view
.frame(width: 640, height: 400)
GradientBackground.gradient04.view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import Combine
import Foundation
import NewTabPage
import os.log
import PixelKit
import SwiftUI
Expand Down Expand Up @@ -59,7 +60,7 @@ extension HomePage.Models {
final class SettingsModel: ObservableObject {

enum Const {
static let maximumNumberOfUserImages = 4
static let maximumNumberOfUserImages = 8
samsymons marked this conversation as resolved.
Show resolved Hide resolved
static let defaultColorPickerColor = NSColor.white
}

Expand Down Expand Up @@ -88,6 +89,7 @@ extension HomePage.Models {
let userColorProvider: () -> UserColorProviding
let showAddImageFailedAlert: () -> Void
let navigator: HomePageSettingsModelNavigator
let customizerOpener = NewTabPageCustomizerOpener()

@Published var settingsButtonWidth: CGFloat = .infinity
@Published private(set) var availableUserBackgroundImages: [UserBackgroundImage] = []
Expand Down Expand Up @@ -244,8 +246,13 @@ extension HomePage.Models {
@Published var customBackground: CustomBackground? {
didSet {
appearancePreferences.homePageCustomBackground = customBackground
if case .userImage(let userBackgroundImage) = customBackground {
switch customBackground {
case .solidColor(let solidColorBackground) where solidColorBackground.predefinedColorName == nil:
lastPickedCustomColor = solidColorBackground.color
samsymons marked this conversation as resolved.
Show resolved Hide resolved
case .userImage(let userBackgroundImage):
customImagesManager?.updateSelectedTimestamp(for: userBackgroundImage)
default:
break
}
if let customBackground {
Logger.homePageSettings.debug("Home page background updated: \(customBackground), color scheme: \(customBackground.colorScheme)")
Expand Down Expand Up @@ -298,9 +305,6 @@ extension HomePage.Models {
provider.showColorPanel(with: lastPickedCustomColorHexValue.flatMap(NSColor.init(hex:)) ?? Const.defaultColorPickerColor)

userColorCancellable = provider.colorPublisher
.handleEvents(receiveOutput: { [weak self] color in
self?.lastPickedCustomColor = color
})
.map { CustomBackground.solidColor(.init(color: $0)) }
.assign(to: \.customBackground, onWeaklyHeld: self)
}
Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo/HomePage/Model/PromotionViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ extension HomePage.Models {
let title: String?
let description: String
let proceedButtonText: String
let proceedAction: () -> Void
let proceedAction: () async -> Void
let closeAction: () -> Void

init(image: ImageResource, title: String? = nil, description: String, proceedButtonText: String, proceedAction: @escaping () -> Void, closeAction: @escaping () -> Void) {
init(image: ImageResource, title: String? = nil, description: String, proceedButtonText: String, proceedAction: @escaping () async -> Void, closeAction: @escaping () -> Void) {
self.image = image
self.title = title
self.description = description
Expand Down
6 changes: 5 additions & 1 deletion DuckDuckGo/HomePage/View/PromotionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,11 @@ extension HomePage.Views {

private var button: some View {
Group {
Button(action: viewModel.proceedAction) {
Button {
Task { @MainActor in
await viewModel.proceedAction()
}
} label: {
Text(verbatim: viewModel.proceedButtonText)
}
.controlSize(.large)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// DefaultHomePageSettingsModelNavigator+NewTabPageLinkOpening.swift
//
// Copyright © 2024 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import NewTabPage

extension DefaultHomePageSettingsModelNavigator: NewTabPageLinkOpening {

func openLink(_ target: NewTabPageDataModel.OpenAction.Target) async {
switch target {
case .settings:
openAppearanceSettings()
}
}
}
11 changes: 10 additions & 1 deletion DuckDuckGo/NewTabPage/NewTabPageActionsManagerExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,18 @@ extension NewTabPageActionsManager {
getLegacyIsViewExpandedSetting: UserDefaultsWrapper<Bool>(key: .homePageShowAllFavorites, defaultValue: false).wrappedValue
)

let customizationProvider = NewTabPageCustomizationProvider(homePageSettingsModel: NSApp.delegateTyped.homePageSettingsModel)
let freemiumDBPBannerProvider = NewTabPageFreemiumDBPBannerProvider(model: NSApp.delegateTyped.freemiumDBPPromotionViewCoordinator)

self.init(scriptClients: [
NewTabPageConfigurationClient(sectionsVisibilityProvider: appearancePreferences),
NewTabPageConfigurationClient(
sectionsVisibilityProvider: appearancePreferences,
customBackgroundProvider: customizationProvider,
linkOpener: DefaultHomePageSettingsModelNavigator()
),
NewTabPageCustomBackgroundClient(model: customizationProvider),
NewTabPageRMFClient(remoteMessageProvider: activeRemoteMessageModel),
NewTabPageFreemiumDBPClient(provider: freemiumDBPBannerProvider),
NewTabPageNextStepsCardsClient(model: NewTabPageNextStepsCardsProvider(continueSetUpModel: HomePage.Models.ContinueSetUpModel(tabOpener: NewTabPageTabOpener()))),
NewTabPageFavoritesClient(favoritesModel: favoritesModel, preferredFaviconSize: Int(Favicon.SizeCategory.medium.rawValue)),
NewTabPagePrivacyStatsClient(model: privacyStatsModel)
Expand Down
Loading
Loading