Skip to content

Commit

Permalink
Unified feedback form for Privacy Pro (#3058)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1205922231854923/1207701941801991/f
Tech Design URL:
CC:

**Description**:

**Steps to test this PR**:
1.

<!--
Tagging instructions
If this PR isn't ready to be merged for whatever reason it should be
marked with the `DO NOT MERGE` label (particularly if it's a draft)
If it's pending Product Review/PFR, please add the `Pending Product
Review` label.

If at any point it isn't actively being worked on/ready for
review/otherwise moving forward (besides the above PR/PFR exception)
strongly consider closing it (or not opening it in the first place). If
you decide not to close it, make sure it's labelled to make it clear the
PRs state and comment with more information.
-->

**Definition of Done**:

* [ ] 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
quanganhdo authored Aug 27, 2024
1 parent 542c108 commit 04d229a
Show file tree
Hide file tree
Showing 42 changed files with 1,686 additions and 94 deletions.
162 changes: 113 additions & 49 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/duckduckgo/BrowserServicesKit",
"state" : {
"revision" : "4a55217003ad7b2d44a1ac616d47596c0bda69dc",
"version" : "186.0.0"
"revision" : "606ccf9e86f5cad3ae83132f46241357feecf152",
"version" : "186.1.0"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "Privacy-Pro-16D.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.
78 changes: 76 additions & 2 deletions DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ extension UserText {
static let networkProtectionInviteSuccessMessage = "DuckDuckGo's VPN secures all of your device's Internet traffic anytime, anywhere."

// MARK: - Navigation Bar Status View
// "network.protection.navbar.status.view.share.feedback" - Menu item for 'Share VPN Feedback' in the VPN status view that's shown in the navigation bar
static let networkProtectionNavBarStatusViewShareFeedback = "Share VPN Feedback…"
// "network.protection.navbar.status.view.send.feedback" - Menu item for 'Send Feedback' in the VPN status view that's shown in the navigation bar
static let networkProtectionNavBarStatusViewSendFeedback = "Send Feedback…"
// "network.protection.status.menu.vpn.settings" - The status menu 'VPN Settings' menu item
static let networkProtectionNavBarStatusMenuVPNSettings = "VPN Settings…"
// "network.protection.status.menu.faq" - The status menu 'FAQ' menu item
Expand Down Expand Up @@ -73,6 +73,24 @@ extension UserText {
extension UserText {

// MARK: - Feedback Form
// "feedback-form.title" - Title for each screen of the feedback form
static let feedbackFormTitle = "Help Improve Privacy Pro"
// "general.feedback-form.category.select-feature" - Title for the feature selection state of the general feedback form
static let generalFeedbackFormCategorySelect = "Select a category"
// "general.feedback-form.category.ppro" - Description for the feedback form when the issue is related to subscription and payments
static let generalFeedbackFormCategoryPPro = "Subscription and Payments"
// "general.feedback-form.category.vpn" - Description for the feedback form when the issue is related to VPN
static let generalFeedbackFormCategoryVPN = "VPN"
// "general.feedback-form.category.pir" - Description for the feedback form when the issue is related to Personal Info Removal (PIR)
static let generalFeedbackFormCategoryPIR = "Personal Info Removal"
// "general.feedback-form.category.itr" - Description for the feedback form when the issue is related to Identity Theft Restoration (ITR)
static let generalFeedbackFormCategoryITR = "Identity Theft Restoration"
// "ppro.feedback-form.category.select-category" - Title for the category selection state of the feedback form
static let pproFeedbackFormCategorySelect = "Select a category"
// "ppro.feedback-form.category.otp" - Description for the feedback form when there is an issue with the one-time password
static let pproFeedbackFormCategoryOTP = "Issue with one-time password"
// "ppro.feedback-form.category.something-else" - Description for the feedback form when the user has an issue not categorized in other options
static let pproFeedbackFormCategoryOther = "Something else"
// "vpn.feedback-form.title" - Title for each screen of the VPN feedback form
static let vpnFeedbackFormTitle = "Help Improve the DuckDuckGo VPN"
// "vpn.feedback-form.category.select-category" - Title for the category selection state of the VPN feedback form
Expand All @@ -93,7 +111,63 @@ extension UserText {
static let vpnFeedbackFormCategoryFeatureRequest = "VPN feature request"
// "vpn.feedback-form.category.other" - Title for the 'other VPN feedback' category of the VPN feedback form
static let vpnFeedbackFormCategoryOther = "Other VPN feedback"
// "pir.feedback-form.category.select-category" - Title for the category selection state of the PIR feedback form
static let pirFeedbackFormCategorySelect = "Select a category"
// "pir.feedback-form.category.no-info-on-specific-site" - Description for the feedback form when the scan didn't find user's info on a specific site
static let pirFeedbackFormCategoryNothingOnSpecificSite = "The scan didn't find my info on a specific site"
// "pir.feedback-form.category.not-me" - Description for the feedback form when the scan found records that don’t belong to the user
static let pirFeedbackFormCategoryNotMe = "The scan found records which aren't me"
// "pir.feedback-form.category.scan-stuck" - Description for the feedback form when the scan process is stuck
static let pirFeedbackFormCategoryScanStuck = "The scan for records is stuck"
// "pir.feedback-form.category.removal-stuck" - Description for the feedback form when the removal process is stuck
static let pirFeedbackFormCategoryRemovalStuck = "The removal process is stuck"
// "itr.feedback-form.category.select-category" - Title for the category selection state of the ITR feedback form
static let itrFeedbackFormCategorySelect = "Select a category"
// "itr.feedback-form.category.access-code" - Description for the feedback form when there is an issue with the access code
static let itrFeedbackFormCategoryAccessCode = "Issue with access code"
// "itr.feedback-form.category.contact-advisor" - Description for the feedback form when the user is unable to contact an advisor
static let itrFeedbackFormCategoryCantContactAdvisor = "Unable to contact advisor"
// "itr.feedback-form.category.unhelpful" - Description for the feedback form when the call to an advisor was unhelpful
static let itrFeedbackFormCategoryUnhelpful = "Call to Advisor was unhelpful"
// "itr.feedback-form.category.something-else" - Description for the feedback form when the user has an issue not categorized in other options
static let itrFeedbackFormCategorySomethingElse = "Something else"

// "ppro.feedback-form.text-1" - Text for the body of the PPro feedback form
static let pproFeedbackFormText1 = "Found an issue not cover in our [help center](duck://)? We definitely want to know about it.\n\nTell us what's going on:"
// "ppro.feedback-form.text-2" - Text for the body of the PPro feedback form
static let pproFeedbackFormText2 = "In addition to the details entered above, we send some anonymized info with your feedback:"
// "ppro.feedback-form.text-3" - Bullet text for the body of the PPro feedback form
static let pproFeedbackFormText3 = "• Whether specific browser features are active"
// "ppro.feedback-form.text-4" - Bullet text for the body of the PPro feedback form
static let pproFeedbackFormText4 = "• Aggregate app diagnostics (e.g., error codes)"
// "ppro.feedback-form.text-5" - Text for the body of the PPro feedback form
static let pproFeedbackFormText5 = "By clicking \"Submit\" you agree that DuckDuckGo may use information submitted to improve the app."
// "ppro.feedback-form.disclaimer" - Text for the disclaimer of the PPro feedback form
static let pproFeedbackFormDisclaimer = "Reports are anonymous and sent to DuckDuckGo to help improve our service"

// "ppro.feedback-form.sending-confirmation.title" - Title for the feedback sent view title of the feedback form
static let pproFeedbackFormSendingConfirmationTitle = "Thank you!"
// "ppro.feedback-form.sending-confirmation.description" - Title for the feedback sent view description of the feedback form
static let pproFeedbackFormSendingConfirmationDescription = "Your Feedback will help us improve Privacy Pro."
// "ppro.feedback-form.sending-confirmation.error" - Title for the feedback sending error text of the feedback form
static let pproFeedbackFormSendingConfirmationError = "We couldn't send your feedback right now, please try again."

// "ppro.feedback-form.button.done" - Title for the Done button of the PPro feedback form
static let pproFeedbackFormButtonDone = "Done"
// "ppro.feedback-form.button.cancel" - Title for the Cancel button of the PPro feedback form
static let pproFeedbackFormButtonCancel = "Cancel"
// "ppro.feedback-form.button.submit" - Title for the Submit button of the PPro feedback form
static let pproFeedbackFormButtonSubmit = "Submit"
// "ppro.feedback-form.button.submitting" - Title for the Submitting state of the PPro feedback form
static let pproFeedbackFormButtonSubmitting = "Submitting…"

// "ppro.feedback-form.general-feedback.placeholder" - Placeholder for the General Feedback step in the Privacy Pro feedback form
static let pproFeedbackFormGeneralFeedbackPlaceholder = "Please give us your feedback:"
// "ppro.feedback-form.request-feature.placeholder" - Placeholder for the Feature Request step in the Privacy Pro feedback form
static let pproFeedbackFormRequestFeaturePlaceholder = "What feature would you like to see?"

// "pir.feedback-form.category.other" - Description for the feedback form when the user has an issue not categorized in other options
static let pirFeedbackFormCategoryOther = "Something else"
// "vpn.feedback-form.text-1" - Text for the body of the VPN feedback form
static let vpnFeedbackFormText1 = "Please describe what's happening, what you expected to happen, and the steps that led to the issue:"
// "vpn.feedback-form.text-2" - Text for the body of the VPN feedback form
Expand Down
1 change: 1 addition & 0 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ struct UserText {
static let bookmarkImportedFromFolder = NSLocalizedString("bookmarks.imported.from.folder", value: "Imported from", comment: "Name of the folder the imported bookmarks are saved into")

// MARK: Feedback
static let sendPProFeedback = NSLocalizedString("send.ppro.feedback", value: "Send Privacy Pro Feedback", comment: "Menu with feedback commands")
static let reportBrokenSite = NSLocalizedString("report.broken.site", value: "Report Broken Site", comment: "Menu with feedback commands")
static let browserFeedback = NSLocalizedString("send.browser.feedback", value: "Send Browser Feedback", comment: "Menu with feedback commands")
static let browserFeedbackTitle = NSLocalizedString("send.browser.feedback.title", value: "Help Improve the DuckDuckGo Browser", comment: "Title of the interface to send feedback on the browser")
Expand Down
14 changes: 13 additions & 1 deletion DuckDuckGo/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -51960,6 +51960,18 @@
}
}
},
"send.ppro.feedback" : {
"comment" : "Menu with feedback commands",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Send Privacy Pro Feedback"
}
}
}
},
"settings" : {
"comment" : "Menu item for opening settings",
"extractionState" : "extracted_with_value",
Expand Down Expand Up @@ -58457,4 +58469,4 @@
}
},
"version" : "1.0"
}
}
5 changes: 5 additions & 0 deletions DuckDuckGo/Menus/MainMenuActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ extension AppDelegate {
}
}

@MainActor
@objc func openPProFeedback(_ sender: Any?) {
WindowControllersManager.shared.showShareFeedbackModal(source: .settings)
}

#endif

@objc func navigateToBookmark(_ sender: Any?) {
Expand Down
24 changes: 22 additions & 2 deletions DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ final class MoreOptionsMenu: NSMenu {
let feedbackMenuItem = NSMenuItem(title: feedbackString, action: nil, keyEquivalent: "")
.withImage(.sendFeedback)

feedbackMenuItem.submenu = FeedbackSubMenu(targetting: self, tabCollectionViewModel: tabCollectionViewModel)
feedbackMenuItem.submenu = FeedbackSubMenu(targetting: self,
tabCollectionViewModel: tabCollectionViewModel,
subscriptionFeatureAvailability: subscriptionFeatureAvailability,
accountManager: accountManager)
addItem(feedbackMenuItem)

addItem(NSMenuItem.separator())
Expand Down Expand Up @@ -488,8 +491,15 @@ final class EmailOptionsButtonSubMenu: NSMenu {

@MainActor
final class FeedbackSubMenu: NSMenu {
private let subscriptionFeatureAvailability: SubscriptionFeatureAvailability
private let accountManager: AccountManager

init(targetting target: AnyObject, tabCollectionViewModel: TabCollectionViewModel) {
init(targetting target: AnyObject,
tabCollectionViewModel: TabCollectionViewModel,
subscriptionFeatureAvailability: SubscriptionFeatureAvailability,
accountManager: AccountManager) {
self.subscriptionFeatureAvailability = subscriptionFeatureAvailability
self.accountManager = accountManager
super.init(title: UserText.sendFeedback)
updateMenuItems(with: tabCollectionViewModel, targetting: target)
}
Expand All @@ -512,6 +522,16 @@ final class FeedbackSubMenu: NSMenu {
keyEquivalent: "")
.withImage(.siteBreakage)
addItem(reportBrokenSiteItem)

if subscriptionFeatureAvailability.usesUnifiedFeedbackForm, accountManager.isUserAuthenticated {
addItem(.separator())

let sendPProFeedbackItem = NSMenuItem(title: UserText.sendPProFeedback,
action: #selector(AppDelegate.openPProFeedback(_:)),
keyEquivalent: "")
.withImage(.pProFeedback)
addItem(sendPProFeedbackItem)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ final class NavigationBarViewController: NSViewController {
private var selectedTabViewModelCancellable: AnyCancellable?
private var credentialsToSaveCancellable: AnyCancellable?
private var vpnToggleCancellable: AnyCancellable?
private var feedbackFormCancellable: AnyCancellable?
private var passwordManagerNotificationCancellable: AnyCancellable?
private var pinnedViewsNotificationCancellable: AnyCancellable?
private var navigationButtonsCancellables = Set<AnyCancellable>()
Expand Down Expand Up @@ -151,6 +152,7 @@ final class NavigationBarViewController: NSViewController {
listenToPasswordManagerNotifications()
listenToPinningManagerNotifications()
listenToMessageNotifications()
listenToFeedbackFormNotifications()
subscribeToDownloads()
addContextMenu()

Expand Down Expand Up @@ -404,6 +406,12 @@ final class NavigationBarViewController: NSViewController {
.store(in: &cancellables)
}

func listenToFeedbackFormNotifications() {
feedbackFormCancellable = NotificationCenter.default.publisher(for: .OpenUnifiedFeedbackForm).receive(on: DispatchQueue.main).sink { _ in
WindowControllersManager.shared.showShareFeedbackModal(source: .ppro)
}
}

@objc private func showVPNUninstalledFeedback() {
// Only show the popover if we aren't already presenting one:
guard view.window?.isKeyWindow == true, (self.presentedViewControllers ?? []).isEmpty else { return }
Expand Down Expand Up @@ -1169,4 +1177,5 @@ extension NavigationBarViewController {

extension Notification.Name {
static let ToggleNetworkProtectionInMainWindow = Notification.Name("com.duckduckgo.vpn.toggle-popover-in-main-window")
static let OpenUnifiedFeedbackForm = Notification.Name("com.duckduckgo.subscription.open-unified-feedback-form")
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager {
try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.showFAQ)
}),
NetworkProtectionStatusView.Model.MenuItem(
name: UserText.networkProtectionNavBarStatusViewShareFeedback,
name: UserText.networkProtectionNavBarStatusViewSendFeedback,
action: {
try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.shareFeedback)
})
Expand All @@ -140,7 +140,7 @@ final class NetworkProtectionNavBarPopoverManager: NetPPopoverManager {
try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.showFAQ)
}),
NetworkProtectionStatusView.Model.MenuItem(
name: UserText.networkProtectionNavBarStatusViewShareFeedback,
name: UserText.networkProtectionNavBarStatusViewSendFeedback,
action: {
try? await appLauncher.launchApp(withCommand: VPNAppLaunchCommand.shareFeedback)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ final class VPNURLEventHandler {
}

func showShareFeedback() {
windowControllerManager.showShareFeedbackModal()
windowControllerManager.showShareFeedbackModal(source: .vpn)
}

func showMainWindow() {
Expand Down
Loading

0 comments on commit 04d229a

Please sign in to comment.