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

Native NetP feedback form #1926

Merged
merged 44 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7eef2c5
Add a prototype feedback form implementation.
samsymons Nov 28, 2023
f5f14ac
Merge branch 'develop' into sam/improved-netp-feedback-form
samsymons Nov 30, 2023
73b56c7
Continue working on the form.
samsymons Dec 1, 2023
207d04b
Tweaing view stuff.
samsymons Dec 1, 2023
b452cfb
Merge branch 'develop' into sam/improved-netp-feedback-form
samsymons Dec 1, 2023
f898835
Working on more UI.
samsymons Dec 2, 2023
978c930
Tweaking height calculation.
samsymons Dec 2, 2023
f7d1236
Add a metadata collector.
samsymons Dec 2, 2023
ebd0886
Even more work on metadata.
samsymons Dec 4, 2023
448d6bf
Wire up pixel sending.
samsymons Dec 4, 2023
95461ac
Use Swift Concurrency for sending pixels.
samsymons Dec 4, 2023
dfa6752
Add a sending failed state.
samsymons Dec 4, 2023
944d2d2
Merge branch 'develop' into sam/improved-netp-feedback-form
samsymons Dec 5, 2023
7f309d0
Merge branch 'develop' into sam/improved-netp-feedback-form
samsymons Dec 5, 2023
9f3f1df
Rename feedback form views and VCs.
samsymons Dec 5, 2023
6b2c880
Clean up height calculation.
samsymons Dec 5, 2023
e1ff59d
More code clean-up.
samsymons Dec 5, 2023
b63dfcf
De-duplicate FocusableTextEditor.
samsymons Dec 5, 2023
d612f2f
Update a comment.
samsymons Dec 6, 2023
ce87c6a
Somewhat fix a todo.
samsymons Dec 6, 2023
370cfa8
Wire the feedback form up to the real Share Feedback button.
samsymons Dec 6, 2023
3466486
Remove the feedback form from the debug menu.
samsymons Dec 6, 2023
b9d6c2b
Remove print statements.
samsymons Dec 6, 2023
652efaf
Merge branch 'develop' into sam/improved-netp-feedback-form
samsymons Dec 6, 2023
ee9601c
Use the correct asset for the form.
samsymons Dec 6, 2023
b33292b
Merge branch 'main' into sam/improved-netp-feedback-form
samsymons Dec 6, 2023
2f0c2b7
Update the feedback form title font.
samsymons Dec 6, 2023
cb2075c
Merge branch 'main' into sam/improved-netp-feedback-form
samsymons Dec 7, 2023
24c61bc
Update copy per the ship review.
samsymons Dec 7, 2023
6d27b19
Merge branch 'main' into sam/improved-netp-feedback-form
samsymons Dec 7, 2023
1babeb7
Fix the macOS 11 text editor compilation.
samsymons Dec 7, 2023
5a8d9f1
Clean up buttons.
samsymons Dec 7, 2023
1a2a05f
Merge branch 'main' into sam/improved-netp-feedback-form
samsymons Dec 8, 2023
4085b5c
Add VPN settings to the metadata collector.
samsymons Dec 8, 2023
58e6578
Add the last error message and connected server IP.
samsymons Dec 8, 2023
af79637
Merge branch 'main' into sam/improved-netp-feedback-form
samsymons Dec 8, 2023
83317e8
Clean up button titles text.
samsymons Dec 8, 2023
e4fcba7
Clean up strings for the VPN sent state.
samsymons Dec 8, 2023
edafbfe
Clean up the VPN feedback form error state.
samsymons Dec 8, 2023
8cf7e64
Clean up remaining VPN form strings.
samsymons Dec 8, 2023
dd2a046
Clean up submit button logic.
samsymons Dec 8, 2023
7599d68
Begin adding unit tests.
samsymons Dec 8, 2023
22ccff5
Fix up tests.
samsymons Dec 8, 2023
29ba688
Test the failure state.
samsymons Dec 8, 2023
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
72 changes: 72 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions DuckDuckGo/Application/URLEventHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ final class URLEventHandler {
}
case AppLaunchCommand.showSettings.launchURL:
WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn)
case AppLaunchCommand.shareFeedback.launchURL:
WindowControllersManager.shared.showShareFeedbackModal()
default:
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "VPNFeedbackSent.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
31 changes: 30 additions & 1 deletion DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extension UserText {

// MARK: - Navigation Bar Status View

static let networkProtectionNavBarStatusViewShareFeedback = NSLocalizedString("network.protection.navbar.status.view.share.feedback", value: "Share Feedback…", comment: "Menu item for 'Share Feedback' in the Network Protection status view that's shown in the navigation bar")
static let networkProtectionNavBarStatusViewShareFeedback = NSLocalizedString("network.protection.navbar.status.view.share.feedback", value: "Send Feedback…", comment: "Menu item for 'Send Feedback' in the Network Protection status view that's shown in the navigation bar")
static let networkProtectionNavBarStatusMenuVPNSettings = NSLocalizedString("network.protection.status.menu.vpn.settings", value: "VPN Settings…", comment: "The status menu 'VPN Settings' menu item")

// MARK: - System Extension Installation Messages
Expand Down Expand Up @@ -153,6 +153,34 @@ extension UserText {
static let networkProtectionTermsOfServiceSection8Title = NSLocalizedString("network-protection.terms-of-service.section.8.title", value: "We need your feedback.", comment: "Terms of Service title for Network Protection")
static let networkProtectionTermsOfServiceSection8List = NSLocalizedString("network-protection.terms-of-service.section.8.list", value: "You may be asked during the beta period to provide feedback about your experience. Doing so is optional and your feedback may be used to improve the service.\n\nIf you have enabled notifications for the DuckDuckGo app, we may use notifications to ask about your experience. You can disable notifications if you do not want to receive them.", comment: "Terms of Service list for Network Protection")

// MARK: - Feedback Form

static let vpnFeedbackFormTitle = NSLocalizedString("vpn.feedback-form.title", value: "Help Improve the DuckDuckGo VPN", comment: "Title for each screen of the VPN feedback form")
static let vpnFeedbackFormCategorySelect = NSLocalizedString("vpn.feedback-form.category.select-category", value: "Select a category", comment: "Title for the category selection state of the VPN feedback form")
static let vpnFeedbackFormCategoryUnableToInstall = NSLocalizedString("vpn.feedback-form.category.unable-to-install", value: "Unable to install VPN", comment: "Title for the 'unable to install' category of the VPN feedback form")
static let vpnFeedbackFormCategoryFailsToConnect = NSLocalizedString("vpn.feedback-form.category.fails-to-connect", value: "VPN fails to connect", comment: "Title for the 'VPN fails to connect' category of the VPN feedback form")
static let vpnFeedbackFormCategoryTooSlow = NSLocalizedString("vpn.feedback-form.category.too-slow", value: "VPN connection is too slow", comment: "Title for the 'VPN is too slow' category of the VPN feedback form")
static let vpnFeedbackFormCategoryIssuesWithApps = NSLocalizedString("vpn.feedback-form.category.issues-with-apps", value: "VPN causes issues with other apps or websites", comment: "Title for the category 'VPN causes issues with other apps or websites' category of the VPN feedback form")
static let vpnFeedbackFormCategoryLocalDeviceConnectivity = NSLocalizedString("vpn.feedback-form.category.local-device-connectivity", value: "VPN won't let me connect to local device", comment: "Title for the local device connectivity category of the VPN feedback form")
static let vpnFeedbackFormCategoryBrowserCrashOrFreeze = NSLocalizedString("vpn.feedback-form.category.browser-crash-or-freeze", value: "VPN causes browser to crash or freeze", comment: "Title for the browser crash/freeze category of the VPN feedback form")
static let vpnFeedbackFormCategoryFeatureRequest = NSLocalizedString("vpn.feedback-form.category.feature-request", value: "VPN feature request", comment: "Title for the 'VPN feature request' category of the VPN feedback form")
static let vpnFeedbackFormCategoryOther = NSLocalizedString("vpn.feedback-form.category.other", value: "Other VPN feedback", comment: "Title for the 'other VPN feedback' category of the VPN feedback form")

static let vpnFeedbackFormText1 = NSLocalizedString("vpn.feedback-form.text-1", value: "Please describe what's happening, what you expected to happen, and the steps that led to the issue:", comment: "Text for the body of the VPN feedback form")
static let vpnFeedbackFormText2 = NSLocalizedString("vpn.feedback-form.text-2", value: "In addition to the details entered into this form, your app issue report will contain:", comment: "Text for the body of the VPN feedback form")
static let vpnFeedbackFormText3 = NSLocalizedString("vpn.feedback-form.text-3", value: "• Whether specific DuckDuckGo features are enabled", comment: "Bullet text for the body of the VPN feedback form")
static let vpnFeedbackFormText4 = NSLocalizedString("vpn.feedback-form.text-4", value: "• Aggregate DuckDuckGo app diagnostics", comment: "Bullet text for the body of the VPN feedback form")
static let vpnFeedbackFormText5 = NSLocalizedString("vpn.feedback-form.text-5", value: "By clicking \"Submit\" I agree that DuckDuckGo may use the information in this report for purposes of improving the app's features.", comment: "Text for the body of the VPN feedback form")

static let vpnFeedbackFormSendingConfirmationTitle = NSLocalizedString("vpn.feedback-form.sending-confirmation.title", value: "Thank you!", comment: "Title for the feedback sent view title of the VPN feedback form")
static let vpnFeedbackFormSendingConfirmationDescription = NSLocalizedString("vpn.feedback-form.sending-confirmation.description", value: "Your feedback will help us improve the\nDuckDuckGo VPN.", comment: "Title for the feedback sent view description of the VPN feedback form")
static let vpnFeedbackFormSendingConfirmationError = NSLocalizedString("vpn.feedback-form.sending-confirmation.error", value: "We couldn't send your feedback right now, please try again.", comment: "Title for the feedback sending error text of the VPN feedback form")

static let vpnFeedbackFormButtonDone = NSLocalizedString("vpn.feedback-form.button.done", value: "Done", comment: "Title for the Done button of the VPN feedback form")
static let vpnFeedbackFormButtonCancel = NSLocalizedString("vpn.feedback-form.button.cancel", value: "Cancel", comment: "Title for the Cancel button of the VPN feedback form")
static let vpnFeedbackFormButtonSubmit = NSLocalizedString("vpn.feedback-form.button.submit", value: "Submit", comment: "Title for the Submit button of the VPN feedback form")
static let vpnFeedbackFormButtonSubmitting = NSLocalizedString("vpn.feedback-form.button.submitting", value: "Submitting…", comment: "Title for the Submitting state of the VPN feedback form")

}

#if DBP
Expand Down Expand Up @@ -190,6 +218,7 @@ extension UserText {
static let dataBrokerProtectionWaitlistButtonEnableNotifications = NSLocalizedString("data-broker-protection.waitlist.button.enable-notifications", value: "Enable Notifications", comment: "Enable Notifications button for Personal Information Removal joined waitlist screen")
static let dataBrokerProtectionWaitlistButtonJoinWaitlist = NSLocalizedString("data-broker-protection.waitlist.button.join-waitlist", value: "Join the Waitlist", comment: "Join Waitlist button for Personal Information Removal join waitlist screen")
static let dataBrokerProtectionWaitlistButtonAgreeAndContinue = NSLocalizedString("data-broker-protection.waitlist.button.agree-and-continue", value: "Agree and Continue", comment: "Agree and Continue button for Personal Information Removal join waitlist screen")

}

#endif
53 changes: 53 additions & 0 deletions DuckDuckGo/Common/View/SwiftUI/FocusableTextEditor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// FocusableTextEditor.swift
//
// Copyright © 2023 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 SwiftUI

@available(macOS 12, *)
struct FocusableTextEditor: View {
samsymons marked this conversation as resolved.
Show resolved Hide resolved

@Binding var text: String
@FocusState var isFocused: Bool

let cornerRadius: CGFloat = 8.0
let borderWidth: CGFloat = 0.4
var characterLimit: Int = 10000

var body: some View {
TextEditor(text: $text)
.frame(height: 150.0)
.font(.body)
.foregroundColor(.primary)
.focused($isFocused)
.padding(EdgeInsets(top: 3.0, leading: 6.0, bottom: 5.0, trailing: 0.0))
.clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
.onChange(of: text) {
text = String($0.prefix(characterLimit))
}
.background(
ZStack {
RoundedRectangle(cornerRadius: cornerRadius).stroke(Color.accentColor.opacity(0.5), lineWidth: 4).opacity(isFocused ? 1 : 0).scaleEffect(isFocused ? 1 : 1.04)
.animation(isFocused ? .easeIn(duration: 0.2) : .easeOut(duration: 0.0), value: isFocused)
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(Color(NSColor.textEditorBorderColor), lineWidth: borderWidth)
RoundedRectangle(cornerRadius: cornerRadius)
.fill(Color(NSColor.textEditorBackgroundColor))
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ extension AppLaunchCommand {
case .justOpen:
return "networkprotection://just-open"
case .shareFeedback:
return "https://form.asana.com/?k=_wNLt6YcT5ILpQjDuW0Mxw&d=137249556945"
return "networkprotection://share-feedback"
case .showStatus:
return "networkprotection://show-status"
case .showSettings:
Expand Down
39 changes: 2 additions & 37 deletions DuckDuckGo/SecureVault/View/PasswordManagementLoginItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -470,10 +470,10 @@ private struct NotesView: View {

if model.isEditing || model.isNew {
#if APPSTORE
FocusableTextEditor()
FocusableTextEditor(text: $model.notes)
#else
if #available(macOS 12, *) {
FocusableTextEditor()
FocusableTextEditor(text: $model.notes)
} else {
TextEditor(text: $model.notes)
.frame(height: 197.0)
Expand Down Expand Up @@ -509,41 +509,6 @@ private struct NotesView: View {

}

@available(macOS 12, *)
struct FocusableTextEditor: View {

@EnvironmentObject var model: PasswordManagementLoginModel
@FocusState var isFocused: Bool

let cornerRadius: CGFloat = 8.0
let borderWidth: CGFloat = 0.4
let characterLimit: Int = 10000

var body: some View {
TextEditor(text: $model.notes)
.frame(height: 197.0)
.font(.body)
.foregroundColor(.primary)
.focused($isFocused)
.padding(EdgeInsets(top: 3.0, leading: 6.0, bottom: 5.0, trailing: 0.0))
.clipShape(RoundedRectangle(cornerRadius: cornerRadius,
style: .continuous))
.onChange(of: model.notes) {
model.notes = String($0.prefix(characterLimit))
}
.background(
ZStack {
RoundedRectangle(cornerRadius: cornerRadius).stroke(Color.accentColor.opacity(0.5), lineWidth: 4).opacity(isFocused ? 1 : 0).scaleEffect(isFocused ? 1 : 1.04)
.animation(isFocused ? .easeIn(duration: 0.2) : .easeOut(duration: 0.0), value: isFocused)
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(Color(NSColor.textEditorBorderColor), lineWidth: borderWidth)
RoundedRectangle(cornerRadius: cornerRadius)
.fill(Color(NSColor.textEditorBackgroundColor))
}
)
}
}

private struct DatesView: View {

@EnvironmentObject var model: PasswordManagementLoginModel
Expand Down
6 changes: 6 additions & 0 deletions DuckDuckGo/Statistics/PixelEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
case dashboardProtectionAllowlistAdd(triggerOrigin: String?)
case dashboardProtectionAllowlistRemove(triggerOrigin: String?)

// VPN
case vpnBreakageReport(category: String, description: String, metadata: String)

// Network Protection Waitlist
case networkProtectionWaitlistUserActive
case networkProtectionWaitlistEntryPointMenuItemDisplayed
Expand Down Expand Up @@ -461,7 +464,7 @@
case .duckPlayerSettingBackToDefault:
return "m_mac_duck-player_setting_back-to-default"

case .dashboardProtectionAllowlistAdd(let triggerOrigin):

Check warning on line 467 in DuckDuckGo/Statistics/PixelEvent.swift

View workflow job for this annotation

GitHub Actions / Test (Non-Sandbox)

immutable value 'triggerOrigin' was never used; consider replacing with '_' or removing it
return "m_mac_mp_wla"
case .dashboardProtectionAllowlistRemove(let triggerOrigin):
return "m_mac_mp_wlr"
Expand All @@ -473,6 +476,9 @@
case .serpDay21to27:
return "m.mac.search-day-21-27.initial"

case .vpnBreakageReport:
return "m_mac_vpn_breakage_report"

case .networkProtectionWaitlistUserActive:
return "m_mac_netp_waitlist_user_active"
case .networkProtectionWaitlistEntryPointMenuItemDisplayed:
Expand Down
7 changes: 7 additions & 0 deletions DuckDuckGo/Statistics/PixelParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ extension Pixel.Event {
guard let trigger = triggerOrigin else { return nil }
return [PixelKit.Parameters.dashboardTriggerOrigin: trigger]

case .vpnBreakageReport(let category, let description, let metadata):
return [
PixelKit.Parameters.vpnBreakageCategory: category,
PixelKit.Parameters.vpnBreakageDescription: description,
PixelKit.Parameters.vpnBreakageMetadata: metadata
]

// Don't use default to force new items to be thought about
case .crash,
.brokenSiteReport,
Expand Down
65 changes: 65 additions & 0 deletions DuckDuckGo/VPNFeedbackForm/VPNFeedbackCategory.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// VPNFeedbackCategory.swift
//
// Copyright © 2023 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

#if NETWORK_PROTECTION

enum VPNFeedbackCategory: String, CaseIterable {
case landingPage
case unableToInstall
case failsToConnect
case tooSlow
case issueWithAppOrWebsite
case cantConnectToLocalDevice
case appCrashesOrFreezes
case featureRequest
case somethingElse

var isFeedbackCategory: Bool {
switch self {
case .landingPage:
return false
case .unableToInstall,
.failsToConnect,
.tooSlow,
.issueWithAppOrWebsite,
.cantConnectToLocalDevice,
.appCrashesOrFreezes,
.featureRequest,
.somethingElse:
return true
}
}

var displayName: String {
switch self {
case .landingPage: return UserText.vpnFeedbackFormCategorySelect
case .unableToInstall: return UserText.vpnFeedbackFormCategoryUnableToInstall
case .failsToConnect: return UserText.vpnFeedbackFormCategoryFailsToConnect
case .tooSlow: return UserText.vpnFeedbackFormCategoryTooSlow
case .issueWithAppOrWebsite: return UserText.vpnFeedbackFormCategoryIssuesWithApps
case .cantConnectToLocalDevice: return UserText.vpnFeedbackFormCategoryLocalDeviceConnectivity
case .appCrashesOrFreezes: return UserText.vpnFeedbackFormCategoryBrowserCrashOrFreeze
case .featureRequest: return UserText.vpnFeedbackFormCategoryFeatureRequest
case .somethingElse: return UserText.vpnFeedbackFormCategoryOther
}
}
}

#endif
Loading
Loading