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 pixel measurements for New Tab Page #3210

Merged
merged 12 commits into from
Aug 19, 2024
37 changes: 37 additions & 0 deletions Core/DailyPixelFiring.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// DailyPixelFiring.swift
// DuckDuckGo
//
// 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 Foundation

public protocol DailyPixelFiring {
static func fireDaily(_ pixel: Pixel.Event,
withAdditionalParameters params: [String: String])

static func fireDaily(_ pixel: Pixel.Event)
}

extension DailyPixel: DailyPixelFiring {
public static func fireDaily(_ pixel: Pixel.Event, withAdditionalParameters params: [String: String]) {
dus7 marked this conversation as resolved.
Show resolved Hide resolved
fire(pixel: pixel, withAdditionalParameters: params)
}

public static func fireDaily(_ pixel: Pixel.Event) {
fire(pixel: pixel)
}
}
49 changes: 48 additions & 1 deletion Core/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,27 @@ extension Pixel {
case bookmarkLaunchedDaily
case newTabPageDisplayedDaily

// MARK: New Tab Page
case newTabPageMessageDisplayed
case newTabPageMessageDismissed

case newTabPageFavoritesPlaceholderTapped
case newTabPageFavoritesInfoTooltip

case newTabPageFavoritesSeeMore
case newTabPageFavoritesSeeLess

case newTabPageCustomize

case newTabPageShortcutClicked(_ shortcutName: String)

case newTabPageCustomizeSectionOff(_ sectionName: String)
case newTabPageCustomizeSectionOn(_ sectionName: String)
case newTabPageSectionReordered

case newTabPageCustomizeShortcutRemoved(_ shortcutName: String)
case newTabPageCustomizeShortcutAdded(_ shortcutName: String)

// MARK: DuckPlayer
case duckPlayerDailyUniqueView
case duckPlayerViewFromYoutubeViaMainOverlay
Expand Down Expand Up @@ -1461,7 +1482,33 @@ extension Pixel.Event {
case .favoriteLaunchedNTPDaily: return "m_favorite_launched_ntp_daily"
case .bookmarkLaunchedDaily: return "m_bookmark_launched_daily"
case .newTabPageDisplayedDaily: return "m_new_tab_page_displayed_daily"


// MARK: New Tab Page
case .newTabPageMessageDisplayed: return "m_new_tab_page_message_displayed"
case .newTabPageMessageDismissed: return "m_new_tab_page_message_dismissed"

case .newTabPageFavoritesPlaceholderTapped: return "m_new_tab_page_favorites_placeholder_click"
case .newTabPageFavoritesInfoTooltip: return "m_new_tab_page_favorites_info_tooltip"

case .newTabPageFavoritesSeeMore: return "m_new_tab_page_favorites_see_more"
case .newTabPageFavoritesSeeLess: return "m_new_tab_page_favorites_see_less"

case .newTabPageShortcutClicked(let name):
return "m_new_tab_page_shortcut_clicked_\(name)"

case .newTabPageCustomize: return "m_new_tab_page_customize"

case .newTabPageCustomizeSectionOff(let sectionName):
return "m_new_tab_page_customize_section_off_\(sectionName)"
case .newTabPageCustomizeSectionOn(let sectionName):
return "m_new_tab_page_customize_section_on_\(sectionName)"
case .newTabPageSectionReordered: return "m_new_tab_page_customize_section_reordered"

case .newTabPageCustomizeShortcutRemoved(let shortcutName):
return "m_new_tab_page_customize_shortcut_removed_\(shortcutName)"
case .newTabPageCustomizeShortcutAdded(let shortcutName):
return "m_new_tab_page_customize_shortcut_added_\(shortcutName)"

// MARK: DuckPlayer
case .duckPlayerDailyUniqueView: return "m_duck-player_daily-unique-view"
case .duckPlayerViewFromYoutubeViaMainOverlay: return "m_duck-player_view-from_youtube_main-overlay"
Expand Down
16 changes: 16 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,10 @@
6F64AA5F2C49463C00CF4489 /* ShortcutsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA5E2C49463C00CF4489 /* ShortcutsModel.swift */; };
6F655BE22BAB289E00AC3597 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */; };
6F691CCA2C4979EC002E9553 /* FavoritesTooltip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F691CC92C4979EC002E9553 /* FavoritesTooltip.swift */; };
6F7FB8E12C660B3E00867DA7 /* NewTabPageFavoritesModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8DF2C660B1A00867DA7 /* NewTabPageFavoritesModelTests.swift */; };
6F7FB8E32C660BF300867DA7 /* DailyPixelFiring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */; };
6F7FB8E52C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8E42C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift */; };
6F7FB8E72C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7FB8E62C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift */; };
6F8496412BC3D8EE00ADA54E /* OnboardingButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */; };
6F934F862C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F934F852C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift */; };
6F96FF102C2B128500162692 /* NewTabPageCustomizeButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F96FF0F2C2B128500162692 /* NewTabPageCustomizeButtonView.swift */; };
Expand Down Expand Up @@ -1495,6 +1499,10 @@
6F64AA5E2C49463C00CF4489 /* ShortcutsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsModel.swift; sourceTree = "<group>"; };
6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = "<group>"; };
6F691CC92C4979EC002E9553 /* FavoritesTooltip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesTooltip.swift; sourceTree = "<group>"; };
6F7FB8DF2C660B1A00867DA7 /* NewTabPageFavoritesModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageFavoritesModelTests.swift; sourceTree = "<group>"; };
6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixelFiring.swift; sourceTree = "<group>"; };
6F7FB8E42C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageShortcutsSettingsModelTests.swift; sourceTree = "<group>"; };
6F7FB8E62C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSectionsSettingsModelTests.swift; sourceTree = "<group>"; };
6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingButtonsView.swift; sourceTree = "<group>"; };
6F934F852C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsPersistentStorageTests.swift; sourceTree = "<group>"; };
6F96FF0F2C2B128500162692 /* NewTabPageCustomizeButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageCustomizeButtonView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3619,6 +3627,9 @@
6F934F852C58DB00008364E4 /* NewTabPageSettingsPersistentStorageTests.swift */,
6FD0C41E2C5BF097000561C9 /* NewTabPageIntroMessageSetupTests.swift */,
6FD0C4202C5BF774000561C9 /* NewTabPageModelTests.swift */,
6F7FB8DF2C660B1A00867DA7 /* NewTabPageFavoritesModelTests.swift */,
6F7FB8E42C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift */,
6F7FB8E62C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift */,
564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */,
6FABAA682C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift */,
);
Expand Down Expand Up @@ -5234,6 +5245,7 @@
853A717520F62FE800FE60BC /* Pixel.swift */,
6F03CB062C32F173004179A8 /* PixelFiring.swift */,
6F03CB082C32F331004179A8 /* PixelFiringAsync.swift */,
6F7FB8E22C660BF300867DA7 /* DailyPixelFiring.swift */,
1E05D1D729C46EDA00BF9A1F /* TimedPixel.swift */,
1E05D1D529C46EBB00BF9A1F /* DailyPixel.swift */,
85E242162AB1B54D000F3E28 /* ReturnUserMeasurement.swift */,
Expand Down Expand Up @@ -7510,6 +7522,7 @@
85C11E4120904BBE00BFFEB4 /* VariantManagerTests.swift in Sources */,
F1134ECE1F40EA9C00B73467 /* AtbParserTests.swift in Sources */,
F189AEE41F18FDAF001EBAE1 /* LinkTests.swift in Sources */,
6F7FB8E12C660B3E00867DA7 /* NewTabPageFavoritesModelTests.swift in Sources */,
F1BDDBFE2C340D9C00459306 /* SubscriptionFlowViewModelTests.swift in Sources */,
F1BDDC022C340DDF00459306 /* SyncManagementViewModelTests.swift in Sources */,
D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */,
Expand All @@ -7534,6 +7547,7 @@
98AAF8E4292EB46000DBDF06 /* BookmarksMigrationTests.swift in Sources */,
85D2187224BF24F2004373D2 /* NotFoundCachingDownloaderTests.swift in Sources */,
C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */,
6F7FB8E72C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift in Sources */,
851CD674244D7E6000331B98 /* UserDefaultsExtension.swift in Sources */,
569437362BE5160600C0881B /* SyncSettingsViewControllerErrorTests.swift in Sources */,
850559D223CF710C0055C0D5 /* WebCacheManagerTests.swift in Sources */,
Expand All @@ -7547,6 +7561,7 @@
C1D21E2F293A599C006E5A05 /* AutofillLoginSessionTests.swift in Sources */,
85D2187924BF6B8B004373D2 /* FaviconSourcesProviderTests.swift in Sources */,
9F69331B2C5A16E200CD6A5D /* OnboardingDaxFavouritesTests.swift in Sources */,
6F7FB8E52C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift in Sources */,
983BD6B52B34760600AAC78E /* MockPrivacyConfiguration.swift in Sources */,
1E8146AD28C8ABF000D1AF63 /* TrackerAnimationLogicTests.swift in Sources */,
C1CDA31E2AFBF811006D1476 /* AutofillNeverPromptWebsitesManagerTests.swift in Sources */,
Expand Down Expand Up @@ -7756,6 +7771,7 @@
85A1B3B220C6CD9900C18F15 /* CookieStorage.swift in Sources */,
9856A1992933D2EB00ACB44F /* BookmarksModelsErrorHandling.swift in Sources */,
850559D023CF647C0055C0D5 /* PreserveLogins.swift in Sources */,
6F7FB8E32C660BF300867DA7 /* DailyPixelFiring.swift in Sources */,
C1CCCBA7283E101500CF3791 /* FaviconsHelper.swift in Sources */,
9813F79822BA71AA00A80EDB /* StorageCache.swift in Sources */,
B603974929C19F6F00902A34 /* Assertions.swift in Sources */,
Expand Down
44 changes: 36 additions & 8 deletions DuckDuckGo/FavoritesDefaultModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import SwiftUI
import Core
import WidgetKit

final class FavoritesDefaultModel: FavoritesModel {
final class FavoritesDefaultModel: FavoritesModel, FavoritesEmptyStateModel {

@Published private(set) var allFavorites: [Favorite] = []
@Published private(set) var isCollapsed: Bool = true

@Published private(set) var isShowingTooltip: Bool = false

private(set) lazy var faviconLoader: FavoritesFaviconLoading? = {
FavoritesFaviconLoader(onFaviconMissing: { [weak self] in
guard let self else { return }
Expand All @@ -42,13 +43,19 @@ final class FavoritesDefaultModel: FavoritesModel {
private var cancellables = Set<AnyCancellable>()

private let interactionModel: FavoritesListInteracting
private let pixelFiring: PixelFiring.Type
private let dailyPixelFiring: DailyPixelFiring.Type

var isEmpty: Bool {
allFavorites.isEmpty
}

init(interactionModel: FavoritesListInteracting) {
init(interactionModel: FavoritesListInteracting,
pixelFiring: PixelFiring.Type = Pixel.self,
dailyPixelFiring: DailyPixelFiring.Type = DailyPixel.self) {
self.interactionModel = interactionModel
self.pixelFiring = pixelFiring
self.dailyPixelFiring = dailyPixelFiring

interactionModel.externalUpdates.sink { [weak self] _ in
try? self?.updateData()
Expand All @@ -63,6 +70,12 @@ final class FavoritesDefaultModel: FavoritesModel {

func toggleCollapse() {
isCollapsed.toggle()

if isCollapsed {
pixelFiring.fire(.newTabPageFavoritesSeeLess, withAdditionalParameters: [:])
} else {
pixelFiring.fire(.newTabPageFavoritesSeeMore, withAdditionalParameters: [:])
}
}

func prefixedFavorites(for columnsCount: Int) -> FavoritesSlice {
Expand All @@ -84,8 +97,8 @@ final class FavoritesDefaultModel: FavoritesModel {
func favoriteSelected(_ favorite: Favorite) {
guard let url = favorite.urlObject else { return }

Pixel.fire(pixel: .favoriteLaunchedNTP)
DailyPixel.fire(pixel: .favoriteLaunchedNTPDaily)
pixelFiring.fire(.favoriteLaunchedNTP, withAdditionalParameters: [:])
dailyPixelFiring.fireDaily(.favoriteLaunchedNTPDaily)
Favicons.shared.loadFavicon(forDomain: url.host, intoCache: .fireproof, fromCache: .tabs)

onFavoriteURLSelected?(url)
Expand All @@ -95,8 +108,8 @@ final class FavoritesDefaultModel: FavoritesModel {
func deleteFavorite(_ favorite: Favorite) {
guard let entity = lookupEntity(for: favorite) else { return }

Pixel.fire(pixel: .homeScreenDeleteFavorite)
pixelFiring.fire(.homeScreenDeleteFavorite, withAdditionalParameters: [:])

interactionModel.removeFavorite(entity)

WidgetCenter.shared.reloadAllTimelines()
Expand All @@ -109,7 +122,7 @@ final class FavoritesDefaultModel: FavoritesModel {
func editFavorite(_ favorite: Favorite) {
guard let entity = lookupEntity(for: favorite) else { return }

Pixel.fire(pixel: .homeScreenEditFavorite)
pixelFiring.fire(.homeScreenEditFavorite, withAdditionalParameters: [:])

onFavoriteEdit?(entity)
}
Expand All @@ -127,6 +140,21 @@ final class FavoritesDefaultModel: FavoritesModel {
allFavorites.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: index)
}

// MARK: - Empty state model

func placeholderTapped() {
pixelFiring.fire(.newTabPageFavoritesPlaceholderTapped, withAdditionalParameters: [:])
}

func toggleTooltip() {
isShowingTooltip.toggle()
if isShowingTooltip {
pixelFiring.fire(.newTabPageFavoritesInfoTooltip, withAdditionalParameters: [:])
}
}

// MARK: -

private func lookupEntity(for favorite: Favorite) -> BookmarkEntity? {
interactionModel.favorites.first {
$0.uuid == favorite.id
Expand Down
27 changes: 20 additions & 7 deletions DuckDuckGo/FavoritesEmptyStateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,29 @@

import SwiftUI

struct FavoritesEmptyStateView: View {
struct FavoritesEmptyStateView<Model: FavoritesEmptyStateModel>: View {
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.isLandscapeOrientation) var isLandscape

@State private var headerPadding: CGFloat = 10
@ObservedObject var model: Model

@Binding var isShowingTooltip: Bool
@State private var headerPadding: CGFloat = 10

var body: some View {
ZStack(alignment: .topTrailing) {
VStack(spacing: 16) {
FavoritesSectionHeader(isShowingTooltip: $isShowingTooltip)
FavoritesSectionHeader(model: model)
.padding(.horizontal, headerPadding)

NewTabPageGridView { placeholdersCount in
let placeholders = Array(0..<placeholdersCount)
ForEach(placeholders, id: \.self) { _ in
FavoriteEmptyStateItem()
.frame(width: NewTabPageGrid.Item.edgeSize, height: NewTabPageGrid.Item.edgeSize)
.contentShape(.capsule)
.onTapGesture {
model.placeholderTapped()
}
}
}.overlay(
GeometryReader(content: { geometry in
Expand All @@ -53,7 +57,7 @@ struct FavoritesEmptyStateView: View {
})
}

if isShowingTooltip {
if model.isShowingTooltip {
FavoritesTooltip()
.offset(x: -headerPadding + 18, y: 24)
.frame(maxWidth: .infinity, alignment: .bottomTrailing)
Expand All @@ -63,8 +67,7 @@ struct FavoritesEmptyStateView: View {
}

#Preview {
@State var isShowingTooltip = false
return FavoritesEmptyStateView(isShowingTooltip: $isShowingTooltip)
return FavoritesEmptyStateView(model: FavoritesPreviewModel())
}

private struct WidthKey: PreferenceKey {
Expand All @@ -73,3 +76,13 @@ private struct WidthKey: PreferenceKey {
}
static var defaultValue: CGFloat = .zero
}

private final class PreviewEmptyStateModel: FavoritesEmptyStateModel {
@Published var isShowingTooltip: Bool = true

func toggleTooltip() {
}

func placeholderTapped() {
}
}
8 changes: 8 additions & 0 deletions DuckDuckGo/FavoritesModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ protocol FavoritesModel: AnyObject, ObservableObject {
func moveFavorites(from indexSet: IndexSet, to index: Int)
}

protocol FavoritesEmptyStateModel: AnyObject, ObservableObject {

var isShowingTooltip: Bool { get }

func placeholderTapped()
func toggleTooltip()
}

struct FavoritesSlice {
let items: [Favorite]
let isCollapsible: Bool
Expand Down
12 changes: 11 additions & 1 deletion DuckDuckGo/FavoritesPreviewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import Bookmarks
import Foundation

final class FavoritesPreviewModel: FavoritesModel {
final class FavoritesPreviewModel: FavoritesModel, FavoritesEmptyStateModel {

@Published var isShowingTooltip: Bool = false
var isCollapsed: Bool = true

@Published var allFavorites: [Favorite]
Expand Down Expand Up @@ -78,6 +80,14 @@ final class FavoritesPreviewModel: FavoritesModel {
func loadFavicon(for favorite: Favorite, size: CGFloat) async {

}

func placeholderTapped() {

}

func toggleTooltip() {

}
}

struct EmptyFaviconLoading: FavoritesFaviconLoading {
Expand Down
Loading
Loading