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

Commit 29f8770

Browse files
authored
Add PPro unified feedback form (#3248)
<!-- Note: This checklist is a reminder of our shared engineering expectations. Feel free to change it, although assigning a GitHub reviewer and the items in bold are required. ⚠️ If you're an external contributor, please file an issue first before working on a PR, as we can't guarantee that we will accept your changes if they haven't been discussed ahead of time. Thanks! --> Task/Issue URL: https://app.asana.com/0/414235014887631/1207701941801991/f Tech Design URL: CC: **Description**: <!-- If at any point it isn't actively being worked on/ready for review/otherwise moving forward strongly consider closing it (or not opening it in the first place). If you decide not to close it, use Draft PR while work is still in progress or use `DO NOT MERGE` label to clarify the PRs state and comment with more information. --> This adds PPro unified feedback form to iOS **Steps to test this PR**: 1. Check Settings > Share Feedback, the existing flow should work 2. Make sure you have a PPro subscription 3. Go to Settings > Subscription Settings, the last menu item should be FAQs and Support 4. Go to Settings > VPN > Share VPN Feedback, the existing flow should work 5. Log in as internal user, use this for the privacy config: http://www.jsonblob.com/api/1274015132613992448 6. Settings > Send Feedback should now let you choose between the current browser feedback and PPro feedback form. Play with the form and see if it works 7. Settings > Subscription Settings, the last menu item should now be Send Feedback, again check if the flow works 8. Settings > VPN > Send feedback, this should use the new unified flow. Smoke test these pixels as you go through the flow: https://app.asana.com/0/72649045549333/1207816523739193/f <!-- Before submitting a PR, please ensure you have tested the combinations you expect the reviewer to test, then delete configurations you *know* do not need explicit testing. Using a simulator where a physical device is unavailable is acceptable. --> **Definition of Done (Internal Only)**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? **Copy Testing**: * [ ] Use of correct apostrophes in new copy, ie `’` rather than `'` **Orientation Testing**: * [ ] Portrait * [ ] Landscape **Device Testing**: * [ ] iPhone SE (1st Gen) * [ ] iPhone 8 * [ ] iPhone X * [ ] iPhone 14 Pro * [ ] iPad **OS Testing**: * [ ] iOS 15 * [ ] iOS 16 * [ ] iOS 17 **Theme Testing**: * [ ] Light theme * [ ] Dark theme --- ###### Internal references: [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943)
1 parent 5ca931e commit 29f8770

22 files changed

+1497
-41
lines changed

Core/Pixel.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,8 @@ public class Pixel {
256256
headers: headers)
257257
let request = APIRequest(configuration: configuration, urlSession: .session(useMainThreadCallbackQueue: true))
258258
request.fetch { _, error in
259-
os_log("Pixel fired %{public}s %{public}s", log: .generalLog, type: .debug, pixelName, "\(params)")
259+
os_log("Pixel fired %{public}s %{public}s", log: .generalLog, type: .debug,
260+
pixelName.replacingOccurrences(of: "_", with: "."), "\(params)")
260261
onComplete(error)
261262
}
262263
}

Core/PixelEvent.swift

+22
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,17 @@ extension Pixel {
779779
case duckPlayerSettingNeverOverlayYoutube
780780
case duckPlayerContingencySettingsDisplayed
781781
case duckPlayerContingencyLearnMoreClicked
782+
783+
// MARK: Unified Feedback Form
784+
case pproFeedbackFeatureRequest(description: String, source: String)
785+
case pproFeedbackGeneralFeedback(description: String, source: String)
786+
case pproFeedbackReportIssue(source: String, category: String, subcategory: String, description: String, metadata: String)
787+
case pproFeedbackFormShow
788+
case pproFeedbackActionsScreenShow(source: String)
789+
case pproFeedbackCategoryScreenShow(source: String, reportType: String)
790+
case pproFeedbackSubcategoryScreenShow(source: String, reportType: String, category: String)
791+
case pproFeedbackSubmitScreenShow(source: String, reportType: String, category: String, subcategory: String)
792+
case pproFeedbackSubmitScreenFAQClick(source: String, reportType: String, category: String, subcategory: String)
782793
}
783794

784795
}
@@ -1551,6 +1562,17 @@ extension Pixel.Event {
15511562
case .duckPlayerSettingNeverOverlayYoutube: return "duckplayer_setting_never_overlay_youtube"
15521563
case .duckPlayerContingencySettingsDisplayed: return "duckplayer_ios_contingency_settings-displayed"
15531564
case .duckPlayerContingencyLearnMoreClicked: return "duckplayer_ios_contingency_learn-more-clicked"
1565+
1566+
// MARK: Unified Feedback Form
1567+
case .pproFeedbackFeatureRequest: return "m_ppro_feedback_feature-request"
1568+
case .pproFeedbackGeneralFeedback: return "m_ppro_feedback_general-feedback"
1569+
case .pproFeedbackReportIssue: return "m_ppro_feedback_report-issue"
1570+
case .pproFeedbackFormShow: return "m_ppro_feedback_general-screen_show"
1571+
case .pproFeedbackActionsScreenShow: return "m_ppro_feedback_actions-screen_show"
1572+
case .pproFeedbackCategoryScreenShow: return "m_ppro_feedback_category-screen_show"
1573+
case .pproFeedbackSubcategoryScreenShow: return "m_ppro_feedback_subcategory-screen_show"
1574+
case .pproFeedbackSubmitScreenShow: return "m_ppro_feedback_submit-screen_show"
1575+
case .pproFeedbackSubmitScreenFAQClick: return "m_ppro_feedback_submit-screen-faq_click"
15541576
}
15551577
}
15561578
}

DuckDuckGo.xcodeproj/project.pbxproj

+36
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@
785785
B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95E728924730004ABA20 /* JSAlertController.storyboard */; };
786786
B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */; };
787787
BBFF18B12C76448100C48D7D /* QuerySubmittedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFF18B02C76448100C48D7D /* QuerySubmittedTests.swift */; };
788+
BD10B8AA2C7629740033115D /* Logger+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD10B8A92C7629740033115D /* Logger+Subscription.swift */; };
788789
BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD15DB842B959CFD00821457 /* BundleExtension.swift */; };
789790
BD2F39EB2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */; };
790791
BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */; };
@@ -796,6 +797,12 @@
796797
BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; };
797798
BDE219E62C406D19005D5884 /* PrivacyProDataReporting.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE219E52C406D19005D5884 /* PrivacyProDataReporting.swift */; };
798799
BDE219EA2C457B46005D5884 /* PrivacyProDataReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE219E92C457B46005D5884 /* PrivacyProDataReporterTests.swift */; };
800+
BDE91CD62C6294020005CB74 /* FeedbackCategoryProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CD52C6294020005CB74 /* FeedbackCategoryProviding.swift */; };
801+
BDE91CD82C629A910005CB74 /* UnifiedFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CD72C629A910005CB74 /* UnifiedFeedbackSender.swift */; };
802+
BDE91CDA2C62A70B0005CB74 /* UnifiedMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CD92C62A70B0005CB74 /* UnifiedMetadataCollector.swift */; };
803+
BDE91CDC2C62AA3A0005CB74 /* DefaultMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CDB2C62AA3A0005CB74 /* DefaultMetadataCollector.swift */; };
804+
BDE91CDE2C62B90F0005CB74 /* UnifiedFeedbackRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CDD2C62B90F0005CB74 /* UnifiedFeedbackRootView.swift */; };
805+
BDE91CE02C6515420005CB74 /* UnifiedFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDE91CDF2C6515410005CB74 /* UnifiedFeedbackFormViewModel.swift */; };
799806
BDF8D0022C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */; };
800807
BDFF031D2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */; };
801808
BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */; };
@@ -2534,6 +2541,7 @@
25342541
B6DFE6CF2BC7E47500A9CE59 /* SwiftLintTool.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftLintTool.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
25352542
B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftLintToolBundleConfiguration.xcconfig; sourceTree = "<group>"; };
25362543
BBFF18B02C76448100C48D7D /* QuerySubmittedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuerySubmittedTests.swift; sourceTree = "<group>"; };
2544+
BD10B8A92C7629740033115D /* Logger+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Logger+Subscription.swift"; sourceTree = "<group>"; };
25372545
BD15DB842B959CFD00821457 /* BundleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtension.swift; sourceTree = "<group>"; };
25382546
BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsView.swift; sourceTree = "<group>"; };
25392547
BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = "<group>"; };
@@ -2544,6 +2552,12 @@
25442552
BDC234F62B27F51100D3C798 /* UniquePixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniquePixel.swift; sourceTree = "<group>"; };
25452553
BDE219E52C406D19005D5884 /* PrivacyProDataReporting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProDataReporting.swift; sourceTree = "<group>"; };
25462554
BDE219E92C457B46005D5884 /* PrivacyProDataReporterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyProDataReporterTests.swift; sourceTree = "<group>"; };
2555+
BDE91CD52C6294020005CB74 /* FeedbackCategoryProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackCategoryProviding.swift; sourceTree = "<group>"; };
2556+
BDE91CD72C629A910005CB74 /* UnifiedFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackSender.swift; sourceTree = "<group>"; };
2557+
BDE91CD92C62A70B0005CB74 /* UnifiedMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedMetadataCollector.swift; sourceTree = "<group>"; };
2558+
BDE91CDB2C62AA3A0005CB74 /* DefaultMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultMetadataCollector.swift; sourceTree = "<group>"; };
2559+
BDE91CDD2C62B90F0005CB74 /* UnifiedFeedbackRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackRootView.swift; sourceTree = "<group>"; };
2560+
BDE91CDF2C6515410005CB74 /* UnifiedFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnifiedFeedbackFormViewModel.swift; sourceTree = "<group>"; };
25472561
BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsViewModel.swift; sourceTree = "<group>"; };
25482562
BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = "<group>"; };
25492563
BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNetworkProtectionVisibility.swift; sourceTree = "<group>"; };
@@ -4828,6 +4842,19 @@
48284842
path = Feedback;
48294843
sourceTree = "<group>";
48304844
};
4845+
BDE91CD42C6292BF0005CB74 /* Feedback */ = {
4846+
isa = PBXGroup;
4847+
children = (
4848+
BDE91CD52C6294020005CB74 /* FeedbackCategoryProviding.swift */,
4849+
BDE91CD72C629A910005CB74 /* UnifiedFeedbackSender.swift */,
4850+
BDE91CD92C62A70B0005CB74 /* UnifiedMetadataCollector.swift */,
4851+
BDE91CDB2C62AA3A0005CB74 /* DefaultMetadataCollector.swift */,
4852+
BDE91CDD2C62B90F0005CB74 /* UnifiedFeedbackRootView.swift */,
4853+
BDE91CDF2C6515410005CB74 /* UnifiedFeedbackFormViewModel.swift */,
4854+
);
4855+
path = Feedback;
4856+
sourceTree = "<group>";
4857+
};
48314858
BDFF031F2BA3D3AD00F324C9 /* Feature Visibility */ = {
48324859
isa = PBXGroup;
48334860
children = (
@@ -5049,6 +5076,7 @@
50495076
D664C7922B289AA000CBFA76 /* Subscription */ = {
50505077
isa = PBXGroup;
50515078
children = (
5079+
BDE91CD42C6292BF0005CB74 /* Feedback */,
50525080
F1FDC92F2BF4E0B3006B1435 /* SubscriptionEnvironment+Default.swift */,
50535081
D60170BB2BA32DD6001911B5 /* Subscription.swift */,
50545082
D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */,
@@ -5081,6 +5109,7 @@
50815109
D664C7962B289AA000CBFA76 /* Extensions */ = {
50825110
isa = PBXGroup;
50835111
children = (
5112+
BD10B8A92C7629740033115D /* Logger+Subscription.swift */,
50845113
F1FDC9342BF51E41006B1435 /* VPNSettings+Environment.swift */,
50855114
D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */,
50865115
);
@@ -7005,6 +7034,7 @@
70057034
files = (
70067035
EE4FB1862A28CE7200E5CBA7 /* NetworkProtectionStatusView.swift in Sources */,
70077036
C17B59592A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift in Sources */,
7037+
BDE91CDE2C62B90F0005CB74 /* UnifiedFeedbackRootView.swift in Sources */,
70087038
D65625A12C232F5E006EF297 /* SettingsDuckPlayerView.swift in Sources */,
70097039
D6FEB8B52B74994000C3615F /* HeadlessWebViewCoordinator.swift in Sources */,
70107040
6FE1273D2C204C2500EB5724 /* FavoritesView.swift in Sources */,
@@ -7140,6 +7170,7 @@
71407170
D65625952C22D382006EF297 /* TabViewController.swift in Sources */,
71417171
8C4838B5221C8F7F008A6739 /* GestureToolbarButton.swift in Sources */,
71427172
310ECFDD282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift in Sources */,
7173+
BDE91CD62C6294020005CB74 /* FeedbackCategoryProviding.swift in Sources */,
71437174
6F9FFE2A2C57ADB100A238BE /* EditableShortcutsView.swift in Sources */,
71447175
1E908BF329827C480008C8F3 /* AutoconsentManagement.swift in Sources */,
71457176
D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */,
@@ -7228,6 +7259,7 @@
72287259
85F2FFCF2211F8E5006BB258 /* TabSwitcherViewController+KeyCommands.swift in Sources */,
72297260
3157B43327F497E90042D3D7 /* SaveLoginView.swift in Sources */,
72307261
F17922E01E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift in Sources */,
7262+
BDE91CDC2C62AA3A0005CB74 /* DefaultMetadataCollector.swift in Sources */,
72317263
D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */,
72327264
EE458D142ABB652900FC651A /* NetworkProtectionDebugUtilities.swift in Sources */,
72337265
8528AE7C212EF4A200D0BD74 /* AppRatingPrompt.swift in Sources */,
@@ -7325,6 +7357,7 @@
73257357
F13B4BC01F180D8A00814661 /* TabsModel.swift in Sources */,
73267358
BD862E052B30DB250073E2EE /* VPNFeedbackCategory.swift in Sources */,
73277359
85AE6690209724120014CF04 /* NotificationView.swift in Sources */,
7360+
BDE91CE02C6515420005CB74 /* UnifiedFeedbackFormViewModel.swift in Sources */,
73287361
1EA51376286596A000493C6A /* PrivacyIconLogic.swift in Sources */,
73297362
980891A92238504B00313A70 /* UILabelExtension.swift in Sources */,
73307363
6FD8E51E2C5B84DE00345670 /* NewTabPageIntroMessageView.swift in Sources */,
@@ -7363,6 +7396,7 @@
73637396
85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */,
73647397
CB825C922C071B1400BCC586 /* AlertView.swift in Sources */,
73657398
1DDF40292BA04FCD006850D9 /* SettingsPrivacyProtectionsView.swift in Sources */,
7399+
BDE91CD82C629A910005CB74 /* UnifiedFeedbackSender.swift in Sources */,
73667400
6F64AA5F2C49463C00CF4489 /* ShortcutsModel.swift in Sources */,
73677401
F1D477C61F2126CC0031ED49 /* OmniBarState.swift in Sources */,
73687402
85F2FFCD2211F615006BB258 /* MainViewController+KeyCommands.swift in Sources */,
@@ -7461,6 +7495,7 @@
74617495
85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */,
74627496
84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */,
74637497
310D091D2799F57200DC0060 /* Download.swift in Sources */,
7498+
BDE91CDA2C62A70B0005CB74 /* UnifiedMetadataCollector.swift in Sources */,
74647499
C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */,
74657500
1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */,
74667501
9FB027122C2526DD009EA190 /* OnboardingView+IntroDialogContent.swift in Sources */,
@@ -7532,6 +7567,7 @@
75327567
D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */,
75337568
1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */,
75347569
9FEA222E2C324ECD006B03BF /* ViewVisibility.swift in Sources */,
7570+
BD10B8AA2C7629740033115D /* Logger+Subscription.swift in Sources */,
75357571
1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */,
75367572
D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */,
75377573
8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */,

DuckDuckGo/Feedback/VPNFeedbackFormViewModel.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ final class VPNFeedbackFormViewModel: ObservableObject {
7272
viewState = .feedbackSending
7373

7474
do {
75-
let metadata = await metadataCollector.collectMetadata()
75+
let metadata = await metadataCollector.collectVPNMetadata()
7676
try await feedbackSender.send(metadata: metadata, category: category, userText: feedbackFormText)
7777
viewState = .feedbackSent
7878
return true

DuckDuckGo/Feedback/VPNMetadataCollector.swift

+16-14
Original file line numberDiff line numberDiff line change
@@ -93,22 +93,10 @@ struct VPNMetadata: Encodable {
9393

9494
return String(data: encodedMetadata, encoding: .utf8)
9595
}
96-
97-
func toBase64() -> String {
98-
let encoder = JSONEncoder()
99-
encoder.outputFormatting = [.sortedKeys]
100-
101-
do {
102-
let encodedMetadata = try encoder.encode(self)
103-
return encodedMetadata.base64EncodedString()
104-
} catch {
105-
return "Failed to encode metadata to JSON, error message: \(error.localizedDescription)"
106-
}
107-
}
10896
}
10997

11098
protocol VPNMetadataCollector {
111-
func collectMetadata() async -> VPNMetadata
99+
func collectVPNMetadata() async -> VPNMetadata
112100
}
113101

114102
final class DefaultVPNMetadataCollector: VPNMetadataCollector {
@@ -130,7 +118,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector {
130118
self.defaults = defaults
131119
}
132120

133-
func collectMetadata() async -> VPNMetadata {
121+
func collectVPNMetadata() async -> VPNMetadata {
134122
let appInfoMetadata = collectAppInfoMetadata()
135123
let deviceInfoMetadata = collectDeviceInfoMetadata()
136124
let networkInfoMetadata = await collectNetworkInformation()
@@ -282,3 +270,17 @@ private extension NSError {
282270
}
283271

284272
}
273+
274+
// MARK: - Unified feedback form support
275+
276+
extension VPNMetadata: UnifiedFeedbackMetadata {}
277+
278+
extension DefaultVPNMetadataCollector: UnifiedMetadataCollector {
279+
convenience init() {
280+
self.init(statusObserver: AppDependencyProvider.shared.connectionObserver)
281+
}
282+
283+
func collectMetadata() async -> VPNMetadata? {
284+
await collectVPNMetadata()
285+
}
286+
}

DuckDuckGo/MainViewController.swift

+15
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ class MainViewController: UIViewController {
121121
private var settingsDeepLinkcancellables = Set<AnyCancellable>()
122122
private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults
123123
private var vpnCancellables = Set<AnyCancellable>()
124+
private var feedbackCancellable: AnyCancellable?
124125

125126
let privacyProDataReporter: PrivacyProDataReporting
126127

@@ -285,6 +286,7 @@ class MainViewController: UIViewController {
285286
subscribeToURLInterceptorNotifications()
286287
subscribeToSettingsDeeplinkNotifications()
287288
subscribeToNetworkProtectionEvents()
289+
subscribeToUnifiedFeedbackNotifications()
288290

289291
findInPageView.delegate = self
290292
findInPageBottomLayoutConstraint.constant = 0
@@ -1548,6 +1550,19 @@ class MainViewController: UIViewController {
15481550
nil, .deliverImmediately)
15491551
}
15501552

1553+
private func subscribeToUnifiedFeedbackNotifications() {
1554+
feedbackCancellable = NotificationCenter.default.publisher(for: .unifiedFeedbackNotification)
1555+
.receive(on: DispatchQueue.main)
1556+
.sink { _ in
1557+
DispatchQueue.main.async { [weak self] in
1558+
guard let navigationController = self?.presentedViewController as? UINavigationController else { return }
1559+
navigationController.popToRootViewController(animated: true)
1560+
ActionMessageView.present(message: UserText.vpnFeedbackFormSubmittedMessage,
1561+
presentationLocation: .withoutBottomBar)
1562+
}
1563+
}
1564+
}
1565+
15511566
private func onNetworkProtectionEntitlementMessagingChange() {
15521567
if tunnelDefaults.showEntitlementAlert {
15531568
presentExpiredEntitlementAlert()

0 commit comments

Comments
 (0)