diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index c7767f56d8..34eaea194c 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -33,6 +33,7 @@ public enum FeatureFlag: String { case autoconsentOnByDefault case history case historyRollout + case newTabPageSections } extension FeatureFlag: FeatureFlagSourceProviding { @@ -62,7 +63,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.feature(.history)) case .historyRollout: return .remoteReleasable(.subfeature(HistorySubFeature.onByDefault)) - + case .newTabPageSections: + return .internalOnly } } } diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 12f59fcbbb..450d4f39bd 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -269,6 +269,14 @@ extension Pixel { case autofillToggledOff case autofillLoginsStacked + case autofillManagementOpened + case autofillManagementCopyUsername + case autofillManagementCopyPassword + case autofillManagementDeleteLogin + case autofillManagementDeleteAllLogins + case autofillManagementSaveLogin + case autofillManagementUpdateLogin + case autofillMultipleAuthCallsTriggered case getDesktopCopy @@ -404,6 +412,9 @@ extension Pixel { case networkProtectionWidgetDisconnectAttempt case networkProtectionWidgetDisconnectSuccess + case networkProtectionDNSUpdateCustom + case networkProtectionDNSUpdateDefault + // MARK: remote messaging pixels case remoteMessageShown @@ -656,12 +667,12 @@ extension Pixel { case settingsWebTrackingProtectionOpen case settingsGpcOn case settingsGpcOff - case settingsAutocompleteOn - case settingsAutocompleteOff - case settingsRecentlyVisitedOn - case settingsRecentlyVisitedOff case settingsGeneralAutocompleteOn case settingsGeneralAutocompleteOff + case settingsPrivateSearchAutocompleteOn + case settingsPrivateSearchAutocompleteOff + case settingsRecentlyVisitedOn + case settingsRecentlyVisitedOff case settingsAddressBarSelectorPressed case settingsAccessibilityOpen case settingsAccessiblityTextSize @@ -960,6 +971,21 @@ extension Pixel.Event { case .autofillLoginsStacked: return "m_autofill_logins_stacked" + case .autofillManagementOpened: + return "m_autofill_management_opened" + case .autofillManagementCopyUsername: + return "m_autofill_management_copy_username" + case .autofillManagementCopyPassword: + return "m_autofill_management_copy_password" + case .autofillManagementDeleteLogin: + return "m_autofill_management_delete_login" + case .autofillManagementDeleteAllLogins: + return "m_autofill_management_delete_all_logins" + case .autofillManagementSaveLogin: + return "m_autofill_management_save_login" + case .autofillManagementUpdateLogin: + return "m_autofill_management_update_login" + case .autofillMultipleAuthCallsTriggered: return "m_autofill_multiple_auth_calls_triggered" case .getDesktopCopy: return "m_get_desktop_copy" @@ -1069,6 +1095,9 @@ extension Pixel.Event { case .networkProtectionServerMigrationAttemptSuccess: return "m_netp_ev_server_migration_attempt_success" case .networkProtectionServerMigrationAttemptFailure: return "m_netp_ev_server_migration_attempt_failed" + case .networkProtectionDNSUpdateCustom: return "m_netp_ev_update_dns_custom" + case .networkProtectionDNSUpdateDefault: return "m_netp_ev_update_dns_default" + // MARK: remote messaging pixels case .remoteMessageShown: return "m_remote_message_shown" @@ -1335,12 +1364,12 @@ extension Pixel.Event { case .settingsWebTrackingProtectionOpen: return "m_settings_web_tracking_protection_open" case .settingsGpcOn: return "m_settings_gpc_on" case .settingsGpcOff: return "m_settings_gpc_off" - case .settingsAutocompleteOn: return "m_settings_autocomplete_on" - case .settingsAutocompleteOff: return "m_settings_autocomplete_off" - case .settingsRecentlyVisitedOn: return "m_settings_autocomplete_recently-visited_on" - case .settingsRecentlyVisitedOff: return "m_settings_autocomplete_recently-visited_off" case .settingsGeneralAutocompleteOn: return "m_settings_general_autocomplete_on" case .settingsGeneralAutocompleteOff: return "m_settings_general_autocomplete_off" + case .settingsPrivateSearchAutocompleteOn: return "m_settings_private_search_autocomplete_on" + case .settingsPrivateSearchAutocompleteOff: return "m_settings_private_search_autocomplete_off" + case .settingsRecentlyVisitedOn: return "m_settings_autocomplete_recently-visited_on" + case .settingsRecentlyVisitedOff: return "m_settings_autocomplete_recently-visited_off" case .settingsAddressBarSelectorPressed: return "m_settings_address_bar_selector_pressed" case .settingsAccessibilityOpen: return "m_settings_accessibility_open" case .settingsAccessiblityTextSize: return "m_settings_accessiblity_text_size" diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 841a05f81d..f85adb3585 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -150,8 +150,13 @@ public struct UserDefaultsWrapper { case pixelExperimentForBrokenSitesCohort = "com.duckduckgo.ios.pixel.experiment.for.broken.sites.cohort" case duckPlayerMode = "com.duckduckgo.ios.duckPlayerMode" + case duckPlayerAskModeOverlayHidden = "com.duckduckgo.ios.duckPlayerAskModeOverlayHidden" case vpnRedditWorkaroundInstalled = "com.duckduckgo.ios.vpn.workaroundInstalled" + + // Debug keys + + case debugNewTabPageSectionsEnabledKey = "com.duckduckgo.ios.debug.newTabPageSectionsEnabled" } private let key: Key diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 20d72cb58b..be1b537455 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -161,6 +161,7 @@ 31B1FA87286EFC5C00CA3C1C /* XCTestCaseExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B1FA86286EFC5C00CA3C1C /* XCTestCaseExtension.swift */; }; 31B2F11F287846320040427A /* NoMicPermissionAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B2F11E287846320040427A /* NoMicPermissionAlert.swift */; }; 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B524562715BB23002225AB /* WebJSAlert.swift */; }; + 31BC5F412C2B0B540004DF37 /* DuckPlayer.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 31BC5F402C2B0B540004DF37 /* DuckPlayer.xcassets */; }; 31C138A427A3352600FFD4B2 /* DownloadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C138A227A3350A00FFD4B2 /* DownloadTests.swift */; }; 31C138A827A3E9C900FFD4B2 /* URLDownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C138A727A3E9C900FFD4B2 /* URLDownloadSession.swift */; }; 31C138AC27A403CB00FFD4B2 /* DownloadManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31C138AB27A403CB00FFD4B2 /* DownloadManagerTests.swift */; }; @@ -248,6 +249,8 @@ 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */; }; 6F655BE22BAB289E00AC3597 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */; }; 6F8496412BC3D8EE00ADA54E /* OnboardingButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */; }; + 6FB1FE9E2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1FE9D2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift */; }; + 6FB1FEA22C256ACD0075B68B /* NewTabPageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1FEA12C256ACD0075B68B /* NewTabPageManager.swift */; }; 6FBF0F8B2BD7C0A900136CF0 /* AllProtectedCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBF0F8A2BD7C0A900136CF0 /* AllProtectedCell.swift */; }; 6FD1BAE42B87A107000C475C /* AdAttributionPixelReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD1BAE12B87A107000C475C /* AdAttributionPixelReporter.swift */; }; 6FD1BAE52B87A107000C475C /* AdAttributionReporterStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD1BAE22B87A107000C475C /* AdAttributionReporterStorage.swift */; }; @@ -256,6 +259,9 @@ 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */; }; 6FDB3F192BD11A4400F7A307 /* AutocompleteSuggestionsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB3F182BD11A4400F7A307 /* AutocompleteSuggestionsModel.swift */; }; 6FE095D82BD90AFB00490FF8 /* UniversalOmniBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE095D72BD90AFB00490FF8 /* UniversalOmniBarState.swift */; }; + 6FE127382C20492500EB5724 /* NewTabPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127372C20492500EB5724 /* NewTabPage.swift */; }; + 6FE1273A2C204BD000EB5724 /* NewTabPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127392C204BD000EB5724 /* NewTabPageView.swift */; }; + 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; @@ -650,6 +656,7 @@ B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95E728924730004ABA20 /* JSAlertController.storyboard */; }; B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */; }; BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD15DB842B959CFD00821457 /* BundleExtension.swift */; }; + BD2F39EB2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */; }; BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */; }; BD862E052B30DB250073E2EE /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */; }; BD862E072B30F5E30073E2EE /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */; }; @@ -657,6 +664,7 @@ BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */; }; BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC234F62B27F51100D3C798 /* UniquePixel.swift */; }; BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; }; + BDF8D0022C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */; }; BDFF031D2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */; }; BDFF03212BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */; }; BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */; }; @@ -1253,6 +1261,7 @@ 31B1FA86286EFC5C00CA3C1C /* XCTestCaseExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCaseExtension.swift; sourceTree = ""; }; 31B2F11E287846320040427A /* NoMicPermissionAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoMicPermissionAlert.swift; sourceTree = ""; }; 31B524562715BB23002225AB /* WebJSAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebJSAlert.swift; sourceTree = ""; }; + 31BC5F402C2B0B540004DF37 /* DuckPlayer.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DuckPlayer.xcassets; sourceTree = ""; }; 31C138A227A3350A00FFD4B2 /* DownloadTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadTests.swift; sourceTree = ""; }; 31C138A727A3E9C900FFD4B2 /* URLDownloadSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLDownloadSession.swift; sourceTree = ""; }; 31C138AB27A403CB00FFD4B2 /* DownloadManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManagerTests.swift; sourceTree = ""; }; @@ -1329,6 +1338,8 @@ 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = ""; }; 6F8496402BC3D8EE00ADA54E /* OnboardingButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingButtonsView.swift; sourceTree = ""; }; 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = ""; }; + 6FB1FE9D2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSectionsDebugView.swift; sourceTree = ""; }; + 6FB1FEA12C256ACD0075B68B /* NewTabPageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageManager.swift; sourceTree = ""; }; 6FBF0F8A2BD7C0A900136CF0 /* AllProtectedCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllProtectedCell.swift; sourceTree = ""; }; 6FD1BAE12B87A107000C475C /* AdAttributionPixelReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdAttributionPixelReporter.swift; path = AdAttribution/AdAttributionPixelReporter.swift; sourceTree = ""; }; 6FD1BAE22B87A107000C475C /* AdAttributionReporterStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdAttributionReporterStorage.swift; path = AdAttribution/AdAttributionReporterStorage.swift; sourceTree = ""; }; @@ -1337,6 +1348,9 @@ 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDisplayHelper.swift; sourceTree = ""; }; 6FDB3F182BD11A4400F7A307 /* AutocompleteSuggestionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteSuggestionsModel.swift; sourceTree = ""; }; 6FE095D72BD90AFB00490FF8 /* UniversalOmniBarState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalOmniBarState.swift; sourceTree = ""; }; + 6FE127372C20492500EB5724 /* NewTabPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPage.swift; sourceTree = ""; }; + 6FE127392C204BD000EB5724 /* NewTabPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageView.swift; sourceTree = ""; }; + 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageViewController.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; @@ -2258,12 +2272,14 @@ B6DFE6CF2BC7E47500A9CE59 /* SwiftLintTool.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftLintTool.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; B6DFE6D92BC7E61B00A9CE59 /* SwiftLintToolBundleConfiguration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SwiftLintToolBundleConfiguration.xcconfig; sourceTree = ""; }; BD15DB842B959CFD00821457 /* BundleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtension.swift; sourceTree = ""; }; + BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsView.swift; sourceTree = ""; }; BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackCategory.swift; sourceTree = ""; }; BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackSender.swift; sourceTree = ""; }; BD862E082B30F63E0073E2EE /* VPNMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNMetadataCollector.swift; sourceTree = ""; }; BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormView.swift; sourceTree = ""; }; BDC234F62B27F51100D3C798 /* UniquePixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniquePixel.swift; sourceTree = ""; }; + BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDNSSettingsViewModel.swift; sourceTree = ""; }; BDFF03192BA39C5A00F324C9 /* NetworkProtectionFeatureVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureVisibility.swift; sourceTree = ""; }; BDFF031C2BA3D2BD00F324C9 /* DefaultNetworkProtectionVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultNetworkProtectionVisibility.swift; sourceTree = ""; }; BDFF03202BA3D3CF00F324C9 /* NetworkProtectionVisibilityForTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVisibilityForTunnelProvider.swift; sourceTree = ""; }; @@ -3311,6 +3327,14 @@ name = Mock; sourceTree = ""; }; + 6FB1FE9C2C24D4060075B68B /* NewTabPageSectionsDebugView */ = { + isa = PBXGroup; + children = ( + 6FB1FE9D2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift */, + ); + name = NewTabPageSectionsDebugView; + sourceTree = ""; + }; 6FD1BAE02B87A0E8000C475C /* AdAttribution */ = { isa = PBXGroup; children = ( @@ -3321,6 +3345,17 @@ name = AdAttribution; sourceTree = ""; }; + 6FE127362C20436A00EB5724 /* HomeRedesign */ = { + isa = PBXGroup; + children = ( + 6FE127372C20492500EB5724 /* NewTabPage.swift */, + 6FE127392C204BD000EB5724 /* NewTabPageView.swift */, + 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */, + 6FB1FEA12C256ACD0075B68B /* NewTabPageManager.swift */, + ); + name = HomeRedesign; + sourceTree = ""; + }; 6FF9157F2B88E04F0042AC87 /* AdAttribution */ = { isa = PBXGroup; children = ( @@ -3705,6 +3740,7 @@ 858566F1252E55AE007501B8 /* Debug */ = { isa = PBXGroup; children = ( + 6FB1FE9C2C24D4060075B68B /* NewTabPageSectionsDebugView */, C14D43002B45D6CD00ACA4DC /* AutofillDebugViewController.swift */, 858566E7252E4F56007501B8 /* Debug.storyboard */, D6F93E3B2B4FFA97004C268D /* SubscriptionDebugViewController.swift */, @@ -4401,6 +4437,7 @@ D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */, D63FF8942C1B67E8006DE24D /* YoutubeOverlayUserScript.swift */, D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */, + 31BC5F402C2B0B540004DF37 /* DuckPlayer.xcassets */, ); path = DuckPlayer; sourceTree = ""; @@ -4629,6 +4666,8 @@ EE9D68D02AE00CF300B55EF4 /* NetworkProtectionVPNSettingsView.swift */, EE01EB3F2AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift */, 4B0F3F4F2B9BFF2100392892 /* NetworkProtectionFAQView.swift */, + BD2F39EA2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift */, + BDF8D0012C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift */, ); name = VPNSettings; sourceTree = ""; @@ -4824,6 +4863,7 @@ F13B4BF31F18C73A00814661 /* Home */ = { isa = PBXGroup; children = ( + 6FE127362C20436A00EB5724 /* HomeRedesign */, F4D9C4F8251179CC00814B71 /* HomeMessages */, 984147B324F0264300362052 /* Home.storyboard */, 853C5F5A21BFF0AE001F7A05 /* HomeCollectionView.swift */, @@ -5960,6 +6000,7 @@ 1E162615296D910F0004127F /* cookie-icon-animated-40-dark.json in Resources */, 85514FFD2372DA0100DBC528 /* ios13-home-row.mp4 in Resources */, 85F98F98296F4CB100742F4A /* SyncAssets.xcassets in Resources */, + 31BC5F412C2B0B540004DF37 /* DuckPlayer.xcassets in Resources */, AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */, 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */, AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */, @@ -6330,6 +6371,7 @@ C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, 31669B9A28020A460071CC18 /* SaveLoginViewModel.swift in Sources */, EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, + 6FB1FE9E2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, 851672D12BED1FC900592F24 /* AutocompleteView.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, @@ -6442,6 +6484,7 @@ D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */, BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */, F4147354283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift in Sources */, + 6FE1273A2C204BD000EB5724 /* NewTabPageView.swift in Sources */, 986DA94A24884B18004A7E39 /* WebViewTransition.swift in Sources */, 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, CB48D3322B90CE9F00631D8B /* UserBehaviorEvent.swift in Sources */, @@ -6451,6 +6494,7 @@ D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */, D668D9252B693778008E2FF2 /* SubscriptionITPView.swift in Sources */, C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */, + 6FE127382C20492500EB5724 /* NewTabPage.swift in Sources */, 982E5630222C3D5B008D861B /* FeedbackPickerViewController.swift in Sources */, 37FCAABC2992F592000E420A /* MultilineScrollableTextFix.swift in Sources */, 85DFEDED24C7CCA500973FE7 /* AppWidthObserver.swift in Sources */, @@ -6500,6 +6544,7 @@ D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */, EE458D142ABB652900FC651A /* NetworkProtectionDebugUtilities.swift in Sources */, 8528AE7C212EF4A200D0BD74 /* AppRatingPrompt.swift in Sources */, + BD2F39EB2C19F955005B19E7 /* NetworkProtectionDNSSettingsView.swift in Sources */, CB2A7EEF283D185100885F67 /* RulesCompilationMonitor.swift in Sources */, 1DEAADF22BA4716C00E25A97 /* SettingsStatus.swift in Sources */, C18ED43C2AB8364400BF3805 /* FileTextPreviewDebugViewController.swift in Sources */, @@ -6735,6 +6780,7 @@ CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */, AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, + BDF8D0022C1B87F4003E3B27 /* NetworkProtectionDNSSettingsViewModel.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, 8590CB67268A2E520089F6BF /* RootDebugViewController.swift in Sources */, 1DEAADEA2BA4539800E25A97 /* SettingsAppearanceView.swift in Sources */, @@ -6748,6 +6794,7 @@ 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */, 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, 98D98A8F25ED952F00D8E3DF /* BrowsingMenuButton.swift in Sources */, + 6FB1FEA22C256ACD0075B68B /* NewTabPageManager.swift in Sources */, 9865DFF922A8220D00D27829 /* FavoritesOverlay.swift in Sources */, 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, @@ -6760,6 +6807,7 @@ 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, CBBB9A192BED441400BEAC71 /* PixelExperimentForBrokenSites.swift in Sources */, F17669D71E43401C003D3222 /* MainViewController.swift in Sources */, + 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */, 984D60B2222A1284003B9E3B /* FeedbackFormViewController.swift in Sources */, 31A42564285A09E800049386 /* FaviconView.swift in Sources */, 85374D3821AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift in Sources */, @@ -7969,7 +8017,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8006,7 +8054,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8098,7 +8146,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8126,7 +8174,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8276,7 +8324,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8302,7 +8350,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8372,7 +8420,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8407,7 +8455,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8441,7 +8489,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8472,7 +8520,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8787,7 +8835,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8819,7 +8867,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8848,7 +8896,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8882,7 +8930,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8913,7 +8961,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8946,11 +8994,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9186,7 +9234,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9214,7 +9262,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9247,7 +9295,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9285,7 +9333,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9321,7 +9369,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9356,11 +9404,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9536,11 +9584,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9569,10 +9617,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9774,7 +9822,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 160.0.0; + version = 161.1.2; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1b4607328c..d8ca804f52 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "70ea98c091350a5dde9d6e5d6b05f064e5a36ec0", - "version" : "160.0.0" + "revision" : "cde956dec771bb7196ab3f4b07e00254e433d58d", + "version" : "161.1.2" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "10aeff1ec7f533d1705233a9b14f9393a699b1c0", - "version" : "11.0.2" + "revision" : "2b81745565db09eee8c1cd44d38eefa1011a9f0a", + "version" : "12.0.1" } }, { diff --git a/DuckDuckGo/AppDelegate+AppDeepLinks.swift b/DuckDuckGo/AppDelegate+AppDeepLinks.swift index 1e0975f35a..3aac5b325b 100644 --- a/DuckDuckGo/AppDelegate+AppDeepLinks.swift +++ b/DuckDuckGo/AppDelegate+AppDeepLinks.swift @@ -57,17 +57,21 @@ extension AppDelegate { #endif case .openPasswords: - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { - mainViewController.launchAutofillLogins(openSearch: true) - } + var source: AutofillSettingsSource = .homeScreenWidget + if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItems = components.queryItems, queryItems.first(where: { $0.name == "ls" }) != nil { Pixel.fire(pixel: .autofillLoginsLaunchWidgetLock) + source = .lockScreenWidget } else { Pixel.fire(pixel: .autofillLoginsLaunchWidgetHome) } + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { + mainViewController.launchAutofillLogins(openSearch: true, source: source) + } + default: guard app.applicationState == .active, let currentTab = mainViewController.currentTab else { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 75135b344a..81b50dd257 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -847,7 +847,7 @@ import WebKit mainViewController?.clearNavigationStack() // Give the `clearNavigationStack` call time to complete. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { [weak self] in - self?.mainViewController?.launchAutofillLogins(openSearch: true) + self?.mainViewController?.launchAutofillLogins(openSearch: true, source: .appIconShortcut) } Pixel.fire(pixel: .autofillLoginsLaunchAppShortcut) return diff --git a/DuckDuckGo/AppSettings.swift b/DuckDuckGo/AppSettings.swift index d312f57fb1..9e90a0eae1 100644 --- a/DuckDuckGo/AppSettings.swift +++ b/DuckDuckGo/AppSettings.swift @@ -42,7 +42,7 @@ enum AddressBarPosition: String, CaseIterable, CustomStringConvertible { } } -protocol AppSettings: AnyObject { +protocol AppSettings: AnyObject, AppDebugSettings { var autocomplete: Bool { get set } var recentlyVisitedSites: Bool { get set } var currentThemeName: ThemeName { get set } @@ -82,3 +82,7 @@ protocol AppSettings: AnyObject { var duckPlayerMode: DuckPlayerMode { get set } } + +protocol AppDebugSettings { + var newTabPageSectionsEnabled: Bool { get set } +} diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index c7dcbd7ad1..971b7d3090 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -77,6 +77,7 @@ public class AppUserDefaults: AppSettings { static let crashCollectionOptInStatus = "com.duckduckgo.ios.crashCollectionOptInStatus" static let duckPlayerMode = "com.duckduckgo.ios.duckPlayerMode" + static let duckPlayerAskModeOverlayHidden = "com.duckduckgo.ios.duckPlayerAskModeOverlayHidden" } private struct DebugKeys { @@ -375,6 +376,9 @@ public class AppUserDefaults: AppSettings { userDefaults?.setValue(newValue.rawValue, forKey: Keys.crashCollectionOptInStatus) } } + + @UserDefaultsWrapper(key: .debugNewTabPageSectionsEnabledKey, defaultValue: false) + var newTabPageSectionsEnabled: Bool var duckPlayerMode: DuckPlayerMode { get { @@ -385,7 +389,9 @@ public class AppUserDefaults: AppSettings { return .alwaysAsk } set { + // Here we set both the DuckPlayer mode and the overlayInteracte userDefaults?.set(newValue.stringValue, forKey: Keys.duckPlayerMode) + userDefaults?.set(false, forKey: Keys.duckPlayerAskModeOverlayHidden) } } } diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index ccebe511e4..1f5f9dd659 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -247,9 +247,11 @@ final class AutofillLoginDetailsViewModel: ObservableObject { case .username: message = UserText.autofillCopyToastUsernameCopied UIPasteboard.general.string = username + Pixel.fire(pixel: .autofillManagementCopyUsername) case .password: message = UserText.autofillCopyToastPasswordCopied UIPasteboard.general.string = password + Pixel.fire(pixel: .autofillManagementCopyPassword) case .address: message = UserText.autofillCopyToastAddressCopied UIPasteboard.general.string = address diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index cc4f4f6063..c30b49c694 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -27,6 +27,16 @@ import DesignResourcesKit import SwiftUI // swiftlint:disable file_length type_body_length + +enum AutofillSettingsSource: String { + case settings + case overflow = "overflow_menu" + case sync + case appIconShortcut = "app_icon_shortcut" + case homeScreenWidget = "home_screen_widget" + case lockScreenWidget = "lock_screen_widget" +} + protocol AutofillLoginSettingsListViewControllerDelegate: AnyObject { func autofillLoginSettingsListViewControllerDidFinish(_ controller: AutofillLoginSettingsListViewController) } @@ -165,7 +175,8 @@ final class AutofillLoginSettingsListViewController: UIViewController { syncService: DDGSyncing, syncDataProviders: SyncDataProviders, selectedAccount: SecureVaultModels.WebsiteAccount?, - openSearch: Bool = false) { + openSearch: Bool = false, + source: AutofillSettingsSource) { let secureVault = try? AutofillSecureVaultFactory.makeVault(reporter: SecureVaultReporter()) if secureVault == nil { os_log("Failed to make vault") @@ -191,6 +202,8 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } } + + Pixel.fire(pixel: .autofillManagementOpened, withAdditionalParameters: ["source": source.rawValue]) } required init?(coder: NSCoder) { @@ -420,6 +433,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { NotificationCenter.default.post(name: FireproofFaviconUpdater.deleteFireproofFaviconNotification, object: nil, userInfo: [FireproofFaviconUpdater.UserInfoKeys.faviconDomain: domain]) + Pixel.fire(pixel: .autofillManagementDeleteLogin) }) } @@ -492,6 +506,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { self.syncService.scheduler.notifyDataChanged() self.viewModel.resetNeverPromptWebsites() self.viewModel.updateData() + Pixel.fire(pixel: .autofillManagementDeleteAllLogins) } } else { self.viewModel.undoClearAllAccounts() @@ -882,6 +897,9 @@ extension AutofillLoginSettingsListViewController: AutofillLoginDetailsViewContr if let account = account { showAccountDetails(account) + Pixel.fire(pixel: .autofillManagementSaveLogin) + } else { + Pixel.fire(pixel: .autofillManagementUpdateLogin) } } diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index eaeae18022..d7c8485d06 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -1,9 +1,9 @@ - + - + @@ -258,9 +258,18 @@ - + + + + + + + + + + @@ -268,7 +277,7 @@ - + @@ -277,7 +286,7 @@ - + @@ -286,7 +295,7 @@ - + @@ -295,7 +304,7 @@ - + @@ -851,34 +860,34 @@ - + - + - + - + diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 79444d9ac9..bbec3ff700 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -23,6 +23,7 @@ import Combine import Foundation import WebKit import UserScript +import Core enum DuckPlayerMode: Equatable, Codable, CustomStringConvertible, CaseIterable { case enabled, alwaysAsk, disabled @@ -41,28 +42,6 @@ enum DuckPlayerMode: Equatable, Codable, CustomStringConvertible, CaseIterable { return UserText.duckPlayerDisabledLabel } } - - init(_ duckPlayerMode: Bool?) { - switch duckPlayerMode { - case true: - self = .enabled - case false: - self = .disabled - default: - self = .alwaysAsk - } - } - - var boolValue: Bool? { - switch self { - case .enabled: - return true - case .alwaysAsk: - return nil - case .disabled: - return false - } - } var stringValue: String { switch self { @@ -112,73 +91,83 @@ struct InitialSetupSettings: Codable { public struct UserValues: Codable { enum CodingKeys: String, CodingKey { case duckPlayerMode = "privatePlayerMode" - case overlayInteracted + case askModeOverlayHidden = "overlayInteracted" } let duckPlayerMode: DuckPlayerMode - let overlayInteracted: Bool + let askModeOverlayHidden: Bool } -final class DuckPlayer { - static let usesSimulatedRequests: Bool = { - if #available(macOS 12.0, *) { - return true - } else { - return false +final class DuckPlayerSettings { + + private var appSettings: AppSettings + + init(appSettings: AppSettings = AppDependencyProvider.shared.appSettings) { + self.appSettings = appSettings + } + + public struct OriginDomains { + static let duckduckgo = "duckduckgo.com" + static let youtubeWWW = "www.youtube.com" + static let youtube = "youtube.com" + static let youtubeMobile = "m.youtube.com" + } + + var mode: DuckPlayerMode { + get { + appSettings.duckPlayerMode + } set { + appSettings.duckPlayerMode = newValue } - }() + } + + @UserDefaultsWrapper(key: .duckPlayerAskModeOverlayHidden, defaultValue: false) + var askModeOverlayHidden: Bool + +} +final class DuckPlayer { + static let duckPlayerHost: String = "player" static let commonName = "Duck Player" + + private var settings: DuckPlayerSettings - static let shared = DuckPlayer() - - var isAvailable: Bool { - return true - } - - @Published var mode: DuckPlayerMode - - var overlayInteracted: Bool { - true - } - init() { - mode = .enabled + init(settings: DuckPlayerSettings = DuckPlayerSettings()) { + self.settings = settings } - + // MARK: - Common Message Handlers - public func handleSetUserValuesMessage( - from origin: YoutubeOverlayUserScript.MessageOrigin - ) -> (_ params: Any, _ message: UserScriptMessage) -> Encodable? { - - return { [weak self] params, _ -> Encodable? in - guard let self else { - return nil - } - guard let userValues: UserValues = DecodableHelper.decode(from: params) else { - assertionFailure("YoutubeOverlayUserScript: expected JSON representation of UserValues") - return nil - } - - return self.encodeUserValues() + public func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? { + guard let userValues: UserValues = DecodableHelper.decode(from: params) else { + assertionFailure("DuckPlayer: expected JSON representation of UserValues") + return nil } + settings.mode = userValues.duckPlayerMode + settings.askModeOverlayHidden = userValues.askModeOverlayHidden + return userValues } - - public func handleGetUserValues(params: Any, message: UserScriptMessage) -> Encodable? { + + public func getUserValues(params: Any, message: WKScriptMessage) -> Encodable? { encodeUserValues() } + + @MainActor + public func openVideoInDuckPlayer(url: URL, webView: WKWebView) { + webView.load(URLRequest(url: url)) + } - public func initialSetup(with webView: WKWebView?) -> (_ params: Any, _ message: UserScriptMessage) async -> Encodable? { - return { _, _ in - return await self.encodedSettings(with: webView) - } + @MainActor + public func initialSetup(params: Any, message: WKScriptMessage) async -> Encodable? { + let webView = message.webView + return await self.encodedSettings(with: webView) } private func encodeUserValues() -> UserValues { UserValues( - duckPlayerMode: .enabled, - overlayInteracted: true + duckPlayerMode: settings.mode, + askModeOverlayHidden: settings.askModeOverlayHidden ) } @@ -193,7 +182,4 @@ final class DuckPlayer { return InitialSetupSettings(userValues: userValues, settings: playerSettings) } - // MARK: - Private - - private static let websiteTitlePrefix = "\(commonName) - " } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/Contents.json b/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/SettingsDuckPlayerHero.imageset/Contents.json b/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/SettingsDuckPlayerHero.imageset/Contents.json new file mode 100644 index 0000000000..a19bd36723 --- /dev/null +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/SettingsDuckPlayerHero.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "SettingsDuckPlayerHero.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/SettingsDuckPlayerHero.imageset/SettingsDuckPlayerHero.pdf b/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/SettingsDuckPlayerHero.imageset/SettingsDuckPlayerHero.pdf new file mode 100644 index 0000000000..d145b378d0 Binary files /dev/null and b/DuckDuckGo/DuckPlayer/DuckPlayer.xcassets/SettingsDuckPlayerHero.imageset/SettingsDuckPlayerHero.pdf differ diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index 4a761d9fec..ee09c20949 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -21,38 +21,54 @@ import Foundation import WebKit import Common import UserScript -// import PixelKit - -protocol YoutubeOverlayUserScriptDelegate: AnyObject { - func youtubeOverlayUserScriptDidRequestDuckPlayer(with url: URL, in webView: WKWebView) -} final class YoutubeOverlayUserScript: NSObject, Subfeature { - + + private var duckPlayer: DuckPlayer + + struct Constants { + static let featureName = "duckPlayer" + } + + init(duckPlayer: DuckPlayer) { + self.duckPlayer = duckPlayer + } + enum MessageOrigin { case duckPlayer, serpOverlay, youtubeOverlay init?(url: URL) { switch url.host { - case "duckduckgo.com": + case DuckPlayerSettings.OriginDomains.duckduckgo: self = .serpOverlay - case "www.youtube.com": + case DuckPlayerSettings.OriginDomains.youtubeMobile: + self = .youtubeOverlay + case DuckPlayerSettings.OriginDomains.youtube: + self = .youtubeOverlay + case DuckPlayerSettings.OriginDomains.youtubeWWW: self = .youtubeOverlay default: return nil } } } + + struct Handlers { + static let setUserValues = "setUserValues" + static let getUserValues = "getUserValues" + static let openDuckPlayer = "openDuckPlayer" + static let sendDuckPlayerPixel = "sendDuckPlayerPixel" + } - // let duckPlayerPreferences: DuckPlayerPreferences weak var broker: UserScriptMessageBroker? - weak var delegate: YoutubeOverlayUserScriptDelegate? weak var webView: WKWebView? + let messageOriginPolicy: MessageOriginPolicy = .only(rules: [ - .exact(hostname: "www.youtube.com"), - .exact(hostname: "duckduckgo.com") + .exact(hostname: DuckPlayerSettings.OriginDomains.duckduckgo), + .exact(hostname: DuckPlayerSettings.OriginDomains.youtube), + .exact(hostname: DuckPlayerSettings.OriginDomains.youtubeMobile) ]) - public var featureName: String = "duckPlayer" + public var featureName: String = Constants.featureName // MARK: - Subfeature @@ -62,29 +78,19 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { // MARK: - MessageNames - enum MessageNames: String, CaseIterable { - case setUserValues - case getUserValues - case openDuckPlayer - case sendDuckPlayerPixel - } - func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - switch MessageNames(rawValue: methodName) { - case .setUserValues: - guard let url = webView?.url, let origin = MessageOrigin(url: url) else { - assertionFailure("YoutubeOverlayUserScript: Unexpected message origin: \(String(describing: webView?.url))") - return nil - } - return DuckPlayer.shared.handleSetUserValuesMessage(from: origin) - case .getUserValues: - return DuckPlayer.shared.handleGetUserValues - case .openDuckPlayer: - return handleOpenDuckPlayer - case .sendDuckPlayerPixel: + switch methodName { + case Handlers.setUserValues: + return duckPlayer.setUserValues + case Handlers.getUserValues: + return duckPlayer.getUserValues + case Handlers.openDuckPlayer: + return openDuckPlayer + case Handlers.sendDuckPlayerPixel: return handleSendJSPixel default: assertionFailure("YoutubeOverlayUserScript: Failed to parse User Script message: \(methodName)") + // TODO: Send pixel here return nil } } @@ -97,19 +103,18 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { } // MARK: - Private Methods - + @MainActor - private func handleOpenDuckPlayer(params: Any, message: UserScriptMessage) -> Encodable? { + private func openDuckPlayer(params: Any, original: WKScriptMessage) -> Encodable? { guard let dict = params as? [String: Any], - let href = dict["href"] as? String, - let url = href.url, - url.isDuckURLScheme, - let webView = message.messageWebView - else { - assertionFailure("YoutubeOverlayUserScript: expected duck:// URL") + let href = dict["href"] as? String, + let url = href.url, + let webView = original.webView else { + assertionFailure("Could not parse WKMessage to obtain video details") + // TODO: Send Pixel Here return nil } - self.delegate?.youtubeOverlayUserScriptDidRequestDuckPlayer(with: url, in: webView) + duckPlayer.openVideoInDuckPlayer(url: url, webView: webView) return nil } @@ -127,8 +132,7 @@ extension YoutubeOverlayUserScript { return nil } let pixelName = parameters["pixelName"] as? String - - // To be implemented once final pixels are defined or updated + return nil } diff --git a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift index 7ec7e1f768..b451c46e07 100644 --- a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift @@ -22,15 +22,29 @@ import Common import UserScript final class YoutubePlayerUserScript: NSObject, Subfeature { - + + private var duckPlayer: DuckPlayer + + struct Constants { + static let featureName = "duckPlayerPage" + } + + struct Handlers { + static let setUserValues = "setUserValues" + static let getUserValues = "getUserValues" + static let initialSetup = "initialSetup" + } + + init(duckPlayer: DuckPlayer) { + self.duckPlayer = duckPlayer + } + weak var broker: UserScriptMessageBroker? weak var webView: WKWebView? - - var isEnabled: Bool = false - - // this isn't an issue to be set to 'all' because the page + + // Allow all origins as this is a 'specialPage' public let messageOriginPolicy: MessageOriginPolicy = .all - public let featureName: String = "duckPlayerPage" + public let featureName: String = Constants.featureName // MARK: - Subfeature @@ -38,22 +52,14 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { self.broker = broker } - // MARK: - MessageNames - - enum MessageNames: String, CaseIterable { - case setUserValues - case getUserValues - case initialSetup - } - func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { - switch MessageNames(rawValue: methodName) { - case .getUserValues: - return DuckPlayer.shared.handleGetUserValues - case .setUserValues: - return DuckPlayer.shared.handleSetUserValuesMessage(from: .duckPlayer) - case .initialSetup: - return DuckPlayer.shared.initialSetup(with: webView) + switch methodName { + case Handlers.getUserValues: + return duckPlayer.getUserValues + case Handlers.setUserValues: + return duckPlayer.setUserValues + case Handlers.initialSetup: + return duckPlayer.initialSetup default: assertionFailure("YoutubePlayerUserScript: Failed to parse User Script message: \(methodName)") return nil diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift index fd116f15e6..b74eca8b17 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift @@ -25,8 +25,7 @@ import NetworkProtection @available(iOS 15.0, *) struct VPNFeedbackFormCategoryView: View { @Environment(\.dismiss) private var dismiss - let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver, - tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) + let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver) var body: some View { VStack { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index cf4e336a5a..8345ec44b5 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -61,11 +61,12 @@ struct VPNMetadata: Encodable { let excludeLocalNetworksEnabled: Bool let notifyStatusChangesEnabled: Bool let selectedServer: String + let customDNS: Bool } struct PrivacyProInfo: Encodable { - let hasToken: Bool - let subscriptionActive: Bool + let hasPrivacyProAccount: Bool + let hasVPNEntitlement: Bool } struct LastDisconnectError: Encodable { @@ -113,18 +114,18 @@ protocol VPNMetadataCollector { final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver - private let tokenStore: NetworkProtectionTokenStore + private let accountManager: AccountManager private let settings: VPNSettings private let defaults: UserDefaults init(statusObserver: ConnectionStatusObserver, serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), - tokenStore: NetworkProtectionTokenStore, + accountManager: AccountManager = AppDependencyProvider.shared.subscriptionManager.accountManager, settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { self.statusObserver = statusObserver self.serverInfoObserver = serverInfoObserver - self.tokenStore = tokenStore + self.accountManager = accountManager self.settings = settings self.defaults = defaults } @@ -135,7 +136,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { let networkInfoMetadata = await collectNetworkInformation() let vpnState = await collectVPNState() let vpnSettingsState = collectVPNSettingsState() - let privacyProInfo = collectPrivacyProInfo() + let privacyProInfo = await collectPrivacyProInfo() return VPNMetadata( appInfo: appInfoMetadata, @@ -247,24 +248,19 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { enforceRoutesEnabled: settings.enforceRoutes, excludeLocalNetworksEnabled: settings.excludeLocalNetworks, notifyStatusChangesEnabled: settings.notifyStatusChanges, - selectedServer: settings.selectedServer.stringValue ?? "automatic" + selectedServer: settings.selectedServer.stringValue ?? "automatic", + customDNS: settings.dnsSettings.usesCustomDNS ) } - func collectPrivacyProInfo() -> VPNMetadata.PrivacyProInfo { - var hasToken: Bool { - guard let token = try? tokenStore.fetchToken(), - !token.hasPrefix(NetworkProtectionKeychainTokenStore.authTokenPrefix) else { - return false - } - return true - } - + func collectPrivacyProInfo() async -> VPNMetadata.PrivacyProInfo { + let hasVPNEntitlement = (try? await accountManager.hasEntitlement(forProductName: .networkProtection).get()) ?? false return .init( - hasToken: hasToken, - subscriptionActive: AppDependencyProvider.shared.subscriptionManager.accountManager.isUserAuthenticated + hasPrivacyProAccount: accountManager.isUserAuthenticated, + hasVPNEntitlement: hasVPNEntitlement ) } + } private extension NSError { diff --git a/DuckDuckGo/HomeScreenTransition.swift b/DuckDuckGo/HomeScreenTransition.swift index 4ca8d34b1d..72763a52c6 100644 --- a/DuckDuckGo/HomeScreenTransition.swift +++ b/DuckDuckGo/HomeScreenTransition.swift @@ -101,7 +101,7 @@ class FromHomeScreenTransition: HomeScreenTransition { tabSwitcherViewController.view.frame = transitionContext.finalFrame(for: tabSwitcherViewController) tabSwitcherViewController.prepareForPresentation() - guard let homeScreen = mainViewController.homeController, + guard let homeScreen = mainViewController.homeViewController, let tab = mainViewController.tabManager.model.currentTab, let rowIndex = tabSwitcherViewController.tabsModel.indexOf(tab: tab), let layoutAttr = tabSwitcherViewController.collectionView.layoutAttributesForItem(at: IndexPath(row: rowIndex, section: 0)) @@ -176,7 +176,7 @@ class ToHomeScreenTransition: HomeScreenTransition { prepareSubviews(using: transitionContext) guard let mainViewController = transitionContext.viewController(forKey: .to) as? MainViewController, - let homeScreen = mainViewController.homeController, + let homeScreen = mainViewController.homeViewController, let tab = mainViewController.tabManager.model.currentTab, let rowIndex = tabSwitcherViewController.tabsModel.indexOf(tab: tab), let layoutAttr = tabSwitcherViewController.collectionView.layoutAttributesForItem(at: IndexPath(row: rowIndex, section: 0)) diff --git a/DuckDuckGo/HomeViewController.swift b/DuckDuckGo/HomeViewController.swift index 513aff13bb..cc2e72cdcc 100644 --- a/DuckDuckGo/HomeViewController.swift +++ b/DuckDuckGo/HomeViewController.swift @@ -26,8 +26,9 @@ import DDGSync import Persistence import RemoteMessaging -class HomeViewController: UIViewController { - + +class HomeViewController: UIViewController, NewTabPage { + @IBOutlet weak var ctaContainerBottom: NSLayoutConstraint! @IBOutlet weak var ctaContainer: UIView! @@ -56,7 +57,11 @@ class HomeViewController: UIViewController { chromeDelegate?.tabBarContainer.alpha = percent } } - + + var isDragging: Bool { + collectionView.isDragging + } + weak var delegate: HomeControllerDelegate? weak var chromeDelegate: BrowserChromeDelegate? @@ -254,7 +259,11 @@ class HomeViewController: UIViewController { func onboardingCompleted() { showNextDaxDialog() } - + + func reloadFavorites() { + collectionView.reloadData() + } + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { super.prepare(for: segue, sender: sender) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 773b3fc5d8..e04428bd65 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -82,10 +82,15 @@ class MainViewController: UIViewController { return emailManager }() - var homeController: HomeViewController? + var homeViewController: HomeViewController? + var newTabPageViewController: NewTabPageViewController? + var homeController: NewTabPage? { + homeViewController ?? newTabPageViewController + } var tabsBarController: TabsBarViewController? var suggestionTrayController: SuggestionTrayViewController? + let homeTabManager: NewTabPageManager let tabManager: TabManager let previewsSource: TabPreviewsSource let appSettings: AppSettings @@ -199,6 +204,7 @@ class MainViewController: UIViewController { historyManager: historyManager, syncService: syncService) self.syncPausedStateManager = syncPausedStateManager + self.homeTabManager = NewTabPageManager() super.init(nibName: nil, bundle: nil) @@ -446,7 +452,7 @@ class MainViewController: UIViewController { @objc private func keyboardWillHide() { - if homeController?.collectionView.isDragging == true, keyboardShowing { + if homeController?.isDragging == true, keyboardShowing { Pixel.fire(pixel: .addressBarGestureDismiss) } } @@ -627,7 +633,7 @@ class MainViewController: UIViewController { } self.menuBookmarksViewModel.favoritesDisplayMode = self.appSettings.favoritesDisplayMode self.favoritesViewModel.favoritesDisplayMode = self.appSettings.favoritesDisplayMode - self.homeController?.collectionView.reloadData() + self.homeController?.reloadFavorites() WidgetCenter.shared.reloadAllTimelines() } } @@ -642,7 +648,7 @@ class MainViewController: UIViewController { .sink { [weak self] _ in self?.favoritesViewModel.reloadData() DispatchQueue.main.async { - self?.homeController?.collectionView.reloadData() + self?.homeController?.reloadFavorites() } } } @@ -726,6 +732,8 @@ class MainViewController: UIViewController { } fileprivate func attachHomeScreen() { + guard !autoClearInProgress else { return } + viewCoordinator.logoContainer.isHidden = false findInPageView.isHidden = true chromeManager.detach() @@ -739,18 +747,23 @@ class MainViewController: UIViewController { fatalError("No tab model") } - let controller = HomeViewController.loadFromStoryboard(model: tabModel, - favoritesViewModel: favoritesViewModel, - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders) - - homeController = controller - - controller.chromeDelegate = self - controller.delegate = self + if homeTabManager.isNewTabPageSectionsEnabled { + let controller = NewTabPageViewController(rootView: NewTabPageView()) + newTabPageViewController = controller + addToContentContainer(controller: controller) + viewCoordinator.logoContainer.isHidden = true + } else { + let controller = HomeViewController.loadFromStoryboard(model: tabModel, + favoritesViewModel: favoritesViewModel, + appSettings: appSettings, + syncService: syncService, + syncDataProviders: syncDataProviders) - addToContentContainer(controller: controller) + controller.delegate = self + controller.chromeDelegate = self + homeViewController = controller + addToContentContainer(controller: controller) + } refreshControls() syncService.scheduler.requestSyncImmediately() @@ -759,7 +772,8 @@ class MainViewController: UIViewController { fileprivate func removeHomeScreen() { homeController?.willMove(toParent: nil) homeController?.dismiss() - homeController = nil + homeViewController = nil + newTabPageViewController = nil } @IBAction func onFirePressed() { @@ -967,9 +981,9 @@ class MainViewController: UIViewController { addChild(controller) viewCoordinator.contentContainer.subviews.forEach { $0.removeFromSuperview() } viewCoordinator.contentContainer.addSubview(controller.view) + controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight] controller.view.frame = viewCoordinator.contentContainer.bounds controller.didMove(toParent: self) - } fileprivate func updateCurrentTab() { @@ -1170,7 +1184,7 @@ class MainViewController: UIViewController { suggestionTrayController?.didHide() } - func launchAutofillLogins(with currentTabUrl: URL? = nil, openSearch: Bool = false) { + func launchAutofillLogins(with currentTabUrl: URL? = nil, openSearch: Bool = false, source: AutofillSettingsSource) { let appSettings = AppDependencyProvider.shared.appSettings let autofillSettingsViewController = AutofillLoginSettingsListViewController( appSettings: appSettings, @@ -1178,7 +1192,8 @@ class MainViewController: UIViewController { syncService: syncService, syncDataProviders: syncDataProviders, selectedAccount: nil, - openSearch: openSearch + openSearch: openSearch, + source: source ) autofillSettingsViewController.delegate = self let navigationController = UINavigationController(rootViewController: autofillSettingsViewController) @@ -1903,7 +1918,7 @@ extension MainViewController: FavoritesOverlayDelegate { func favoritesOverlay(_ overlay: FavoritesOverlay, didSelect favorite: BookmarkEntity) { guard let url = favorite.urlObject else { return } Pixel.fire(pixel: .favoriteLaunchedWebsite) - homeController?.chromeDelegate = nil + homeViewController?.chromeDelegate = nil dismissOmniBar() Favicons.shared.loadFavicon(forDomain: url.host, intoCache: .fireproof, fromCache: .tabs) if url.isBookmarklet() { @@ -1926,7 +1941,7 @@ extension MainViewController: AutocompleteViewControllerDelegate { } func autocomplete(selectedSuggestion suggestion: Suggestion) { - homeController?.chromeDelegate = nil + homeViewController?.chromeDelegate = nil dismissOmniBar() viewCoordinator.omniBar.cancel() switch suggestion { @@ -2169,7 +2184,7 @@ extension MainViewController: TabDelegate { } func tabDidRequestAutofillLogins(tab: TabViewController) { - launchAutofillLogins(with: currentTab?.url) + launchAutofillLogins(with: currentTab?.url, source: .overflow) } func tabDidRequestSettings(tab: TabViewController) { diff --git a/DuckDuckGo/NetworkProtectionDNSSettingsView.swift b/DuckDuckGo/NetworkProtectionDNSSettingsView.swift new file mode 100644 index 0000000000..415226eeba --- /dev/null +++ b/DuckDuckGo/NetworkProtectionDNSSettingsView.swift @@ -0,0 +1,139 @@ +// +// NetworkProtectionDNSSettingsView.swift +// DuckDuckGo +// +// Copyright © 2024 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. +// + +#if NETWORK_PROTECTION + +import SwiftUI +import NetworkProtection + +@available(iOS 15.0, *) +struct NetworkProtectionDNSSettingsView: View { + @StateObject var viewModel = NetworkProtectionDNSSettingsViewModel(settings: VPNSettings(defaults: .networkProtectionGroupDefaults)) + @Environment(\.dismiss) private var dismiss + @FocusState private var isCustomDNSServerFocused: Bool + + var body: some View { + VStack { + List { + Section { + ChecklistItem(isSelected: !viewModel.isCustomDNSSelected) { + viewModel.toggleDNSSettings() + } label: { + Text(UserText.vpnSettingDNSServerOptionRecommended) + .daxBodyRegular() + .foregroundStyle(Color(designSystemColor: .textPrimary)) + } + ChecklistItem(isSelected: viewModel.isCustomDNSSelected) { + viewModel.toggleDNSSettings() + } label: { + Text(UserText.vpnSettingDNSServerOptionCustom) + .daxBodyRegular() + .foregroundStyle(Color(designSystemColor: .textPrimary)) + } + } footer: { + if !viewModel.isCustomDNSSelected { + Text(UserText.netPSecureDNSSettingFooter) + .daxFootnoteRegular() + .foregroundColor(.init(designSystemColor: .textSecondary)) + } + } + .listRowBackground(Color(designSystemColor: .surface)) + .onChange(of: viewModel.isCustomDNSSelected) { _ in + viewModel.updateApplyButtonState() + } + + if viewModel.isCustomDNSSelected { + customDNSSection() + } + } + } + .applyInsetGroupedListStyle() + .navigationTitle(UserText.vpnSettingDNSServerScreenTitle) + .toolbar { + ToolbarItemGroup(placement: .topBarTrailing) { + Button { + viewModel.applyDNSSettings() + dismiss() + } label: { + Text(UserText.vpnSettingDNSServerApplyButtonTitle) + } + .disabled(!viewModel.isApplyButtonEnabled) + } + } + } + + func customDNSSection() -> some View { + Section { + HStack { + Text(UserText.vpnSettingDNSServerIPv4Title) + .daxBodyRegular() + .foregroundColor(.init(designSystemColor: .textPrimary)) + Spacer(minLength: 2) + TextField("0.0.0.0", text: $viewModel.customDNSServers) + .daxBodyRegular() + .foregroundColor(.init(designSystemColor: .textSecondary)) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .keyboardType(.numbersAndPunctuation) + .multilineTextAlignment(.trailing) + .focused($isCustomDNSServerFocused) + .onChange(of: viewModel.customDNSServers) { _ in + viewModel.updateApplyButtonState() + } + } + } header: { + Text(UserText.vpnSettingDNSSectionHeader) + } footer: { + Text(UserText.vpnSettingDNSSectionDisclaimer) + .foregroundColor(.init(designSystemColor: .textSecondary)) + } + .listRowBackground(Color(designSystemColor: .surface)) + .onAppear { + isCustomDNSServerFocused = true + } + } +} + +@available(iOS 15, *) +private struct ChecklistItem: View where Content: View { + let isSelected: Bool + let action: () -> Void + @ViewBuilder let label: () -> Content + + var body: some View { + Button( + action: action, + label: { + HStack(spacing: 12) { + label() + Spacer() + Image(systemName: "checkmark") + .tint(.init(designSystemColor: .accent)) + .if(!isSelected) { + $0.hidden() + } + } + } + ) + .tint(Color(designSystemColor: .textPrimary)) + .listRowInsets(EdgeInsets(top: 14, leading: 16, bottom: 14, trailing: 16)) + } +} + +#endif diff --git a/DuckDuckGo/NetworkProtectionDNSSettingsViewModel.swift b/DuckDuckGo/NetworkProtectionDNSSettingsViewModel.swift new file mode 100644 index 0000000000..1acaec5edf --- /dev/null +++ b/DuckDuckGo/NetworkProtectionDNSSettingsViewModel.swift @@ -0,0 +1,85 @@ +// +// NetworkProtectionDNSSettingsViewModel.swift +// DuckDuckGo +// +// Copyright © 2024 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 +import Combine +import NetworkProtection +import Core + +final class NetworkProtectionDNSSettingsViewModel: ObservableObject { + private let settings: VPNSettings + private var cancellables: Set = [] + + @Published public var dnsSettings: NetworkProtectionDNSSettings = .default + + @Published public var isCustomDNSSelected = false + + @Published public var customDNSServers = "" + + @Published public var isApplyButtonEnabled = false + + init(settings: VPNSettings) { + self.settings = settings + + settings.dnsSettingsPublisher + .receive(on: DispatchQueue.main) + .assign(to: \.dnsSettings, onWeaklyHeld: self) + .store(in: &cancellables) + + isCustomDNSSelected = settings.dnsSettings.usesCustomDNS + customDNSServers = settings.dnsSettings.dnsServersText + } + + func toggleDNSSettings() { + isCustomDNSSelected.toggle() + } + + func applyDNSSettings() { + if isCustomDNSSelected { + settings.dnsSettings = .custom([customDNSServers]) + } else { + settings.dnsSettings = .default + } + + /// Updating `dnsSettings` does an IPv4 conversion before actually commiting the change, + /// so we do a final check to see which outcome the user ends up with + if settings.dnsSettings.usesCustomDNS { + DailyPixel.fireDailyAndCount(pixel: .networkProtectionDNSUpdateCustom) + } else { + DailyPixel.fireDailyAndCount(pixel: .networkProtectionDNSUpdateDefault) + } + } + + func updateApplyButtonState() { + if isCustomDNSSelected { + isApplyButtonEnabled = !customDNSServers.isEmpty && customDNSServers.isValidIpv4Host + } else { + isApplyButtonEnabled = true + } + } +} + +extension NetworkProtectionDNSSettings { + fileprivate var dnsServersText: String { + switch self { + case .default: return "" + case .custom(let servers): return servers.joined(separator: ", ") + } + } +} diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 27cdd8d7ac..472402a443 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -654,8 +654,7 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") @MainActor private func refreshMetadata() async { - let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver, - tokenStore: AppDependencyProvider.shared.networkProtectionKeychainTokenStore) + let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver) self.vpnMetadata = await collector.collectMetadata() self.tableView.reloadData() } diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index ffd1ace366..2dc751f821 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -169,7 +169,11 @@ struct NetworkProtectionStatusView: View { private func connectionDetails() -> some View { Section { if let ipAddress = statusModel.ipAddress { - NetworkProtectionServerItemView(title: UserText.netPStatusViewIPAddress, value: ipAddress) + NetworkProtectionConnectionDetailView(title: UserText.netPStatusViewIPAddress, value: ipAddress) + } + + if statusModel.dnsSettings.usesCustomDNS { + NetworkProtectionConnectionDetailView(title: UserText.netPStatusViewCustomDNS, value: String(describing: statusModel.dnsSettings)) } NetworkProtectionThroughputItemView( @@ -270,7 +274,7 @@ private struct NetworkProtectionLocationItemView: View { } } -private struct NetworkProtectionServerItemView: View { +private struct NetworkProtectionConnectionDetailView: View { let title: String let value: String @@ -318,4 +322,11 @@ private struct NetworkProtectionThroughputItemView: View { } } +extension NetworkProtectionDNSSettings { + var usesCustomDNS: Bool { + guard case .custom(let servers) = self, !servers.isEmpty else { return false } + return true + } +} + #endif diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index d2f8e5b91e..fe81186243 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -131,6 +131,7 @@ final class NetworkProtectionStatusViewModel: ObservableObject { @Published public var shouldShowConnectionDetails: Bool = false @Published public var location: String? @Published public var ipAddress: String? + @Published public var dnsSettings: NetworkProtectionDNSSettings @Published public var uploadTotal: String = Constants.defaultUploadVolume @Published public var downloadTotal: String = Constants.defaultDownloadVolume @@ -159,12 +160,15 @@ final class NetworkProtectionStatusViewModel: ObservableObject { self.preferredLocation = NetworkProtectionLocationStatusModel(selectedLocation: settings.selectedLocation) + self.dnsSettings = settings.dnsSettings + setUpIsConnectedStatePublishers() setUpToggledStatePublisher() setUpStatusMessagePublishers() setUpDisableTogglePublisher() setUpServerInfoPublishers() setUpLocationPublishers() + setUpDNSSettingsPublisher() setUpThroughputRefreshTimer() setUpErrorPublishers() @@ -315,6 +319,13 @@ final class NetworkProtectionStatusViewModel: ObservableObject { .store(in: &cancellables) } + private func setUpDNSSettingsPublisher() { + settings.dnsSettingsPublisher + .receive(on: DispatchQueue.main) + .assign(to: \.dnsSettings, onWeaklyHeld: self) + .store(in: &cancellables) + } + private func setUpThroughputRefreshTimer() { if let throughputUpdateTimer, throughputUpdateTimer.isValid { // Prevent the timer from being set up multiple times diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index 4841517533..80ee7defa2 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -189,6 +189,9 @@ final class NetworkProtectionTunnelController: TunnelController { } options[NetworkProtectionOptionKey.selectedEnvironment] = AppDependencyProvider.shared.vpnSettings .selectedEnvironment.rawValue as NSString + if let data = try? JSONEncoder().encode(AppDependencyProvider.shared.vpnSettings.dnsSettings) { + options[NetworkProtectionOptionKey.dnsSettings] = NSData(data: data) + } do { try tunnelManager.connection.startVPNTunnel(options: options) diff --git a/DuckDuckGo/NetworkProtectionVPNSettingsView.swift b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift index c68aefb485..06a265e736 100644 --- a/DuckDuckGo/NetworkProtectionVPNSettingsView.swift +++ b/DuckDuckGo/NetworkProtectionVPNSettingsView.swift @@ -31,11 +31,14 @@ struct NetworkProtectionVPNSettingsView: View { List { // Widget only available for iOS 17 and up if #available(iOS 17.0, *) { - NavigationLink { - WidgetEducationView.vpn - } label: { - Text(UserText.vpnSettingsAddWidget).daxBodyRegular() + Section { + NavigationLink { + WidgetEducationView.vpn + } label: { + Text(UserText.vpnSettingsAddWidget).daxBodyRegular() + } } + .listRowBackground(Color(designSystemColor: .surface)) } switch viewModel.viewKind { @@ -46,6 +49,7 @@ struct NetworkProtectionVPNSettingsView: View { toggleSection( text: UserText.netPExcludeLocalNetworksSettingTitle, + headerText: UserText.netPExcludeLocalNetworksSettingHeader, footerText: UserText.netPExcludeLocalNetworksSettingFooter ) { Toggle("", isOn: $viewModel.excludeLocalNetworks) @@ -54,16 +58,7 @@ struct NetworkProtectionVPNSettingsView: View { } } - Section { - HStack(spacing: 16) { - Image("Info-Solid-24") - .foregroundColor(.init(designSystemColor: .icons).opacity(0.3)) - Text(UserText.netPSecureDNSSettingFooter) - .daxFootnoteRegular() - .foregroundColor(.init(designSystemColor: .textSecondary)) - } - } - .listRowBackground(Color(designSystemColor: .surface)) + dnsSection() } } .applyInsetGroupedListStyle() @@ -74,8 +69,37 @@ struct NetworkProtectionVPNSettingsView: View { } } + func dnsSection() -> some View { + Section { + NavigationLink { + NetworkProtectionDNSSettingsView() + } label: { + HStack { + Text(UserText.vpnSettingDNSServerTitle) + .daxBodyRegular() + .foregroundColor(.init(designSystemColor: .textPrimary)) + Spacer() + Text(viewModel.dnsServers) + .daxBodyRegular() + .foregroundColor(.init(designSystemColor: .textSecondary)) + } + } + } header: { + Text(UserText.vpnSettingDNSSectionHeader) + } footer: { + if viewModel.usesCustomDNS { + Text(UserText.vpnSettingDNSSectionDisclaimer) + .foregroundColor(.init(designSystemColor: .textSecondary)) + } else { + Text(UserText.netPSecureDNSSettingFooter) + .foregroundColor(.init(designSystemColor: .textSecondary)) + } + } + .listRowBackground(Color(designSystemColor: .surface)) + } + @ViewBuilder - func toggleSection(text: String, footerText: String, @ViewBuilder toggle: () -> some View) -> some View { + func toggleSection(text: String, headerText: String, footerText: String, @ViewBuilder toggle: () -> some View) -> some View { Section { HStack { VStack(alignment: .leading, spacing: 4) { @@ -88,6 +112,8 @@ struct NetworkProtectionVPNSettingsView: View { toggle() .toggleStyle(SwitchToggleStyle(tint: .init(designSystemColor: .accent))) } + } header: { + Text(headerText) } footer: { Text(footerText) .foregroundColor(.init(designSystemColor: .textSecondary)) @@ -125,6 +151,8 @@ struct NetworkProtectionVPNSettingsView: View { ) ) .toggleStyle(SwitchToggleStyle(tint: .init(designSystemColor: .accent))) + } header: { + Text(UserText.netPVPNAlertsSectionHeader) } footer: { Text(UserText.netPVPNAlertsToggleSectionFooter) .foregroundColor(.init(designSystemColor: .textSecondary)) diff --git a/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift b/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift index 540198ea8b..60dc380107 100644 --- a/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift +++ b/DuckDuckGo/NetworkProtectionVPNSettingsViewModel.swift @@ -42,6 +42,8 @@ final class NetworkProtectionVPNSettingsViewModel: ObservableObject { } @Published public var excludeLocalNetworks: Bool = true + @Published public var usesCustomDNS = false + @Published public var dnsServers: String = UserText.vpnSettingDNSServerDefaultValue init(notificationsAuthorization: NotificationsAuthorizationControlling, settings: VPNSettings) { self.settings = settings @@ -51,6 +53,16 @@ final class NetworkProtectionVPNSettingsViewModel: ObservableObject { .receive(on: DispatchQueue.main) .assign(to: \.excludeLocalNetworks, onWeaklyHeld: self) .store(in: &cancellables) + settings.dnsSettingsPublisher + .receive(on: DispatchQueue.main) + .map { $0.usesCustomDNS } + .assign(to: \.usesCustomDNS, onWeaklyHeld: self) + .store(in: &cancellables) + settings.dnsSettingsPublisher + .receive(on: DispatchQueue.main) + .map { String(describing: $0) } + .assign(to: \.dnsServers, onWeaklyHeld: self) + .store(in: &cancellables) } @MainActor diff --git a/DuckDuckGo/NewTabPage.swift b/DuckDuckGo/NewTabPage.swift new file mode 100644 index 0000000000..5ae09b931b --- /dev/null +++ b/DuckDuckGo/NewTabPage.swift @@ -0,0 +1,35 @@ +// +// NewTabPage.swift +// DuckDuckGo +// +// Copyright © 2024 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 UIKit + +protocol NewTabPage: UIViewController { + + var isDragging: Bool { get } // TODO: Mariusz, check if needed in both + func reloadFavorites() // TODO: Mariusz: check if needed with reactive approach + + func launchNewSearch() + func openedAsNewTab(allowingKeyboard: Bool) + func omniBarCancelPressed() + + func dismiss() + + func showNextDaxDialog() + func onboardingCompleted() +} diff --git a/DuckDuckGo/NewTabPageManager.swift b/DuckDuckGo/NewTabPageManager.swift new file mode 100644 index 0000000000..0b1ff35c8e --- /dev/null +++ b/DuckDuckGo/NewTabPageManager.swift @@ -0,0 +1,64 @@ +// +// NewTabPageManager.swift +// DuckDuckGo +// +// Copyright © 2024 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 +import BrowserServicesKit + +protocol NewTabPageManaging: AnyObject { + var isNewTabPageSectionsEnabled: Bool { get } +} + +protocol NewTabPageDebugging: NewTabPageManaging { + var isLocalFlagEnabled: Bool { get set } + var isFeatureFlagEnabled: Bool { get } +} + +final class NewTabPageManager: NewTabPageManaging, NewTabPageDebugging { + + var appDefaults: AppDebugSettings + let featureFlagger: FeatureFlagger + + init(appDefaults: AppDebugSettings = AppDependencyProvider.shared.appSettings, + featureFlager: FeatureFlagger = AppDependencyProvider.shared.featureFlagger) { + + self.appDefaults = appDefaults + self.featureFlagger = featureFlager + } + + // MARK: - HomeTabManaging + + var isNewTabPageSectionsEnabled: Bool { + isLocalFlagEnabled && isFeatureFlagEnabled + } + + // MARK: - NewTabPageDebugging + + var isLocalFlagEnabled: Bool { + get { + appDefaults.newTabPageSectionsEnabled + } + set { + appDefaults.newTabPageSectionsEnabled = newValue + } + } + + var isFeatureFlagEnabled: Bool { + featureFlagger.isFeatureOn(.newTabPageSections) + } +} diff --git a/DuckDuckGo/NewTabPageSectionsDebugView.swift b/DuckDuckGo/NewTabPageSectionsDebugView.swift new file mode 100644 index 0000000000..e4ec527a25 --- /dev/null +++ b/DuckDuckGo/NewTabPageSectionsDebugView.swift @@ -0,0 +1,88 @@ +// +// NewTabPageSectionsDebugView.swift +// DuckDuckGo +// +// Copyright © 2024 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 + +struct NewTabPageSectionsDebugView: View { + + private var newTabPageDebugging: NewTabPageDebugging + @State private var isFeatureEnabled: Bool + private var localFlagEnabled: Binding { + Binding { + newTabPageDebugging.isLocalFlagEnabled + } set: { + newTabPageDebugging.isLocalFlagEnabled = $0 + isFeatureEnabled = newTabPageDebugging.isNewTabPageSectionsEnabled + } + + } + + init() { + let manager = NewTabPageManager() + newTabPageDebugging = manager + isFeatureEnabled = manager.isNewTabPageSectionsEnabled + } + + var body: some View { + List { + Section { + HStack { + Text("New tab page sections enabled") + Spacer() + if isFeatureEnabled { + Image(systemName: "checkmark") + .foregroundColor(Color(designSystemColor: .accent)) + } else { + Image(systemName: "xmark") + .foregroundColor(Color.red40) + } + } + } + + Section { + HStack { + Text("Feature flag enabled") + Spacer() + if newTabPageDebugging.isFeatureFlagEnabled { + Image(systemName: "checkmark") + .renderingMode(.template) + .foregroundColor(Color(designSystemColor: .accent)) + } else { + Image(systemName: "xmark") + .renderingMode(.template) + .foregroundColor(Color.red40) + } + } + } footer: { + Text("Requires internal user") + } + + Section { + Toggle(isOn: localFlagEnabled, + label: { + Text("Local setting enabled") + }) + } + } + } +} + +#Preview { + NewTabPageSectionsDebugView() +} diff --git a/DuckDuckGo/NewTabPageView.swift b/DuckDuckGo/NewTabPageView.swift new file mode 100644 index 0000000000..719eb8bbd2 --- /dev/null +++ b/DuckDuckGo/NewTabPageView.swift @@ -0,0 +1,30 @@ +// +// NewTabPageView.swift +// DuckDuckGo +// +// Copyright © 2024 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 + +struct NewTabPageView: View { + var body: some View { + Text("Empty") + } +} + +#Preview { + NewTabPageView() +} diff --git a/DuckDuckGo/NewTabPageViewController.swift b/DuckDuckGo/NewTabPageViewController.swift new file mode 100644 index 0000000000..503896a1c3 --- /dev/null +++ b/DuckDuckGo/NewTabPageViewController.swift @@ -0,0 +1,56 @@ +// +// NewTabPageViewController.swift +// DuckDuckGo +// +// Copyright © 2024 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 + +final class NewTabPageViewController: UIHostingController, NewTabPage { + + let isDragging: Bool = false + + weak var chromeDelegate: BrowserChromeDelegate? + weak var delegate: HomeControllerDelegate? + + func launchNewSearch() { + + } + + func openedAsNewTab(allowingKeyboard: Bool) { + + } + + func omniBarCancelPressed() { + + } + + func dismiss() { + + } + + func showNextDaxDialog() { + + } + + func onboardingCompleted() { + + } + + func reloadFavorites() { + + } +} diff --git a/DuckDuckGo/PrivateSearchView.swift b/DuckDuckGo/PrivateSearchView.swift index f42c3e990d..a16218a4a8 100644 --- a/DuckDuckGo/PrivateSearchView.swift +++ b/DuckDuckGo/PrivateSearchView.swift @@ -51,10 +51,9 @@ struct PrivateSearchViewSettings: View { Section(footer: Text(UserText.settingsAutocompleteSubtitle)) { // Autocomplete Suggestions SettingsCellView(label: UserText.settingsAutocompleteLabel, - accesory: .toggle(isOn: viewModel.autocompleteBinding)) + accesory: .toggle(isOn: viewModel.autocompletePrivateSearchBinding)) } - if viewModel.shouldShowRecentlyVisitedSites { Section(footer: Text(UserText.settingsAutocompleteRecentlyVisitedSubtitle)) { SettingsCellView(label: UserText.settingsAutocompleteRecentlyVisitedLabel, diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index feb079c741..305df76064 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -28,6 +28,7 @@ import Configuration import Persistence import DDGSync import NetworkProtection +import SwiftUI class RootDebugViewController: UITableViewController { @@ -41,6 +42,7 @@ class RootDebugViewController: UITableViewController { case openVanillaBrowser = 670 case resetSendCrashLogs = 671 case refreshConfig = 672 + case newTabPageSections = 674 } @IBOutlet weak var shareButton: UIBarButtonItem! @@ -158,6 +160,9 @@ class RootDebugViewController: UITableViewController { AppUserDefaults().crashCollectionOptInStatus = .undetermined case .refreshConfig: fetchAssets() + case .newTabPageSections: + let controller = UIHostingController(rootView: NewTabPageSectionsDebugView()) + show(controller, sender: nil) } } } diff --git a/DuckDuckGo/SettingsDuckPlayerView.swift b/DuckDuckGo/SettingsDuckPlayerView.swift index e20d77ae5d..978d86cee7 100644 --- a/DuckDuckGo/SettingsDuckPlayerView.swift +++ b/DuckDuckGo/SettingsDuckPlayerView.swift @@ -22,18 +22,42 @@ import SwiftUI import DesignResourcesKit struct SettingsDuckPlayerView: View { + private static let learnMoreURL = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/duck-player/")! @EnvironmentObject var viewModel: SettingsViewModel - var body: some View { List { + VStack(alignment: .center) { + Image("SettingsDuckPlayerHero") + .padding(.top, -20) // Adjust for the image padding + + Text(UserText.settingsDuckPlayerTitle) + .daxTitle3() + + Text(UserText.settingsDuckPlayerInfoText) + .daxBodyRegular() + .multilineTextAlignment(.center) + .foregroundColor(Color(designSystemColor: .textSecondary)) + .padding(.top, 12) + + Link(UserText.settingsDuckPlayerLearnMore, + destination: SettingsDuckPlayerView.learnMoreURL) + .daxBodyRegular() + .accentColor(Color.init(designSystemColor: .accent)) + } + .listRowBackground(Color.clear) + Section { - SettingsPickerCellView(label: "Open videos in DuckPlayer", + SettingsPickerCellView(label: UserText.settingsOpenVideosInDuckPlayerLabel, options: DuckPlayerMode.allCases, selectedOption: viewModel.duckPlayerModeBinding) + } footer: { + Text(UserText.settingsDuckPlayerFooter) + .daxFootnoteRegular() + .multilineTextAlignment(.center) } } - .applySettingsListModifiers(title: "Open videos in DuckPlayer", + .applySettingsListModifiers(title: UserText.settingsDuckPlayerTitle, displayMode: .inline, viewModel: viewModel) } diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index 6e528bf084..5f51165375 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -39,7 +39,7 @@ struct SettingsGeneralView: View { footer: Text(UserText.settingsAutocompleteSubtitle)) { // Autocomplete Suggestions SettingsCellView(label: UserText.settingsAutocompleteLabel, - accesory: .toggle(isOn: viewModel.autocompleteBinding)) + accesory: .toggle(isOn: viewModel.autocompleteGeneralBinding)) } if viewModel.shouldShowRecentlyVisitedSites { diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 4db2187800..8fefd7ea99 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -99,7 +99,8 @@ class SettingsLegacyViewProvider: ObservableObject { return AutofillLoginSettingsListViewController(appSettings: self.appSettings, syncService: self.syncService, syncDataProviders: self.syncDataProviders, - selectedAccount: selectedAccount) + selectedAccount: selectedAccount, + source: .settings) } var debug: UIViewController { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 367c78da1e..228140d87b 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -31,6 +31,7 @@ import Subscription import NetworkProtection #endif +// swiftlint:disable type_body_length final class SettingsViewModel: ObservableObject { // Dependencies @@ -154,13 +155,36 @@ final class SettingsViewModel: ObservableObject { ) } - var autocompleteBinding: Binding { + var autocompleteGeneralBinding: Binding { Binding( get: { self.state.autocomplete }, set: { self.appSettings.autocomplete = $0 self.state.autocomplete = $0 self.updateRecentlyVisitedSitesVisibility() + + if $0 { + Pixel.fire(pixel: .settingsGeneralAutocompleteOn) + } else { + Pixel.fire(pixel: .settingsGeneralAutocompleteOff) + } + } + ) + } + + var autocompletePrivateSearchBinding: Binding { + Binding( + get: { self.state.autocomplete }, + set: { + self.appSettings.autocomplete = $0 + self.state.autocomplete = $0 + self.updateRecentlyVisitedSitesVisibility() + + if $0 { + Pixel.fire(pixel: .settingsPrivateSearchAutocompleteOn) + } else { + Pixel.fire(pixel: .settingsPrivateSearchAutocompleteOff) + } } ) } @@ -321,6 +345,7 @@ final class SettingsViewModel: ObservableObject { subscriptionSignOutObserver = nil } } +// swiftlint:enable type_body_length // MARK: Private methods extension SettingsViewModel { diff --git a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift index 3ffee80dc6..ac544ba010 100644 --- a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift +++ b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift @@ -113,7 +113,7 @@ extension SyncSettingsViewController: SyncManagementViewModelDelegate { func launchAutofillViewController() { guard let mainVC = view.window?.rootViewController as? MainViewController else { return } dismiss(animated: true) - mainVC.launchAutofillLogins() + mainVC.launchAutofillLogins(source: .sync) } func launchBookmarksViewController() { diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 6cc79cf3d9..2c5e3fb563 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -724,23 +724,30 @@ class TabViewController: UIViewController { func goBack() { dismissJSAlertIfNeeded() - if let handler = youtubeNavigationHandler { + if let url = url, url.isDuckPlayer, let handler = youtubeNavigationHandler { handler.goBack(webView: webView) chromeDelegate?.omniBar.resignFirstResponder() - } else { - if isError { - hideErrorMessage() - url = webView.url - onWebpageDidStartLoading(httpsForced: false) - onWebpageDidFinishLoading() - } else if webView.canGoBack { - webView.goBack() - chromeDelegate?.omniBar.resignFirstResponder() - } else if openingTab != nil { - delegate?.tabDidRequestClose(self) - } + return + } + + if isError { + hideErrorMessage() + url = webView.url + onWebpageDidStartLoading(httpsForced: false) + onWebpageDidFinishLoading() + return + } + + if webView.canGoBack { + webView.goBack() + chromeDelegate?.omniBar.resignFirstResponder() + return } + if openingTab != nil { + delegate?.tabDidRequestClose(self) + } + } func goForward() { diff --git a/DuckDuckGo/UserScripts.swift b/DuckDuckGo/UserScripts.swift index e82f965ecc..a8d6e2697a 100644 --- a/DuckDuckGo/UserScripts.swift +++ b/DuckDuckGo/UserScripts.swift @@ -34,8 +34,10 @@ final class UserScripts: UserScriptsProvider { let contentScopeUserScriptIsolated: ContentScopeUserScript let autoconsentUserScript: AutoconsentUserScript let specialPages: SpecialPagesUserScript? - let youtubeOverlayScript: YoutubeOverlayUserScript? - let youtubePlayerUserScript: YoutubePlayerUserScript? + + let duckPlayer: DuckPlayer? + var youtubeOverlayScript: YoutubeOverlayUserScript? + var youtubePlayerUserScript: YoutubePlayerUserScript? private(set) var faviconScript = FaviconUserScript() private(set) var navigatorPatchScript = NavigatorSharePatchUserScript() @@ -50,7 +52,7 @@ final class UserScripts: UserScriptsProvider { surrogatesScript = SurrogatesUserScript(configuration: sourceProvider.surrogatesConfig) autofillUserScript = AutofillUserScript(scriptSourceProvider: sourceProvider.autofillSourceProvider) autofillUserScript.sessionKey = sourceProvider.contentScopeProperties.sessionKey - + loginFormDetectionScript = sourceProvider.loginDetectionEnabled ? LoginFormDetectionUserScript() : nil contentScopeUserScript = ContentScopeUserScript(sourceProvider.privacyConfigurationManager, properties: sourceProvider.contentScopeProperties) @@ -59,20 +61,19 @@ final class UserScripts: UserScriptsProvider { isIsolated: true) autoconsentUserScript = AutoconsentUserScript(config: sourceProvider.privacyConfigurationManager.privacyConfig) + // DuckPlayer Scripts & Special Pages + duckPlayer = DuckPlayer() specialPages = SpecialPagesUserScript() - youtubeOverlayScript = YoutubeOverlayUserScript() - youtubePlayerUserScript = YoutubePlayerUserScript() - if let youtubeOverlayScript { - contentScopeUserScriptIsolated.registerSubfeature(delegate: youtubeOverlayScript) - } - if let specialPages = specialPages { - if let youtubePlayerUserScript { - specialPages.registerSubfeature(delegate: youtubePlayerUserScript) - } + youtubeOverlayScript = duckPlayer.flatMap { YoutubeOverlayUserScript(duckPlayer: $0) } + youtubePlayerUserScript = duckPlayer.flatMap { YoutubePlayerUserScript(duckPlayer: $0) } + youtubeOverlayScript.map { contentScopeUserScriptIsolated.registerSubfeature(delegate: $0) } + youtubePlayerUserScript.map { specialPages?.registerSubfeature(delegate: $0) } + + if let specialPages { userScripts.append(specialPages) } - } + lazy var userScripts: [UserScript] = [ debugScript, diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 1cf3fe359d..17849eb004 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -495,6 +495,7 @@ public struct UserText { static let netPStatusViewLocation = NSLocalizedString("network.protection.status.view.location", value: "Location", comment: "Location label shown in NetworkProtection's status view.") static let netPStatusViewIPAddress = NSLocalizedString("network.protection.status.view.ip.address", value: "IP Address", comment: "IP Address label shown in NetworkProtection's status view.") static let netPStatusViewConnectionDetails = NSLocalizedString("network.protection.status.view.connection.details", value: "Connection Details", comment: "Connection details label shown in NetworkProtection's status view.") + static let netPStatusViewCustomDNS = NSLocalizedString("network.protection.status.view.custom.dns", value: "DNS Server", comment: "Custom DNS label shown in NetworkProtection's status view.") static let netPStatusViewSettingsSectionTitle = NSLocalizedString("network.protection.status.view.settings.section.title", value: "Manage", comment: "Label shown on the title of the settings section in NetworkProtection's status view.") static let netPVPNSettingsTitle = NSLocalizedString("network.protection.vpn.settings.title", value: "VPN Settings", comment: "Title for the VPN Settings screen.") static let netPVPNSettingsFAQ = NSLocalizedString("network.protection.vpn.settings.faq", value: "FAQs and Support", comment: "Title for the FAQ row in the VPN status screen.") @@ -523,11 +524,13 @@ public struct UserText { static let vpnDataVolume = NSLocalizedString("network.protection.vpn.data-volume", value: "Data Volume", comment: "Title for the data volume section in the VPN status screen") static let vpnAbout = NSLocalizedString("network.protection.vpn.about", value: "About", comment: "Title of the About section in the VPN status screen") static let netPExcludeLocalNetworksSettingTitle = NSLocalizedString("network.protection.vpn.exclude.local.networks.setting.title", value: "Exclude Local Networks", comment: "Title for the Exclude Local Networks setting item.") + static let netPExcludeLocalNetworksSettingHeader = NSLocalizedString("network.protection.vpn.exclude.local.networks.setting.header", value: "General", comment: "Header text for the Exclude Local Networks setting item.") static let netPExcludeLocalNetworksSettingFooter = NSLocalizedString("network.protection.vpn.exclude.local.networks.setting.footer", value: "Let local traffic bypass the VPN and connect to devices on your local network, like a printer.", comment: "Footer text for the Exclude Local Networks setting item.") - static let netPSecureDNSSettingFooter = NSLocalizedString("network.protection.vpn.secure.dns.setting.footer", value: "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit.", comment: "Footer text for the Always on VPN setting item.") + static let netPSecureDNSSettingFooter = NSLocalizedString("network.protection.vpn.secure.dns.setting.footer", value: "DuckDuckGo routes DNS queries through our DNS servers so your internet provider can't see what websites you visit.", comment: "Footer text for the DNS server setting item.") static let netPTurnOnNotificationsButtonTitle = NSLocalizedString("network.protection.turn.on.notifications.button.title", value: "Turn On Notifications", comment: "Title for the button to link to the iOS app settings and enable notifications app-wide.") static let netPTurnOnNotificationsSectionFooter = NSLocalizedString("network.protection.turn.on.notifications.section.footer", value: "Allow DuckDuckGo to notify you if your connection drops or VPN status changes.", comment: "Footer text under the button to link to the iOS app settings and enable notifications app-wide.") - static let netPVPNAlertsToggleTitle = NSLocalizedString("network.protection.vpn.alerts.toggle.title", value: "VPN Alerts", comment: "Title for the toggle for VPN alerts.") + static let netPVPNAlertsSectionHeader = NSLocalizedString("network.protection.vpn.alerts.section.header", value: "Notifications", comment: "Section header for the toggle for VPN notifications.") + static let netPVPNAlertsToggleTitle = NSLocalizedString("network.protection.vpn.alerts.toggle.title", value: "VPN Notifications", comment: "Title for the toggle for VPN notifications.") static let netPVPNAlertsToggleSectionFooter = NSLocalizedString("network.protection.vpn.alerts.toggle.section.footer", value: "Get notified if your connection drops or VPN status changes.", comment: "List section footer for the toggle for VPN alerts.") static let netPFrequentlyAskedQuestionsTitle = NSLocalizedString("network.protection.faq.title", value: "DuckDuckGo VPN FAQ", comment: "Title for the VPN FAQ screen.") @@ -577,6 +580,18 @@ public struct UserText { public static let vpnSettingsAddWidget = NSLocalizedString("vpn.settings.add.widget", value: "Add VPN Widget to Home Screen", comment: "VPN settings screen cell text for adding the VPN widget to the home screen") public static let addVPNWidgetSettingsThirdParagraph = NSLocalizedString("vpn.addWidget.settings.title", value: "Find and select DuckDuckGo. Then swipe to VPN and select Add Widget.", comment: "Title for the VPN widget onboarding screen") + // MARK: Custom DNS + + public static let vpnSettingDNSSectionHeader = NSLocalizedString("vpn.settings.dns.section-header", value: "DNS", comment: "Header text for the DNS section on the VPN Settings screen") + public static let vpnSettingDNSServerTitle = NSLocalizedString("vpn.settings.dns.server.title", value: "DNS Server", comment: "Title for the DNS Server row on the VPN Settings screen") + public static var vpnSettingDNSSectionDisclaimer = NSLocalizedString("vpn.settings.dns.section-disclaimer", value: "Using a custom DNS server can impact browsing speeds and expose your activity to third parties if the server isn't secure or reliable.", comment: "Disclaimer for the DNS Server section on the DNS Server screen") + public static let vpnSettingDNSServerDefaultValue = NSLocalizedString("vpn.settings.dns.server.default.value", value: "DuckDuckGo", comment: "Default value for the DNS Server row on the VPN Settings screen") + public static let vpnSettingDNSServerOptionRecommended = NSLocalizedString("vpn.settings.dns.server.option.default", value: "DuckDuckGo (Recommended)", comment: "Recommended option for the DNS Server setting") + public static let vpnSettingDNSServerOptionCustom = NSLocalizedString("vpn.settings.dns.server.option.custom", value: "Custom", comment: "Custom option for the DNS Server setting") + public static let vpnSettingDNSServerIPv4Title = NSLocalizedString("vpn.settings.dns.server.ipv4.title", value: "IPv4 Address", comment: "Title for the IPv4 Address setting") + public static let vpnSettingDNSServerScreenTitle = NSLocalizedString("vpn.settings.dns.server.screen.title", value: "DNS Server", comment: "Title for the DNS Server setting screen") + public static let vpnSettingDNSServerApplyButtonTitle = NSLocalizedString("vpn.settings.dns.server.apply.button.title", value: "Apply", comment: "Title for the Apply button on the DNS Server setting screen") + // MARK: Notifications public static let macWaitlistAvailableNotificationTitle = NSLocalizedString("mac-waitlist.available.notification.title", value: "DuckDuckGo for Mac is ready!", comment: "Title for the macOS waitlist notification") @@ -1148,10 +1163,14 @@ But if you *do* want a peek under the hood, you can find more information about // Duck Player public static let duckPlayerAlwaysEnabledLabel = NSLocalizedString("duckPlayer.alwaysEnabled.label", value: "Always", comment: "Text displayed when DuckPlayer is always enabled") - public static let duckPlayerAskLabel = NSLocalizedString("duckPlayer.ask.label", value: "Always", comment: "Text displayed when DuckPlayer is in 'Ask' mode.") - public static let duckPlayerDisabledLabel = NSLocalizedString("duckPlayer.never.label", value: "Always", comment: "Text displayed when DuckPlayer is in off.") - - - + public static let duckPlayerAskLabel = NSLocalizedString("duckPlayer.ask.label", value: "Ask every time", comment: "Text displayed when DuckPlayer is in 'Ask' mode.") + public static let duckPlayerDisabledLabel = NSLocalizedString("duckPlayer.never.label", value: "Never", comment: "Text displayed when DuckPlayer is in off.") + public static let settingsOpenVideosInDuckPlayerLabel = NSLocalizedString("duckplayer.settings.open-videos-in", value: "Open Videos in Duck Player", comment: "Settings screen cell text for DuckPlayer settings") + public static let settingsDuckPlayerTitle = NSLocalizedString("duckplayer.settings.title", value: "Duck Player", comment: "Settings screen cell text for DuckPlayer settings") + + public static let settingsOpenVideosInDuckPlayerTitle = NSLocalizedString("duckplayer.settings.title", value: "Duck Player", comment: "Settings screen cell text for DuckPlayer settings") + public static let settingsDuckPlayerFooter = NSLocalizedString("duckplayer.settings.footer", value: "DuckDuckGo provides all the privacy essentials you need to protect yourself as you browse the web.", comment: "Footer label in the settings screen for Duck Player") + public static let settingsDuckPlayerLearnMore = NSLocalizedString("duckplayer.settings.learn-more", value: "Learn More", comment: "Button that takes the user to learn more about Duck Player.") + public static let settingsDuckPlayerInfoText = NSLocalizedString("duckplayer.settings.info-text", value: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations.", comment: "Text explaining what Duck Player is in the settings screen.") } diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index c4e46c540b..0b7ec2ea41 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Търси с DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Предложенията вече включват наскоро посетени сайтове. Те се съхраняват на вашето устройство и никога на сървърите на DuckDuckGo. Изключете ги в настройките, а можете и да ги изтриете по всяко време с 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Предложенията за търсене вече включват наскоро посетени сайтове. Изключете ги в Настройки, а можете и да ги изтриете по всяко време с 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Същата поверителност.\nПо-добри предложения при търсене!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Деактивирайте, за да предотвратите автоматично отваряне на връзки в други инсталирани приложения."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Показване на предложенията за автоматично довършване"; +"settings.autocomplete" = "Предложения за търсене"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Наскоро посетени сайтове"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Показване на наскоро посетени сайтове в предложенията за търсене. Наскоро посетените сайтове са поверителни, съхраняват се само на вашето устройство и могат да бъдат изтрити с Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Показване на предложения за търсене при въвеждане, включително от отметките. Всички търсения са поверителни."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Заключване на приложение"; diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index 81e65bb0d8..32d6be1434 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Vyhledat prostřednictvím DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Návrhy teď zahrnují poslední navštívené stránky. Ty se ukládají přímo v zařízení a nikdy na serverech DuckDuckGo. Vypnout se dají v nastaveních nebo je můžeš kdykoli vymazat tlačítkem 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Návrhy vyhledávání teď zahrnují tvoje nedávno navštívené stránky. Vypnout se dají v nastavení nebo je můžeš kdykoli vymazat tlačítkem 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Stejné soukromí.\nLepší návrhy pro vyhledávání!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Zakažte, chcete-li zabránit automatickému otevírání odkazů v jiných nainstalovaných aplikacích."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Zobrazit návrhy automatického doplňování"; +"settings.autocomplete" = "Návrhy vyhledávání"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Nedávno navštívené stránky"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Zobrazovat poslední navštívené weby v návrzích vyhledávání. Poslední navštívené weby jsou soukromé, ukládají se jen do tvého zařízení a dají se vymazat tlačítkem Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Nech si při psaní zobrazovat návrhy vyhledávání včetně záložek. Všechna vyhledávání jsou soukromá."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Zámek aplikace"; diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 2b4967e9ef..0f939020df 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Søg med DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Forslag inkluderer nu nyligt besøgte websteder. Disse gemmes på din enhed og aldrig på DuckDuckGos servere. Slå fra i indstillingerne, eller slet når som helst med 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Søgeforslag inkluderer nu nyligt besøgte websteder. Slå fra i Indstillinger, eller ryd når som helst med 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Samme fortrolighed.\nBedre søgeforslag!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Deaktiver for at forhindre, at links automatisk åbnes i andre installerede apps."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Vis forslag til Autoudfyld"; +"settings.autocomplete" = "Søgeforslag"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Nyligt besøgte websteder"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Vis nyligt besøgte websteder i søgeforslag. Nyligt besøgte sider er private, gemmes kun på din enhed og kan slettes med Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Vis søgeforslag, mens du skriver, inklusive fra dine bogmærker. Alle søgninger er private."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Applikationslås"; diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index cf262aeb54..9a47be7145 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Mit DuckDuckGo suchen"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Vorschläge enthalten jetzt kürzlich besuchte Seiten. Diese werden auf deinem Gerät gespeichert, niemals auf den Servern von DuckDuckGo. Du kannst sie in den Einstellungen deaktivieren oder jederzeit mit dem 🔥 Fire Button löschen."; +"autocomplete.history.warning.message" = "Suchvorschläge enthalten jetzt Ihre kürzlich besuchten Seiten. Du kannst sie in den Einstellungen deaktivieren oder jederzeit mit dem 🔥 Fire Button löschen."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Gleiche Privatsphäre.\nBessere Suchvorschläge!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Aktivieren, damit sich Links nicht automatisch in anderen installierten Apps öffnen."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Vorschläge für die Autovervollständigung"; +"settings.autocomplete" = "Vorschläge für die Suche"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Kürzlich besuchte Seiten"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Kürzlich besuchte Websites in den Suchvorschlägen anzeigen. Kürzlich besuchte Websites sind privat, werden nur auf deinem Gerät gespeichert und können mit dem Fire Button gelöscht werden."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Zeige Suchvorschläge während du tippst, einschließlich deiner Lesezeichen. Alle Suchanfragen sind privat."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Anwendungssperre"; diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 437f3e957c..21b2604eaf 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Αναζήτηση DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Οι προτάσεις περιλαμβάνουν πλέον ιστότοπους που επισκεφθήκατε πρόσφατα. Αποθηκεύονται στη συσκευή σας και ποτέ στους διακομιστές του DuckDuckGo. Απενεργοποιήστε τη λειτουργία από τις ρυθμίσεις ή καταργήστε τη οποιαδήποτε στιγμή με το 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Οι προτάσεις αναζήτησης περιλαμβάνουν πλέον τους ιστότοπους που επισκεφθήκατε πρόσφατα. Απενεργοποιήστε τη λειτουργία από τις Ρυθμίσεις ή καταργήστε την οποιαδήποτε στιγμή με το κουμπί 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Ίδιο απόρρητο.\nΚαλύτερες προτάσεις αναζήτησης!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Απενεργοποιήστε για να αποτρέψετε το αυτόματο άνοιγμα συνδέσμων σε άλλες εγκατεστημένες εφαρμογές."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Εμφάνιση προτάσεων αυτόματης συμπλήρωσης"; +"settings.autocomplete" = "Προτάσεις αναζήτησης"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Πρόσφατα επισκεφθέντες ιστότοποι"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Προβολή ιστότοπων που επισκεφθήκατε πρόσφατα σε προτάσεις αναζήτησης. Οι ιστότοποι που επισκεφθήκατε πρόσφατα είναι ιδιωτικοί, αποθηκεύονται μόνο στη συσκευή σας και μπορούν να διαγραφούν με το κουμπί Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Προβολή προτάσεων αναζήτησης καθώς πληκτρολογείτε, συμπεριλαμβανομένων των σελιδοδεικτών σας. Όλες οι αναζητήσεις είναι ιδιωτικές."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Κλείδωμα εφαρμογής"; diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 797be49d52..26c4f07dc6 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -872,10 +872,25 @@ "duckPlayer.alwaysEnabled.label" = "Always"; /* Text displayed when DuckPlayer is in 'Ask' mode. */ -"duckPlayer.ask.label" = "Always"; +"duckPlayer.ask.label" = "Ask every time"; /* Text displayed when DuckPlayer is in off. */ -"duckPlayer.never.label" = "Always"; +"duckPlayer.never.label" = "Never"; + +/* Footer label in the settings screen for Duck Player */ +"duckplayer.settings.footer" = "DuckDuckGo provides all the privacy essentials you need to protect yourself as you browse the web."; + +/* Text explaining what Duck Player is in the settings screen. */ +"duckplayer.settings.info-text" = "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations."; + +/* Button that takes the user to learn more about Duck Player. */ +"duckplayer.settings.learn-more" = "Learn More"; + +/* Settings screen cell text for DuckPlayer settings */ +"duckplayer.settings.open-videos-in" = "Open Videos in Duck Player"; + +/* Settings screen cell text for DuckPlayer settings */ +"duckplayer.settings.title" = "Duck Player"; /* Email protection service offered by DuckDuckGo */ "email-protection" = "Email Protection"; @@ -1429,6 +1444,9 @@ https://duckduckgo.com/mac"; /* Connection details label shown in NetworkProtection's status view. */ "network.protection.status.view.connection.details" = "Connection Details"; +/* Custom DNS label shown in NetworkProtection's status view. */ +"network.protection.status.view.custom.dns" = "DNS Server"; + /* Generic connection failed error message shown in NetworkProtection's status view. */ "network.protection.status.view.error.connection.failed.message" = "Please try again later."; @@ -1462,11 +1480,14 @@ https://duckduckgo.com/mac"; /* Title of the About section in the VPN status screen */ "network.protection.vpn.about" = "About"; +/* Section header for the toggle for VPN notifications. */ +"network.protection.vpn.alerts.section.header" = "Notifications"; + /* List section footer for the toggle for VPN alerts. */ "network.protection.vpn.alerts.toggle.section.footer" = "Get notified if your connection drops or VPN status changes."; -/* Title for the toggle for VPN alerts. */ -"network.protection.vpn.alerts.toggle.title" = "VPN Alerts"; +/* Title for the toggle for VPN notifications. */ +"network.protection.vpn.alerts.toggle.title" = "VPN Notifications"; /* Title for the data volume section in the VPN status screen */ "network.protection.vpn.data-volume" = "Data Volume"; @@ -1474,6 +1495,9 @@ https://duckduckgo.com/mac"; /* Footer text for the Exclude Local Networks setting item. */ "network.protection.vpn.exclude.local.networks.setting.footer" = "Let local traffic bypass the VPN and connect to devices on your local network, like a printer."; +/* Header text for the Exclude Local Networks setting item. */ +"network.protection.vpn.exclude.local.networks.setting.header" = "General"; + /* Title for the Exclude Local Networks setting item. */ "network.protection.vpn.exclude.local.networks.setting.title" = "Exclude Local Networks"; @@ -1513,8 +1537,8 @@ https://duckduckgo.com/mac"; /* Title for the Preferred Location VPN Settings item. */ "network.protection.vpn.preferred.location.title" = "Preferred Location"; -/* Footer text for the Always on VPN setting item. */ -"network.protection.vpn.secure.dns.setting.footer" = "Our VPN uses Secure DNS to keep your online activity private, so that your Internet provider can't see what websites you visit."; +/* Footer text for the DNS server setting item. */ +"network.protection.vpn.secure.dns.setting.footer" = "DuckDuckGo routes DNS queries through our DNS servers so your internet provider can't see what websites you visit."; /* Title for the FAQ row in the VPN status screen. */ "network.protection.vpn.settings.faq" = "FAQs and Support"; @@ -2404,6 +2428,33 @@ But if you *do* want a peek under the hood, you can find more information about /* VPN settings screen cell text for adding the VPN widget to the home screen */ "vpn.settings.add.widget" = "Add VPN Widget to Home Screen"; +/* Disclaimer for the DNS Server section on the DNS Server screen */ +"vpn.settings.dns.section-disclaimer" = "Using a custom DNS server can impact browsing speeds and expose your activity to third parties if the server isn't secure or reliable."; + +/* Header text for the DNS section on the VPN Settings screen */ +"vpn.settings.dns.section-header" = "DNS"; + +/* Title for the Apply button on the DNS Server setting screen */ +"vpn.settings.dns.server.apply.button.title" = "Apply"; + +/* Default value for the DNS Server row on the VPN Settings screen */ +"vpn.settings.dns.server.default.value" = "DuckDuckGo"; + +/* Title for the IPv4 Address setting */ +"vpn.settings.dns.server.ipv4.title" = "IPv4 Address"; + +/* Custom option for the DNS Server setting */ +"vpn.settings.dns.server.option.custom" = "Custom"; + +/* Recommended option for the DNS Server setting */ +"vpn.settings.dns.server.option.default" = "DuckDuckGo (Recommended)"; + +/* Title for the DNS Server setting screen */ +"vpn.settings.dns.server.screen.title" = "DNS Server"; + +/* Title for the DNS Server row on the VPN Settings screen */ +"vpn.settings.dns.server.title" = "DNS Server"; + /* Title for the button to enable push notifications in system settings */ "waitlist.allow-notifications" = "Allow Notifications"; diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index b788b3f286..fc4bd5136b 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Buscar en DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Las sugerencias ahora incluyen sitios visitados recientemente. Estos se almacenan en tu dispositivo y nunca en los servidores de DuckDuckGo. Desactívalas en ajustes o elimínalas en cualquier momento con el 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Las sugerencias de búsqueda ahora incluyen los sitios visitados recientemente. Desactívalas en Ajustes o elimínalas en cualquier momento con el 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Misma privacidad.\n¡Mejores sugerencias de búsqueda!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Desactivar para evitar que los enlaces se abran automáticamente en otras aplicaciones instaladas."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Sugerencias de autocompletado"; +"settings.autocomplete" = "Sugerencias de búsqueda"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Sitios visitados recientemente"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Mostrar los sitios visitados recientemente en las sugerencias de búsqueda. Los sitios visitados recientemente son privados, solo se almacenan en tu dispositivo y se pueden borrar con el Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Muestra las sugerencias de búsqueda mientras escribes, incluidos tus marcadores. Todas las búsquedas son privadas."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Bloqueo de aplicación"; diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index 798f276be0..1331829971 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Otsi DuckDuckGo'st"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Soovitused hõlmavad nüüd hiljuti külastatud saite. Need salvestatakse sinu seadmesse ja mitte kunagi DuckDuckGo serveritesse. Saad selle välja lülitada seadetes või tühjendada igal ajal nupu 🔥 Fire Button abil."; +"autocomplete.history.warning.message" = "Otsingusoovitused hõlmavad nüüd sinu hiljuti külastatud saite. Saad selle välja lülitada seadetes või tühjendada igal ajal nupu 🔥 Fire Button abil."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Sama privaatsus.\nParemad otsingusoovitused!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Keela, et takistada linkide automaatset avanemist teistes installitud rakendustes."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Automaatselt täidetavad ettepanekud"; +"settings.autocomplete" = "Otsingusoovitused"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Hiljuti külastatud saidid"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Kuva hiljuti külastatud saidid otsingusoovitustes. Viimased külastatud saidid on privaatsed, salvestatakse ainult sinu seadmesse ja neid saab tühjendada nupu Fire Button abil."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Kuva sisestamisel otsingusoovitusi, sealhulgas järjehoidjaid. Kõik otsingud on privaatsed."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Rakenduse lukk"; diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index 43e9c2b008..c20a7cfe29 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Hae DuckDuckGo:sta"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Äskettäin käydyt sivustot sisältyvät nyt ehdotuksiin. Ne tallennetaan laitteellesi, ei koskaan DuckDuckGon palvelimille. Voit poistaa tämän käytöstä asetuksissa tai tyhjentää sivustotiedot milloin tahansa 🔥 Fire Buttonilla."; +"autocomplete.history.warning.message" = "Hakuehdotuksiin sisältyvät nyt myös viimeksi käymäsi sivustot. Voit poistaa tämän käytöstä asetuksissa tai tyhjentää sivustotiedot milloin tahansa 🔥 Fire Buttonilla."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Sama tietoja,\nparempia hakuehdotuksia!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Ota pois käytöstä estääksesi linkkejä avautumasta automaattisesti muissa asennetuissa sovelluksissa."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Näytä automaattisen täydennyksen ehdotukset"; +"settings.autocomplete" = "Hakuehdotukset"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Viimeksi käydyt sivustot"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Näytä viimeksi käydyt sivustot hakuehdotuksissa. Viimeksi käydyt sivustot ovat yksityisiä, tallennetaan vain laitteeseen ja ne voidaan tyhjentää Fire Buttonilla."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Näytä hakuehdotuksia kirjoittaessasi, mukaan lukien kirjanmerkit. Kaikki haut ovat yksityisiä."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Sovelluksen lukitus"; diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 6734d32e0d..9de1356f04 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Rechercher avec DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Les suggestions incluent désormais les sites récemment visités. Ils sont stockés sur votre appareil, jamais sur les serveurs de DuckDuckGo. Désactivez cette option dans les paramètres ou effacez à tout moment grâce au Fire Button 🔥."; +"autocomplete.history.warning.message" = "Les suggestions de recherche incluent désormais les sites que vous avez récemment visités. Désactivez cette option dans Réglages ou effacez à tout moment grâce au Fire Button 🔥."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "La confidentialité reste la même.\nLes suggestions de recherche s'améliorent !"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Désactiver pour empêcher l'ouverture automatique des liens dans d'autres applications installées."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Afficher les suggestions de saisie semi-automatique"; +"settings.autocomplete" = "Suggestions de recherche"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Sites récemment visités"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Affichez les sites récemment visités dans les suggestions de recherche. Les sites récemment visités sont confidentiels, ne sont stockés que sur votre appareil et peuvent être effacés avec le Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Affichez les suggestions de recherche au fur et à mesure que vous tapez, y compris vos signets. Toutes les recherches sont privées."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Verrouillage de l'application"; diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index 61aafd7696..47008863c2 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Pretraži DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Prijedlozi sad uključuju nedavno posjećena web-mjesta. Ona se pohranjuju na tvom uređaju, a ne na DuckDuckGo poslužiteljima. Isključi u postavkama ili izbriši u bilo kojem trenutku pomoću Fire Buttona 🔥."; +"autocomplete.history.warning.message" = "Prijedlozi za pretraživanje sada uključuju i nedavno posjećena mrežna mjesta. Isključi u Postavkama ili izbriši u bilo kojem trenutku pomoću Fire Buttona 🔥."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Ista privatnost.\nBolji prijedlozi za pretraživanje!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Onemogući da spriječiš automatsko otvaranje poveznica u drugim instaliranim aplikacijama."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Prikaži prijedloge za automatsko ispunjavanje"; +"settings.autocomplete" = "Prijedlozi za pretraživanje"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Nedavno posjećena web-mjesta"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Prikaži nedavno posjećena mrežna mjesta u prijedlozima pretraživanja. Nedavno posjećena mrežna mjesta su privatna, pohranjena samo na tvom uređaju i mogu se izbrisati pomoću Fire Buttona."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Prikaži prijedloge pretraživanja dok tipkaš, uključujući knjižne oznake. Sve pretrage su privatne."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Zaključavanje aplikacije"; diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index a699931519..f511622ecd 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "DuckDuckGo keresés"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "A javaslatok között már a legutóbb meglátogatott webhelyek is szerepelnek. Ezeket az eszközödön tároljuk, és soha nem a DuckDuckGo szerverein. A beállításokban kikapcsolhatod, vagy bármikor törölheted a 🔥 Tűz gombbal."; +"autocomplete.history.warning.message" = "A keresési javaslatok között már a legutóbb meglátogatott webhelyek is szerepelnek. A Beállítások alatt kikapcsolhatod, vagy bármikor törölheted a 🔥 Tűz gombbal."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Ugyanolyan adatvédelem.\nJobb keresési javaslatok!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Letiltásával megakadályozhatod, hogy a hivatkozások automatikusan megnyíljanak más telepített alkalmazásokban."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Automatikus kiegészítési javaslatok megjelenítése"; +"settings.autocomplete" = "Keresési javaslatok"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Legutóbb meglátogatott webhelyek"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "A legutóbb meglátogatott webhelyek megjelenítése a keresési javaslatokban. A legutóbb meglátogatott webhelyek privátak, csak az eszközödön tárolódnak, és a Tűz gombbal törölheted őket."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Keresési javaslatok megjelenítése gépelés közben, beleértve a könyvjelzőket is. Minden keresés privát."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Alkalmazás zárolás"; diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index 34fbb00008..cdc7afcd16 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Cerca DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Ora i suggerimenti includono i siti visitati di recente. Vengono archiviati sul tuo dispositivo e mai sui server di DuckDuckGo. Disattiva la funzione nelle impostazioni o cancellala in qualsiasi momento con il 🔥 Fire Button."; +"autocomplete.history.warning.message" = "I suggerimenti di ricerca ora includono i siti visitati di recente. Disattiva nelle impostazioni o cancella in qualsiasi momento con il 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "La stessa privacy.\nSuggerimenti di ricerca migliori!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Disattiva per impedire che i collegamenti si aprano automaticamente in altre app installate."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Mostra suggerimenti di completamento automatico"; +"settings.autocomplete" = "Suggerimenti di ricerca"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Siti visitati di recente"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Mostra i siti visitati di recente nei suggerimenti di ricerca. Questi siti sono privati, vengono memorizzati solo sul tuo dispositivo e possono essere rimossi con il Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Visualizza i suggerimenti di ricerca mentre digiti, inclusi i tuoi segnalibri. Tutte le ricerche sono private."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Blocco applicazione"; diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 195b4121d6..1c6fcda60e 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Ieškoti DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Dabar į pasiūlymus įtraukiamos neseniai aplankytos svetainės. Jos saugomos jūsų įrenginyje ir niekada – „DuckDuckGo“ serveriuose. Išjunkite nustatymuose arba bet kada ištrinkite naudodami mygtuką 🔥 „Fire Button“."; +"autocomplete.history.warning.message" = "Dabar paieškos pasiūlymuose bus rodomos ir neseniai aplankytos svetainės. Išjunkite nustatymuose arba bet kada išvalykite naudodami mygtuką 🔥 „Fire Button“."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Tas pats privatumas.\nGeresni paieškos pasiūlymai!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Išjungti, kad nuorodos automatiškai neatsidarytų kitose įdiegtose programose."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Rodyti automatinio užbaigimo pasiūlymus"; +"settings.autocomplete" = "Paieškos pasiūlymai"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Neseniai aplankytos svetainės"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Rodyti neseniai aplankytas interneto svetaines paieškos pasiūlymuose. Neseniai aplankytos interneto svetainės yra privačios, saugomos tik jūsų įrenginyje ir gali būti išvalytos naudojant mygtuką „Fire Button“."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Įvesdami tekstą matysite paieškos pasiūlymus, įskaitant žymes. Visos paieškos yra privačios."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Programos užraktas"; diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index f34cf72eaf..f6ebf8a58b 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Meklēt DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Ieteikumi tagad ietver nesen apmeklētās vietnes. Tās tiek saglabātas tikai tavā ierīcē, nevis DuckDuckGo serveros. Izslēdz iestatījumos vai jebkurā laikā notīri ar 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Meklēšanas ieteikumi tagad ietver tavas nesen apmeklētās vietnes. Izslēdz sadaļā Iestatījumi vai jebkurā laikā notīri ar 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Tas pats privātums.\nLabāki meklēšanas ieteikumi!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Atspējo, lai novērstu saišu automātisku atvēršanu citās instalētajās lietotnēs."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Automātiski pabeigt ieteikumus"; +"settings.autocomplete" = "Meklēšanas ieteikumi"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Nesen apmeklētās vietnes"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Meklēšanas ieteikumos rādīt nesen apmeklētās vietnes. Nesen apmeklētās vietnes ir privātas, tiek saglabātas tikai tavā ierīcē, un tās var notīrīt, izmantojot pogu Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Rakstīšanas laikā tiks rādīti meklēšanas ieteikumi, tostarp tavas grāmatzīmes. Visi meklējumi ir privāti."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Lietojumprogrammas bloķēšana"; diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index 2c9231b2af..9f949c9f4e 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Søk i DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Forslag inkluderer nå nylig besøkte nettsteder. Disse er lagret på enheten din og aldri på DuckDuckGos servere. Slå dem av i innstillingene, eller slett dem når som helst med Fire Button 🔥."; +"autocomplete.history.warning.message" = "Søkeforslag inkluderer nå nylig besøkte nettsteder. Slå dem av i innstillingene, eller slett dem når som helst med Fire Button 🔥."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Samme personvern.\nBedre søkeforslag!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Deaktiver for å forhindre at lenker automatisk åpnes i andre installerte apper."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Vis forslag fra autofullføring"; +"settings.autocomplete" = "Søkeforslag"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Nylig besøkte nettsteder"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Vis nylig besøkte nettsteder i søkeforslag. Nylig besøkte nettsteder er private, lagres bare på enheten din og kan slettes med Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Vis søkeforslag mens du skriver, inkludert bokmerkene dine. Alle søk er private."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Applås"; diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 36f62de3bb..45f7f3ddfe 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Zoek in DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Suggesties bevatten nu ook onlangs bezochte sites. Deze worden op je apparaat opgeslagen en nooit op de servers van DuckDuckGo. Je kunt suggesties uitschakelen in de instellingen of ze op elk gewenst moment wissen met de 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Zoekopdrachtsuggesties bevatten nu ook je recent bezochte sites. Je kunt suggesties uitschakelen in de instellingen of ze op elk gewenst moment wissen met de 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Dezelfde privacy.\nBetere zoeksuggesties!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Uitschakelen om te voorkomen dat links automatisch geopend worden in andere geïnstalleerde apps."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Suggesties voor automatisch aanvullen weergeven"; +"settings.autocomplete" = "Zoeksuggesties"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Onlangs bezochte websites"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Toon onlangs bezochte websites in zoeksuggesties. Onlangs bezochte websites zijn privé, worden alleen op je apparaat opgeslagen en kunnen worden gewist met de Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Toon suggesties voor zoekopdrachten terwijl je typt, inclusief je bladwijzers. Alle zoekopdrachten zijn privé."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "App-vergrendeling"; diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 1d30057f25..6e109279ef 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Przeszukaj DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Obecnie sugestie obejmują ostatnio odwiedzone witryny. Są one przechowywane na Twoim urządzeniu, nigdy na serwerach DuckDuckGo. Możesz je wyłączyć w ustawieniach lub w każdej chwili wyczyścić za pomocą przycisku 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Obecnie sugestie wyszukiwania obejmują ostatnio odwiedzone witryny. Możesz je wyłączyć w ustawieniach lub w każdej chwili wyczyścić za pomocą przycisku 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Ta sama prywatność.\nLepsze sugestie podczas wyszukiwania!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Wyłącz, aby uniemożliwić automatyczne otwieranie łączy w innych zainstalowanych aplikacjach."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Pokaż sugestie autouzupełniania"; +"settings.autocomplete" = "Sugestie wyszukiwania"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Ostatnio odwiedzone witryny"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Pokaż ostatnio odwiedzone witryny w sugestiach wyszukiwania. Ostatnio odwiedzane witryny są prywatne, przechowywane tylko na Twoim urządzeniu i można je wyczyścić za pomocą funkcji Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Wyświetlaj sugestie wyszukiwania podczas wpisywania, łącznie z zakładkami. Wszystkie wyszukiwania są prywatne."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Blokada aplikacji"; diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 2497b24222..8ccaa869a1 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Pesquisar no DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "As sugestões incluem agora sites visitados recentemente. Estes são armazenados no teu dispositivo e nunca nos servidores da DuckDuckGo. Desativa esta funcionalidade nas definições ou apaga os sites guardados em qualquer altura com o 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Agora, as sugestões de pesquisa incluem os sites visitados recentemente. Desativa esta funcionalidade nas definições ou limpa os sites guardados em qualquer altura com o 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "A mesma privacidade.\nMelhores sugestões de pesquisa!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Desative para prevenir que ligações sejam abertas automaticamente noutras aplicações instaladas."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Mostrar sugestões de preenchimento automático"; +"settings.autocomplete" = "Sugestões de pesquisa"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Sites visitados recentemente"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Mostrar sites visitados recentemente nas sugestões de pesquisa. Os sites visitados recentemente são privados, guardados apenas no teu dispositivo e podem ser apagados com o Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Mostra sugestões de pesquisa enquanto escreves, incluindo os teus marcadores. Todas as pesquisas são privadas."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Bloqueio de aplicações"; diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index ebd64b4e50..d2c478099c 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Căutare DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Sugestiile includ acum site-uri vizitate recent. Acestea sunt stocate pe dispozitivul tău și niciodată pe serverele DuckDuckGo. Dezactivează-le din setări sau șterge-le în orice moment cu ajutorul Fire Button 🔥."; +"autocomplete.history.warning.message" = "Sugestiile de căutare includ acum site-urile accesate recent. Dezactivează-le din Setări sau șterge-le în orice moment cu ajutorul Fire Button 🔥."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Aceeași confidențialitate.\nSugestii de căutare mai bune!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Dezactivează pentru a preveni deschiderea automată a linkurilor în alte aplicații instalate."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Afișează sugestiile de completare automată"; +"settings.autocomplete" = "Sugestii de căutare"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Site-uri vizitate recent"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Afișează site-urile accesate recent în sugestiile de căutare. Site-urile accesate recent sunt private, stocate doar pe dispozitiv și pot fi șterse cu Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Afișează sugestii de căutare, inclusiv marcajele tale, pe măsură ce tastezi. Toate căutările sunt private."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Blocarea aplicației"; diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index f35a55f101..69f9e3de7f 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Поиск в DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Теперь рекомендации включают недавно посещенные сайты. Они хранятся на вашем устройстве, а не на серверах DuckDuckGo. Их можно отключить в настройках или очистить с помощью кнопки Fire Button 🔥."; +"autocomplete.history.warning.message" = "Теперь рекомендации включают недавно посещенные вами сайты. Их можно отключить в настройках или очистить с помощью кнопки Fire Button 🔥."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Та же конфиденциальность.\nЛучшие поисковые рекомендации!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Отключение этой функции не позволит ссылкам автоматически открываться в других приложениях."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Показать предложения автозаполнения"; +"settings.autocomplete" = "Поисковые рекомендации"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Недавно посещенные сайты"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Показывать в рекомендациях поиска недавно посещенные сайты. Их список хранится только на вашем устройстве, и его можно удалить с помощью кнопки Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Показывать поисковые рекомендации (включая закладки) по мере ввода текста. Все поисковые запросы конфиденциальны."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Блокировка"; diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 55135624ed..f613e9be41 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Vyhľadávajte cez DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Návrhy teraz zahŕňajú nedávno navštívené stránky. Tie sú uložené vo vašom zariadení a nikdy nie na serveroch DuckDuckGo. Vypnite ich v nastaveniach alebo ich kedykoľvek zrušte pomocou tlačidla 🔥Fire Button."; +"autocomplete.history.warning.message" = "Návrhy vyhľadávania teraz zahŕňajú vaše nedávno navštívené stránky. Vypnite ich v nastaveniach alebo ich kedykoľvek zrušte pomocou tlačidla 🔥Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Rovnaké súkromie.\nLepšie návrhy vyhľadávania!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Zakážte a zabráňte tak automatickému otváraniu odkazov v iných nainštalovaných aplikáciách."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Zobraziť návrhy automatického dopĺňania"; +"settings.autocomplete" = "Návrhy na vyhľadávanie"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Nedávno navštívené weby"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Zobrazte naposledy navštívené lokality v návrhoch vyhľadávania. Naposledy navštívené lokality sú súkromné, ukladajú sa len v zariadení a môžete ich vymazať pomocou tlačidla Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Zobrazenie návrhov vyhľadávania pri písaní vrátane záložiek. Všetky vyhľadávania sú súkromné."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Zámok aplikácie"; diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 0f7a8cc559..05d4f72a9d 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Iščite po DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Predlogi zdaj vključujejo nedavno obiskana spletna mesta. Ta so shranjena v vaši napravi in nikoli v strežnikih DuckDuckGo. Funkcijo izklopite v nastavitvah ali pa seznam kadar koli počistite z gumbom Fire Button 🔥."; +"autocomplete.history.warning.message" = "Iskalni predlogi zdaj vključujejo vaša nedavno obiskana spletna mesta. Izklopite v nastavitvah ali pa seznam kadar koli počistite z gumbom Fire Button 🔥."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Enaka raven zasebnosti.\nBoljši predlogi iskanja!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Onemogoči, da preprečiš samodejno odpiranje povezav v drugih nameščenih aplikacijah."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Pokaži predloge za samodokončanje"; +"settings.autocomplete" = "Predlogi za iskanje"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Nedavno obiskana spletna mesta"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Prikaži nedavno obiskana spletna mesta v predlogih za iskanje. Nedavno obiskana spletna mesta so zasebna, shranjena samo v vaši napravi in jih lahko izbrišete z gumbom Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Med vnašanjem si oglejte predloge iskanja, vključno z zaznamki. Vsa iskanja so zasebna."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Zaklepanje aplikacije"; diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 2974c6f43f..799c79dc36 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "Sök med DuckDuckGo"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Nyligen besökta webbplatser inkluderas nu i förslag. Dessa lagras på din enhet – aldrig på servrar hos DuckDuckGo. Stäng av funktionen i inställningarna, eller rensa när som helst med 🔥 Fire Button."; +"autocomplete.history.warning.message" = "Sökförslag inkluderar nu dina nyligen besökta webbplatser. Stäng av funktionen i inställningarna, eller rensa när som helst med 🔥 Fire Button."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Samma integritet.\nBättre sökförslag!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Inaktivera för att förhindra att länkar automatiskt öppnas i andra installerade appar."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Visa Autoslutför förslag"; +"settings.autocomplete" = "Sökförslag"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Senast besökta webbplatser"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Visa nyligen besökta webbplatser i sökförslag. Nyligen besökta webbplatser är privata. De lagras endast på din enhet och kan rensas med Fire Button."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Visa sökförslag medan du skriver, inklusive dina bokmärken. Alla sökningar är privata."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "App-lås"; diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 3242461aa8..f9c3674851 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -245,7 +245,7 @@ "autocomplete.history.search.duckduckgo" = "DuckDuckGo'da Ara"; /* The message text shown in suggestions */ -"autocomplete.history.warning.message" = "Öneriler artık son ziyaret edilen siteleri de içeriyor. Bunlar cihazınızda saklanır, hiçbir zaman DuckDuckGo'nun sunucularında saklanmaz. Bunları ayarlardan kapatabilir veya 🔥 Fire Button ile istediğiniz zaman temizleyebilirsiniz."; +"autocomplete.history.warning.message" = "Arama önerileri artık son ziyaret ettiğiniz siteleri de içeriyor. Ayarlardan kapatabilir veya istediğiniz zaman 🔥 Fire Button ile temizleyebilirsiniz."; /* Title for message show in suggestions */ "autocomplete.history.warning.title" = "Aynı gizlilik.\nDaha iyi arama önerileri!"; @@ -1799,11 +1799,17 @@ "settings.associated.apps.description" = "Bağlantıların yüklenmiş olan diğer uygulamalarda otomatik olarak açılmasını önlemek için devre dışı bırakın."; /* Settings screen cell for autocomplete */ -"settings.autocomplete" = "Otomatik Tamamlama Önerileri"; +"settings.autocomplete" = "Arama Önerileri"; /* Settings label for enabling or disabling recently visited sites */ "settings.autocomplete.recentlyvisited" = "Son Ziyaret Edilen Siteler"; +/* Settings label for enabling or disabling recently visited sites */ +"settings.autocomplete.recentlyvisited.subtitle" = "Son ziyaret edilen siteleri arama önerilerinde göster. Son ziyaret edilen siteler özeldir, yalnızca cihazınızda saklanır ve Fire Button ile temizlenebilir."; + +/* Subtitle for Search Suggestions setting */ +"settings.autocomplete.subtitle" = "Yazarken yer imleriniz de dahil olmak üzere arama önerilerini gösterin. Tüm aramalar özeldir."; + /* Settings screen cell text for Application Lock */ "settings.autolock" = "Uygulama Kilidi"; diff --git a/DuckDuckGoTests/AppSettingsMock.swift b/DuckDuckGoTests/AppSettingsMock.swift index 9ed2915d11..b98ddc2c18 100644 --- a/DuckDuckGoTests/AppSettingsMock.swift +++ b/DuckDuckGoTests/AppSettingsMock.swift @@ -81,8 +81,8 @@ class AppSettingsMock: AppSettings { var autoconsentEnabled = true var crashCollectionOptInStatus: CrashCollectionOptInStatus = .undetermined - - var duckPlayerMode: DuckDuckGo.DuckPlayerMode = .alwaysAsk - + var newTabPageSectionsEnabled: Bool = false + + var duckPlayerMode: DuckDuckGo.DuckPlayerMode = .alwaysAsk } diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index 323594968e..eb4acc3a78 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -163,6 +163,10 @@ final class MockSecureVault: AutofillSecureVault { return storedIdentities } + func identitiesCount() throws -> Int { + return storedIdentities.count + } + func identityFor(id: Int64) throws -> SecureVaultModels.Identity? { return storedIdentities.first { $0.id == id } } @@ -367,6 +371,10 @@ class MockDatabaseProvider: AutofillDatabaseProvider { return Array(_identities.values) } + func identitiesCount() throws -> Int { + return _identities.count + } + func identityForIdentityId(_ identityId: Int64) throws -> SecureVaultModels.Identity? { return _identities[identityId] } diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index 4def4cc357..947a33a7e1 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,3 +1,2 @@ Bug fixes and other improvements. -Join our fully distributed team and help raise the standard of trust online! https://duckduckgo.com/hiring \ No newline at end of file