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

Implement static prompts for add to dock #3790

Merged
merged 22 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions DuckDuckGo-macOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2876,6 +2876,8 @@
B6FA893F269C424500588ECD /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */; };
B6FA8941269C425400588ECD /* PrivacyDashboardPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */; };
BB0346F52CEB80B400D23E05 /* DownloadsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0346F42CEB80B400D23E05 /* DownloadsTests.swift */; };
BB1A43902D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */; };
BB1A43912D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */; };
BB3229052D08644400DA92E9 /* TabBarRemoteMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3229042D08643700DA92E9 /* TabBarRemoteMessageView.swift */; };
BB3229062D08644400DA92E9 /* TabBarRemoteMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3229042D08643700DA92E9 /* TabBarRemoteMessageView.swift */; };
BB4339DB2C7F9606005D7ED7 /* PinnedTabsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4339DA2C7F9606005D7ED7 /* PinnedTabsTests.swift */; };
Expand Down Expand Up @@ -4876,6 +4878,7 @@
B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = "<group>"; };
B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPopover.swift; sourceTree = "<group>"; };
BB0346F42CEB80B400D23E05 /* DownloadsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsTests.swift; sourceTree = "<group>"; };
BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemWithNotificationDot.swift; sourceTree = "<group>"; };
BB3229042D08643700DA92E9 /* TabBarRemoteMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarRemoteMessageView.swift; sourceTree = "<group>"; };
BB4339DA2C7F9606005D7ED7 /* PinnedTabsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTabsTests.swift; sourceTree = "<group>"; };
BB470EBA2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkManagementDetailViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -8428,6 +8431,7 @@
AA86491624D8339A001BABEE /* View */ = {
isa = PBXGroup;
children = (
BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */,
BBB9314C2D1F0F1700D50AC1 /* ShowToolbarsOnFullScreenMenuCoordinator.swift */,
AA7EB6EE27E880EA00036718 /* Animations */,
AAC5E4F025D6BF10007F5990 /* AddressBarButton.swift */,
Expand Down Expand Up @@ -11966,6 +11970,7 @@
B62B483A2ADE46FC000DECE5 /* Application.swift in Sources */,
3706FBEE293F65D500E42796 /* MainView.swift in Sources */,
3706FBEF293F65D500E42796 /* EmailUrlExtensions.swift in Sources */,
BB1A43912D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */,
3706FBF0293F65D500E42796 /* PasswordManagementItemModel.swift in Sources */,
3706FBF2293F65D500E42796 /* FindInPageModel.swift in Sources */,
1D9A4E5B2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */,
Expand Down Expand Up @@ -13170,6 +13175,7 @@
37878E562CA3330300CC9EB5 /* HomePageAddressBarModel.swift in Sources */,
85480FCF25D1AA22009424E3 /* ConfigurationStore.swift in Sources */,
AA3D531B27A2F57E00074EC1 /* Feedback.swift in Sources */,
BB1A43902D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */,
4B0A63E8289DB58E00378EF7 /* FirefoxFaviconsReader.swift in Sources */,
1E7E2E9029029A2A00C01B54 /* ContentBlockingRulesUpdateObserver.swift in Sources */,
4B8AC93926B48A5100879451 /* FirefoxLoginReader.swift in Sources */,
Expand Down
6 changes: 6 additions & 0 deletions DuckDuckGo/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

#if SPARKLE
var updateController: UpdateController!
var dockCustomization: DockCustomization!
#endif

@UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: Date.monthAgo)
Expand All @@ -176,6 +177,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
return firstLaunchDate >= Date.weekAgo
}

static var twoDaysPassedSinceFirstLaunch: Bool {
return firstLaunchDate.daysSinceNow() >= 2
}

@MainActor
override init() {
// will not add crash handlers and will fire pixel on applicationDidFinishLaunching if didCrashDuringCrashHandlersSetUp == true
Expand Down Expand Up @@ -349,6 +354,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
#if SPARKLE
if NSApp.runType != .uiTests {
updateController = UpdateController(internalUserDecider: internalUserDecider)
dockCustomization = DockCustomizer()
stateRestorationManager.subscribeToAutomaticAppRelaunching(using: updateController.willRelaunchAppPublisher)
}
#endif
Expand Down
61 changes: 60 additions & 1 deletion DuckDuckGo/Application/DockCustomizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,50 @@
//

import Foundation
import Combine
import Common
import os.log
import Persistence

protocol DockCustomization {
var isAddedToDock: Bool { get }

@discardableResult
func addToDock() -> Bool

/// The notification mentiond here is the blue dot notification shown in the more options menu.
/// The blue dot is also show in the Add To Dock menu item.
///
/// The requriments for the blue dot show to shown are the following:
/// - Two days passed since first lauch.
/// - We didn't show it in the past (this means the blue dot was shown, the user opened the more options menu and then closed it)
var shouldShowNotification: Bool { get }
var shouldShowNotificationPublisher: AnyPublisher<Bool, Never> { get }
func didCloseMoreOptionsMenu()
func resetData()
}

final class DockCustomizer: DockCustomization {
enum Keys {
static let wasNotificationShownToUser = "was-dock-notification.show-to-users"
}

private let positionProvider: DockPositionProviding
private let keyValueStore: KeyValueStoring

init(positionProvider: DockPositionProviding = DockPositionProvider()) {
@Published private var shouldShowNotificationPrivate: Bool = false
var shouldShowNotificationPublisher: AnyPublisher<Bool, Never> {
$shouldShowNotificationPrivate.eraseToAnyPublisher()
}
private var cancellables = Set<AnyCancellable>()

init(positionProvider: DockPositionProviding = DockPositionProvider(),
keyValueStore: KeyValueStoring = UserDefaults.standard) {
self.positionProvider = positionProvider
self.keyValueStore = keyValueStore

shouldShowNotificationPrivate = shouldShowNotification
startTimer()
}

private var dockPlistURL: URL = URL(fileURLWithPath: NSString(string: "~/Library/Preferences/com.apple.dock.plist").expandingTildeInPath)
Expand All @@ -41,6 +69,25 @@ final class DockCustomizer: DockCustomization {
return NSDictionary(contentsOf: dockPlistURL) as? [String: AnyObject]
}

private func startTimer() {
Timer.publish(every: 12 * 60 * 60, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard let self = self else { return }

self.shouldShowNotificationPrivate = self.shouldShowNotification
}
.store(in: &cancellables)
}

private var didWeShowNotificationToUser: Bool {
keyValueStore.object(forKey: Keys.wasNotificationShownToUser) as? Bool ?? false
}

var shouldShowNotification: Bool {
AppDelegate.twoDaysPassedSinceFirstLaunch && !didWeShowNotificationToUser
}

// This checks whether the bundle identifier of the current bundle
// is present in the 'persistent-apps' array of the Dock's plist.
var isAddedToDock: Bool {
Expand All @@ -53,6 +100,18 @@ final class DockCustomizer: DockCustomization {
return persistentApps.contains(where: { ($0["tile-data"] as? [String: AnyObject])?["bundle-identifier"] as? String == bundleIdentifier })
}

func didCloseMoreOptionsMenu() {
if AppDelegate.twoDaysPassedSinceFirstLaunch {
shouldShowNotificationPrivate = false
keyValueStore.set(true, forKey: Keys.wasNotificationShownToUser)
}
}

func resetData() {
keyValueStore.set(false, forKey: Keys.wasNotificationShownToUser)
shouldShowNotificationPrivate = shouldShowNotification
}

// Adds a dictionary representing the application, either by using an existing
// one from 'recent-apps' or creating a new one if the application isn't recently used.
// It then inserts this dictionary into the 'persistent-apps' list at a position
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xDE",
"green" : "0x79",
"red" : "0x5F"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xA1",
"green" : "0x44",
"red" : "0x2D"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x3C",
"green" : "0xBA",
"red" : "0x4C"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x6D",
"green" : "0xD6",
"red" : "0x7B"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "add-to-home.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "Check-Circle-16.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
1 change: 1 addition & 0 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,7 @@ struct UserText {
static let isAddedToDock = NSLocalizedString("preferences.is-added-to-dock", value: "DuckDuckGo is added to the Dock.", comment: "Indicates that the browser is added to the macOS system Dock")
static let isNotAddedToDock = NSLocalizedString("preferences.not-added-to-dock", value: "DuckDuckGo is not added to the Dock.", comment: "Indicate that the browser is not added to macOS system Dock")
static let addToDock = NSLocalizedString("preferences.add-to-dock", value: "Add to Dock…", comment: "Action button to add the app to the Dock")
static let addDuckDuckGoToDock = NSLocalizedString("preferences.add-to-dock", value: "Add DuckDuckGo To Dock…", comment: "Action button to add the app to the Dock")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this has been reviewed already, but usually the word to in macOS menus seems to be lowercase. Just dropping a note that we may want to change this:
Screenshot 2025-01-28 at 15 15 44

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, copy was approved, but I can bring this to their attention.

static let onStartup = NSLocalizedString("preferences.on-startup", value: "On Startup", comment: "Name of the preferences section related to app startup")
static let reopenAllWindowsFromLastSession = NSLocalizedString("preferences.reopen-windows", value: "Reopen all windows from last session", comment: "Option to control session restoration")
static let showHomePage = NSLocalizedString("preferences.show-home", value: "Open a new window", comment: "Option to control session startup")
Expand Down
18 changes: 9 additions & 9 deletions DuckDuckGo/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -56685,55 +56685,55 @@
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Zum Dock hinzufügen …"
"value" : "DuckDuckGo zum Dock hinzufügen…"
}
},
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Add to Dock…"
"value" : "Add DuckDuckGo To Dock…"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Añadir al Dock…"
"value" : "Añadir DuckDuckGo al Dock…"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ajouter au Dock…"
"value" : "Ajouter DuckDuckGo au Dock…"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aggiungi al dock…"
"value" : "Aggiungi DuckDuckGo al Dock…"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "App toevoegen aan je dock…"
"value" : "Voeg DuckDuckGo toe aan Dock…"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dodaj do Docka…"
"value" : "Dodaj DuckDuckGo do Dock…"
}
},
"pt" : {
"stringUnit" : {
"state" : "translated",
"value" : "Adicionar à Dock…"
"value" : "Adicionar DuckDuckGo ao Dock…"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Добавить на док-панель…"
"value" : "Добавить DuckDuckGo в док-панель…"
}
}
}
Expand Down
Loading
Loading