Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 8080c1f

Browse files
Implement static prompts for add to dock (#3790)
Task/Issue URL: https://app.asana.com/0/1204006570077678/1209173259474635/f Tech Design URL: CC: ## Description This adds new static entry points to add to the dock. Now, the adding to dock setting can be used from the main menu, and more options menu, and also in the Settings -> Default Browser -> Shortcuts section. In the more options menu, we will show a blue dot (both in the more options button and in the menu item), the notification dot will disappear after the user opens and closes the more options menu. If you want to check the blue dot again, I’ve added a debug menu in the Reset Data -> Reset Add To Dock more options menu notification. ### Acceptance criteria **AC1 - AppStore users should not see any Add To Dock prompt** Given an AppStore user, when the user does not have DDG in the dock, then it should never see one of the prompts. **AC2 - If the user is non-AppStore, and DDG is not in the dock.** Given a non-AppStore user, when the user does not have DDG in the dock, then the user should see a 'Add to Dock' prompt on the main menu, more options, and in the Settings → Default Browser preference pane. **AC3 - If the user is non-AppStore, and DDG is in the dock.** Given a non-AppStore user, when the user does have DDG in the dock, then the user should not see the 'Add to Dock' prompt on the main menu and more options, and in Settings → Default Browser, you should see 'DuckDuckGo is in your Dock. **Definition of Done**: * [x] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f)
1 parent 90afa41 commit 8080c1f

25 files changed

+553
-22
lines changed

DuckDuckGo-macOS.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -2909,6 +2909,8 @@
29092909
B6FA893F269C424500588ECD /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */; };
29102910
B6FA8941269C425400588ECD /* PrivacyDashboardPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */; };
29112911
BB0346F52CEB80B400D23E05 /* DownloadsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0346F42CEB80B400D23E05 /* DownloadsTests.swift */; };
2912+
BB1A43902D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */; };
2913+
BB1A43912D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */; };
29122914
BB3229052D08644400DA92E9 /* TabBarRemoteMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3229042D08643700DA92E9 /* TabBarRemoteMessageView.swift */; };
29132915
BB3229062D08644400DA92E9 /* TabBarRemoteMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB3229042D08643700DA92E9 /* TabBarRemoteMessageView.swift */; };
29142916
BB4339DB2C7F9606005D7ED7 /* PinnedTabsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB4339DA2C7F9606005D7ED7 /* PinnedTabsTests.swift */; };
@@ -4954,6 +4956,7 @@
49544956
B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = "<group>"; };
49554957
B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPopover.swift; sourceTree = "<group>"; };
49564958
BB0346F42CEB80B400D23E05 /* DownloadsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsTests.swift; sourceTree = "<group>"; };
4959+
BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemWithNotificationDot.swift; sourceTree = "<group>"; };
49574960
BB3229042D08643700DA92E9 /* TabBarRemoteMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarRemoteMessageView.swift; sourceTree = "<group>"; };
49584961
BB4339DA2C7F9606005D7ED7 /* PinnedTabsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedTabsTests.swift; sourceTree = "<group>"; };
49594962
BB470EBA2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkManagementDetailViewModel.swift; sourceTree = "<group>"; };
@@ -8584,6 +8587,7 @@
85848587
AA86491624D8339A001BABEE /* View */ = {
85858588
isa = PBXGroup;
85868589
children = (
8590+
BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */,
85878591
BBB9314C2D1F0F1700D50AC1 /* ShowToolbarsOnFullScreenMenuCoordinator.swift */,
85888592
AA7EB6EE27E880EA00036718 /* Animations */,
85898593
AAC5E4F025D6BF10007F5990 /* AddressBarButton.swift */,
@@ -12130,6 +12134,7 @@
1213012134
B62B483A2ADE46FC000DECE5 /* Application.swift in Sources */,
1213112135
3706FBEE293F65D500E42796 /* MainView.swift in Sources */,
1213212136
3706FBEF293F65D500E42796 /* EmailUrlExtensions.swift in Sources */,
12137+
BB1A43912D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */,
1213312138
3706FBF0293F65D500E42796 /* PasswordManagementItemModel.swift in Sources */,
1213412139
3706FBF2293F65D500E42796 /* FindInPageModel.swift in Sources */,
1213512140
1D9A4E5B2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */,
@@ -13344,6 +13349,7 @@
1334413349
37878E562CA3330300CC9EB5 /* HomePageAddressBarModel.swift in Sources */,
1334513350
85480FCF25D1AA22009424E3 /* ConfigurationStore.swift in Sources */,
1334613351
AA3D531B27A2F57E00074EC1 /* Feedback.swift in Sources */,
13352+
BB1A43902D4968F2000807C7 /* MenuItemWithNotificationDot.swift in Sources */,
1334713353
4B0A63E8289DB58E00378EF7 /* FirefoxFaviconsReader.swift in Sources */,
1334813354
1E7E2E9029029A2A00C01B54 /* ContentBlockingRulesUpdateObserver.swift in Sources */,
1334913355
4B8AC93926B48A5100879451 /* FirefoxLoginReader.swift in Sources */,

DuckDuckGo/Application/AppDelegate.swift

+6
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
164164

165165
#if SPARKLE
166166
var updateController: UpdateController!
167+
var dockCustomization: DockCustomization!
167168
#endif
168169

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

180+
static var twoDaysPassedSinceFirstLaunch: Bool {
181+
return firstLaunchDate.daysSinceNow() >= 2
182+
}
183+
179184
@MainActor
180185
override init() {
181186
// will not add crash handlers and will fire pixel on applicationDidFinishLaunching if didCrashDuringCrashHandlersSetUp == true
@@ -349,6 +354,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
349354
#if SPARKLE
350355
if NSApp.runType != .uiTests {
351356
updateController = UpdateController(internalUserDecider: internalUserDecider)
357+
dockCustomization = DockCustomizer()
352358
stateRestorationManager.subscribeToAutomaticAppRelaunching(using: updateController.willRelaunchAppPublisher)
353359
}
354360
#endif

DuckDuckGo/Application/DockCustomizer.swift

+60-1
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,50 @@
1717
//
1818

1919
import Foundation
20+
import Combine
2021
import Common
2122
import os.log
23+
import Persistence
2224

2325
protocol DockCustomization {
2426
var isAddedToDock: Bool { get }
2527

2628
@discardableResult
2729
func addToDock() -> Bool
30+
31+
/// The notification mentiond here is the blue dot notification shown in the more options menu.
32+
/// The blue dot is also show in the Add To Dock menu item.
33+
///
34+
/// The requriments for the blue dot show to shown are the following:
35+
/// - Two days passed since first lauch.
36+
/// - 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)
37+
var shouldShowNotification: Bool { get }
38+
var shouldShowNotificationPublisher: AnyPublisher<Bool, Never> { get }
39+
func didCloseMoreOptionsMenu()
40+
func resetData()
2841
}
2942

3043
final class DockCustomizer: DockCustomization {
44+
enum Keys {
45+
static let wasNotificationShownToUser = "was-dock-notification.show-to-users"
46+
}
3147

3248
private let positionProvider: DockPositionProviding
49+
private let keyValueStore: KeyValueStoring
3350

34-
init(positionProvider: DockPositionProviding = DockPositionProvider()) {
51+
@Published private var shouldShowNotificationPrivate: Bool = false
52+
var shouldShowNotificationPublisher: AnyPublisher<Bool, Never> {
53+
$shouldShowNotificationPrivate.eraseToAnyPublisher()
54+
}
55+
private var cancellables = Set<AnyCancellable>()
56+
57+
init(positionProvider: DockPositionProviding = DockPositionProvider(),
58+
keyValueStore: KeyValueStoring = UserDefaults.standard) {
3559
self.positionProvider = positionProvider
60+
self.keyValueStore = keyValueStore
61+
62+
shouldShowNotificationPrivate = shouldShowNotification
63+
startTimer()
3664
}
3765

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

72+
private func startTimer() {
73+
Timer.publish(every: 12 * 60 * 60, on: .main, in: .common)
74+
.autoconnect()
75+
.sink { [weak self] _ in
76+
guard let self = self else { return }
77+
78+
self.shouldShowNotificationPrivate = self.shouldShowNotification
79+
}
80+
.store(in: &cancellables)
81+
}
82+
83+
private var didWeShowNotificationToUser: Bool {
84+
keyValueStore.object(forKey: Keys.wasNotificationShownToUser) as? Bool ?? false
85+
}
86+
87+
var shouldShowNotification: Bool {
88+
AppDelegate.twoDaysPassedSinceFirstLaunch && !didWeShowNotificationToUser
89+
}
90+
4491
// This checks whether the bundle identifier of the current bundle
4592
// is present in the 'persistent-apps' array of the Dock's plist.
4693
var isAddedToDock: Bool {
@@ -53,6 +100,18 @@ final class DockCustomizer: DockCustomization {
53100
return persistentApps.contains(where: { ($0["tile-data"] as? [String: AnyObject])?["bundle-identifier"] as? String == bundleIdentifier })
54101
}
55102

103+
func didCloseMoreOptionsMenu() {
104+
if AppDelegate.twoDaysPassedSinceFirstLaunch {
105+
shouldShowNotificationPrivate = false
106+
keyValueStore.set(true, forKey: Keys.wasNotificationShownToUser)
107+
}
108+
}
109+
110+
func resetData() {
111+
keyValueStore.set(false, forKey: Keys.wasNotificationShownToUser)
112+
shouldShowNotificationPrivate = shouldShowNotification
113+
}
114+
56115
// Adds a dictionary representing the application, either by using an existing
57116
// one from 'recent-apps' or creating a new one if the application isn't recently used.
58117
// It then inserts this dictionary into the 'persistent-apps' list at a position
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0xDE",
9+
"green" : "0x79",
10+
"red" : "0x5F"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0xA1",
27+
"green" : "0x44",
28+
"red" : "0x2D"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0x3C",
9+
"green" : "0xBA",
10+
"red" : "0x4C"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x6D",
27+
"green" : "0xD6",
28+
"red" : "0x7B"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "add-to-home.svg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true,
14+
"template-rendering-intent" : "template"
15+
}
16+
}
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "Check-Circle-16.svg",
5+
"idiom" : "universal"
6+
}
7+
],
8+
"info" : {
9+
"author" : "xcode",
10+
"version" : 1
11+
},
12+
"properties" : {
13+
"preserves-vector-representation" : true,
14+
"template-rendering-intent" : "template"
15+
}
16+
}

DuckDuckGo/Common/Localizables/UserText.swift

+1
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,7 @@ struct UserText {
663663
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")
664664
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")
665665
static let addToDock = NSLocalizedString("preferences.add-to-dock", value: "Add to Dock…", comment: "Action button to add the app to the Dock")
666+
static let addDuckDuckGoToDock = NSLocalizedString("preferences.add-to-dock", value: "Add DuckDuckGo To Dock…", comment: "Action button to add the app to the Dock")
666667
static let onStartup = NSLocalizedString("preferences.on-startup", value: "On Startup", comment: "Name of the preferences section related to app startup")
667668
static let reopenAllWindowsFromLastSession = NSLocalizedString("preferences.reopen-windows", value: "Reopen all windows from last session", comment: "Option to control session restoration")
668669
static let showHomePage = NSLocalizedString("preferences.show-home", value: "Open a new window", comment: "Option to control session startup")

DuckDuckGo/Localizable.xcstrings

+9-9
Original file line numberDiff line numberDiff line change
@@ -56685,55 +56685,55 @@
5668556685
"de" : {
5668656686
"stringUnit" : {
5668756687
"state" : "translated",
56688-
"value" : "Zum Dock hinzufügen …"
56688+
"value" : "DuckDuckGo zum Dock hinzufügen…"
5668956689
}
5669056690
},
5669156691
"en" : {
5669256692
"stringUnit" : {
5669356693
"state" : "new",
56694-
"value" : "Add to Dock…"
56694+
"value" : "Add DuckDuckGo To Dock…"
5669556695
}
5669656696
},
5669756697
"es" : {
5669856698
"stringUnit" : {
5669956699
"state" : "translated",
56700-
"value" : "Añadir al Dock…"
56700+
"value" : "Añadir DuckDuckGo al Dock…"
5670156701
}
5670256702
},
5670356703
"fr" : {
5670456704
"stringUnit" : {
5670556705
"state" : "translated",
56706-
"value" : "Ajouter au Dock…"
56706+
"value" : "Ajouter DuckDuckGo au Dock…"
5670756707
}
5670856708
},
5670956709
"it" : {
5671056710
"stringUnit" : {
5671156711
"state" : "translated",
56712-
"value" : "Aggiungi al dock…"
56712+
"value" : "Aggiungi DuckDuckGo al Dock…"
5671356713
}
5671456714
},
5671556715
"nl" : {
5671656716
"stringUnit" : {
5671756717
"state" : "translated",
56718-
"value" : "App toevoegen aan je dock…"
56718+
"value" : "Voeg DuckDuckGo toe aan Dock…"
5671956719
}
5672056720
},
5672156721
"pl" : {
5672256722
"stringUnit" : {
5672356723
"state" : "translated",
56724-
"value" : "Dodaj do Docka…"
56724+
"value" : "Dodaj DuckDuckGo do Dock…"
5672556725
}
5672656726
},
5672756727
"pt" : {
5672856728
"stringUnit" : {
5672956729
"state" : "translated",
56730-
"value" : "Adicionar à Dock…"
56730+
"value" : "Adicionar DuckDuckGo ao Dock…"
5673156731
}
5673256732
},
5673356733
"ru" : {
5673456734
"stringUnit" : {
5673556735
"state" : "translated",
56736-
"value" : "Добавить на док-панель…"
56736+
"value" : "Добавить DuckDuckGo в док-панель…"
5673756737
}
5673856738
}
5673956739
}

0 commit comments

Comments
 (0)