Skip to content

Commit

Permalink
Implement static prompts for add to dock (#3790)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
jotaemepereira authored Jan 31, 2025
1 parent 90afa41 commit 8080c1f
Show file tree
Hide file tree
Showing 25 changed files with 553 additions and 22 deletions.
6 changes: 6 additions & 0 deletions DuckDuckGo-macOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2909,6 +2909,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 @@ -4954,6 +4956,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 @@ -8584,6 +8587,7 @@
AA86491624D8339A001BABEE /* View */ = {
isa = PBXGroup;
children = (
BB1A438F2D4968F2000807C7 /* MenuItemWithNotificationDot.swift */,
BBB9314C2D1F0F1700D50AC1 /* ShowToolbarsOnFullScreenMenuCoordinator.swift */,
AA7EB6EE27E880EA00036718 /* Animations */,
AAC5E4F025D6BF10007F5990 /* AddressBarButton.swift */,
Expand Down Expand Up @@ -12130,6 +12134,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 @@ -13344,6 +13349,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")
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

0 comments on commit 8080c1f

Please sign in to comment.