diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 7ca4eef0f6..493731e07b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -197,7 +197,6 @@ 3712091E2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3712091D2C21E390003ADF3D /* RemoteMessagingStoreErrorHandling.swift */; }; 372A0FF02B2389590033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEF2B2389590033BF7F /* SyncMetricsEventsHandler.swift */; }; 373608902ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3736088F2ABB1E6C00629E7F /* FavoritesDisplayModeStorage.swift */; }; - 373608922ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373608912ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift */; }; 373608932ABB432600629E7F /* FavoritesDisplayMode+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373608912ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift */; }; 37445F972A155F7C0029F789 /* SyncDataProviders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37445F962A155F7C0029F789 /* SyncDataProviders.swift */; }; 3760DFED299315EF0045A446 /* Waitlist in Frameworks */ = {isa = PBXBuildFile; productRef = 3760DFEC299315EF0045A446 /* Waitlist */; }; @@ -366,6 +365,7 @@ 6FF9AD3F2CE63DD800C5A406 /* TabSwitcherOpenDailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD3E2CE63DC200C5A406 /* TabSwitcherOpenDailyPixel.swift */; }; 6FF9AD412CE6610F00C5A406 /* TabSwitcherOpenDailyPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD402CE6610600C5A406 /* TabSwitcherOpenDailyPixelTests.swift */; }; 6FF9AD452CE766F700C5A406 /* NewTabPageControllerPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF9AD442CE766F700C5A406 /* NewTabPageControllerPixelTests.swift */; }; + 7B020B9A2D11F99D00876178 /* FavoritesDisplayMode+UserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373608912ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift */; }; 7B059F0F2D0387E900371ED0 /* NumberedParagraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B059F0A2D0387E900371ED0 /* NumberedParagraphView.swift */; }; 7B059F112D0387E900371ED0 /* WidgetEducationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B059F0D2D0387E900371ED0 /* WidgetEducationViewController.swift */; }; 7B059F122D0387E900371ED0 /* WidgetEducationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B059F0C2D0387E900371ED0 /* WidgetEducationView.swift */; }; @@ -373,21 +373,23 @@ 7B059F1D2D03A7E400371ED0 /* WidgetsShared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7B059F1C2D03A7E400371ED0 /* WidgetsShared.xcassets */; }; 7B059F1E2D03A7E400371ED0 /* WidgetsShared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7B059F1C2D03A7E400371ED0 /* WidgetsShared.xcassets */; }; 7B059F222D03BC4400371ED0 /* WidgetEducation.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7B059F212D03BC4400371ED0 /* WidgetEducation.xcassets */; }; + 7B10FF242D11A56300F36BF2 /* ControlWidgetVPNIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B10FF232D11A56300F36BF2 /* ControlWidgetVPNIntents.swift */; }; + 7B10FF252D11A56300F36BF2 /* ControlWidgetVPNIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B10FF232D11A56300F36BF2 /* ControlWidgetVPNIntents.swift */; }; 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */; }; 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */; }; 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */; }; - 7B1680FF2D106333005EAE24 /* VPNIntentTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1680FD2D106333005EAE24 /* VPNIntentTunnelController.swift */; }; 7B1681012D106CB9005EAE24 /* UserTextShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1681002D106CB4005EAE24 /* UserTextShared.swift */; }; 7B1681022D106CCC005EAE24 /* UserTextShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1681002D106CB4005EAE24 /* UserTextShared.swift */; }; - 7B1681032D106E1D005EAE24 /* VPNIntentTunnelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1680FD2D106333005EAE24 /* VPNIntentTunnelController.swift */; }; - 7B1681062D10BC96005EAE24 /* VPNAppIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1681042D10BC7B005EAE24 /* VPNAppIntents.swift */; }; + 7B1681062D10BC96005EAE24 /* VPNIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1681042D10BC7B005EAE24 /* VPNIntents.swift */; }; 7B1681092D10C678005EAE24 /* VPNStatusValueProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4DC5BF2CB2A4A500EE5CC2 /* VPNStatusValueProvider.swift */; }; 7B16810A2D10C680005EAE24 /* VPNControlWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFC32AF2CB291BB007A8E17 /* VPNControlWidget.swift */; }; - 7B16810C2D10CF44005EAE24 /* VPNWidgetIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B16810B2D10CF44005EAE24 /* VPNWidgetIntents.swift */; }; - 7B16810D2D10CF44005EAE24 /* VPNWidgetIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B16810B2D10CF44005EAE24 /* VPNWidgetIntents.swift */; }; + 7B16810C2D10CF44005EAE24 /* WidgetVPNIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B16810B2D10CF44005EAE24 /* WidgetVPNIntents.swift */; }; + 7B16810D2D10CF44005EAE24 /* WidgetVPNIntents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B16810B2D10CF44005EAE24 /* WidgetVPNIntents.swift */; }; 7B1C892C2CF714AA0008224E /* VPNTipsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1C892B2CF714AA0008224E /* VPNTipsModel.swift */; }; - 7B4DC5BD2CB29D8400EE5CC2 /* VPNToggleIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4DC5BC2CB29D8400EE5CC2 /* VPNToggleIntent.swift */; }; - 7B4DC5BE2CB29D8400EE5CC2 /* VPNToggleIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4DC5BC2CB29D8400EE5CC2 /* VPNToggleIntent.swift */; }; + 7B2CCBA32D11ABB100FE5852 /* VPNWidgetSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 7B2CCBA22D11ABB100FE5852 /* VPNWidgetSupport */; }; + 7B2CCBA52D11ABBA00FE5852 /* VPNWidgetSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 7B2CCBA42D11ABBA00FE5852 /* VPNWidgetSupport */; }; + 7B2CCBA72D11F01F00FE5852 /* VPNAppIntents in Frameworks */ = {isa = PBXBuildFile; productRef = 7B2CCBA62D11F01F00FE5852 /* VPNAppIntents */; }; + 7B2CCBA92D11F02800FE5852 /* VPNAppIntents in Frameworks */ = {isa = PBXBuildFile; productRef = 7B2CCBA82D11F02800FE5852 /* VPNAppIntents */; }; 7B4DC5C22CB2AE4600EE5CC2 /* WidgetKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4DC5C12CB2AE4600EE5CC2 /* WidgetKind.swift */; }; 7B4DC5C32CB2AF0700EE5CC2 /* WidgetKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4DC5C12CB2AE4600EE5CC2 /* WidgetKind.swift */; }; 7B4DC5C42CB2B1D000EE5CC2 /* WidgetKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4DC5C12CB2AE4600EE5CC2 /* WidgetKind.swift */; }; @@ -1737,16 +1739,16 @@ 7B059F132D03881000371ED0 /* ControlCenterWidgetEducationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlCenterWidgetEducationView.swift; sourceTree = ""; }; 7B059F1C2D03A7E400371ED0 /* WidgetsShared.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = WidgetsShared.xcassets; sourceTree = ""; }; 7B059F212D03BC4400371ED0 /* WidgetEducation.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = WidgetEducation.xcassets; sourceTree = ""; }; + 7B10FF232D11A56300F36BF2 /* ControlWidgetVPNIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlWidgetVPNIntents.swift; sourceTree = ""; }; + 7B10FF282D11AA0D00F36BF2 /* VPNiOS */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = VPNiOS; sourceTree = ""; }; 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = ""; }; 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; - 7B1680FD2D106333005EAE24 /* VPNIntentTunnelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNIntentTunnelController.swift; sourceTree = ""; }; 7B1681002D106CB4005EAE24 /* UserTextShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTextShared.swift; sourceTree = ""; }; - 7B1681042D10BC7B005EAE24 /* VPNAppIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAppIntents.swift; sourceTree = ""; }; - 7B16810B2D10CF44005EAE24 /* VPNWidgetIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNWidgetIntents.swift; sourceTree = ""; }; + 7B1681042D10BC7B005EAE24 /* VPNIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNIntents.swift; sourceTree = ""; }; + 7B16810B2D10CF44005EAE24 /* WidgetVPNIntents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetVPNIntents.swift; sourceTree = ""; }; 7B1C892B2CF714AA0008224E /* VPNTipsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNTipsModel.swift; sourceTree = ""; }; 7B1D7A912D0C723B00E48644 /* DesignResourcesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = DesignResourcesKit; path = ../DesignResourcesKit; sourceTree = SOURCE_ROOT; }; - 7B4DC5BC2CB29D8400EE5CC2 /* VPNToggleIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNToggleIntent.swift; sourceTree = ""; }; 7B4DC5BF2CB2A4A500EE5CC2 /* VPNStatusValueProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNStatusValueProvider.swift; sourceTree = ""; }; 7B4DC5C12CB2AE4600EE5CC2 /* WidgetKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetKind.swift; sourceTree = ""; }; 7B4DC5E02CB2D87C00EE5CC2 /* VPNAutoShortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAutoShortcuts.swift; sourceTree = ""; }; @@ -3154,6 +3156,8 @@ 0238E44F29C0FAA100615E30 /* FindInPageIOSJSSupport in Frameworks */, 3760DFED299315EF0045A446 /* Waitlist in Frameworks */, F1D43AFA2B99C1D300BAB743 /* BareBonesBrowserKit in Frameworks */, + 7B2CCBA52D11ABBA00FE5852 /* VPNWidgetSupport in Frameworks */, + 7B2CCBA92D11F02800FE5852 /* VPNAppIntents in Frameworks */, F143C2EB1E4A4CD400CFDE3A /* Core.framework in Frameworks */, 31E69A63280F4CB600478327 /* DuckUI in Frameworks */, CB941A6E2B96AB08000F9E7A /* PrivacyDashboard in Frameworks */, @@ -3188,7 +3192,9 @@ files = ( 8512EA5124ED30D20073EE19 /* SwiftUI.framework in Frameworks */, 4BD96E062C4DBC93003BC32C /* NetworkExtension.framework in Frameworks */, + 7B2CCBA72D11F01F00FE5852 /* VPNAppIntents in Frameworks */, 85DF714624F7FE6100C89288 /* Core.framework in Frameworks */, + 7B2CCBA32D11ABB100FE5852 /* VPNWidgetSupport in Frameworks */, 8512EA4F24ED30D20073EE19 /* WidgetKit.framework in Frameworks */, 4BBBBA872B02E85400D965DA /* DesignResourcesKit in Frameworks */, ); @@ -3788,9 +3794,10 @@ isa = PBXGroup; children = ( 7B1D7A912D0C723B00E48644 /* DesignResourcesKit */, + 31794BFF2821DFB600F18633 /* DuckUI */, 85875B5F29912A2D00115F05 /* SyncUI */, + 7B10FF282D11AA0D00F36BF2 /* VPNiOS */, 37FCAACB2993149A000E420A /* Waitlist */, - 31794BFF2821DFB600F18633 /* DuckUI */, ); path = LocalPackages; sourceTree = ""; @@ -3852,9 +3859,7 @@ 4B5C46282AF2A6DB002A4432 /* Intents */ = { isa = PBXGroup; children = ( - 7B1680FD2D106333005EAE24 /* VPNIntentTunnelController.swift */, - 7B1681042D10BC7B005EAE24 /* VPNAppIntents.swift */, - 7B4DC5BC2CB29D8400EE5CC2 /* VPNToggleIntent.swift */, + 7B1681042D10BC7B005EAE24 /* VPNIntents.swift */, 7B4DC5E02CB2D87C00EE5CC2 /* VPNAutoShortcuts.swift */, ); name = Intents; @@ -4466,6 +4471,7 @@ 7B059F1C2D03A7E400371ED0 /* WidgetsShared.xcassets */, 4BCBE45D2BA7E81F00FC75A1 /* PrivacyInfo.xcprivacy */, 853273AC24FEF49600E3C778 /* ColorExtension.swift */, + 7B10FF232D11A56300F36BF2 /* ControlWidgetVPNIntents.swift */, 853273B124FF114700E3C778 /* DeepLinks.swift */, 8512EA5824ED30D30073EE19 /* Info.plist */, 98B001A2251EABB40090EC07 /* InfoPlist.strings */, @@ -4478,7 +4484,7 @@ 853273AF24FEFE4600E3C778 /* WidgetsExtension.entitlements */, 853273A924FEF24300E3C778 /* WidgetViews.swift */, 7B4DC5BF2CB2A4A500EE5CC2 /* VPNStatusValueProvider.swift */, - 7B16810B2D10CF44005EAE24 /* VPNWidgetIntents.swift */, + 7B16810B2D10CF44005EAE24 /* WidgetVPNIntents.swift */, 4BB7CBAF2AF59C310014A35F /* VPNWidget.swift */, 7BFC32AF2CB291BB007A8E17 /* VPNControlWidget.swift */, ); @@ -6771,6 +6777,8 @@ 9F8FE9482BAE50E50071E372 /* Lottie */, 9F96F73A2C9144D5009E45D5 /* Onboarding */, 1E5918462CA422A7008ED2B3 /* Navigation */, + 7B2CCBA42D11ABBA00FE5852 /* VPNWidgetSupport */, + 7B2CCBA82D11F02800FE5852 /* VPNAppIntents */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -6820,6 +6828,8 @@ name = WidgetsExtension; packageProductDependencies = ( 4BBBBA862B02E85400D965DA /* DesignResourcesKit */, + 7B2CCBA22D11ABB100FE5852 /* VPNWidgetSupport */, + 7B2CCBA62D11F01F00FE5852 /* VPNAppIntents */, ); productName = WidgetsExtension; productReference = 8512EA4D24ED30D20073EE19 /* WidgetsExtension.appex */; @@ -7643,7 +7653,6 @@ 6FDC64012C92F4A300DB71B3 /* NewTabPageIntroDataStoring.swift in Sources */, 9F46BEF82CD8D7490092E0EF /* OnboardingView+AddToDockContent.swift in Sources */, B609D5522862EAFF0088CAC2 /* InlineWKDownloadDelegate.swift in Sources */, - 7B4DC5BE2CB29D8400EE5CC2 /* VPNToggleIntent.swift in Sources */, BDFF03222BA3D8E200F324C9 /* NetworkProtectionFeatureVisibility.swift in Sources */, B652DEFD287BE67400C12A9C /* UserScripts.swift in Sources */, 3768D8472C2CC98C004120AE /* RemoteMessagingConfigMatcherProvider.swift in Sources */, @@ -7855,7 +7864,7 @@ 1EEF12502851016B003DDE57 /* PrivacyIconAndTrackersAnimator.swift in Sources */, 31CB4251273AF50700FA0F3F /* SpeechRecognizerProtocol.swift in Sources */, 319A37172829C8AD0079FBCE /* UITableViewExtension.swift in Sources */, - 7B1681062D10BC96005EAE24 /* VPNAppIntents.swift in Sources */, + 7B1681062D10BC96005EAE24 /* VPNIntents.swift in Sources */, 85EE7F59224673C5000FE757 /* WebContainerNavigationController.swift in Sources */, 6FD1BAE52B87A107000C475C /* AdAttributionReporterStorage.swift in Sources */, 6F5CC0812C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift in Sources */, @@ -7916,7 +7925,6 @@ 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, 56D060262C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift in Sources */, 9F8E0F382CCFAA8A001EA7C5 /* AddToDockPromoView.swift in Sources */, - 373608922ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift in Sources */, C160544129D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift in Sources */, 31C70B5528045E3500FB6AD1 /* SecureVaultReporter.swift in Sources */, F4CE6D1B257EA33C00D0A6AA /* FireButtonAnimator.swift in Sources */, @@ -7938,6 +7946,7 @@ 564DE4532C3ED1B700D23241 /* NewTabDaxDialogFactory.swift in Sources */, 1EDE39D22705D4A200C99C72 /* FileSizeDebugViewController.swift in Sources */, 4B412ACC2BBB3D0900A39F5E /* LazyView.swift in Sources */, + 7B10FF252D11A56300F36BF2 /* ControlWidgetVPNIntents.swift in Sources */, 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, @@ -7977,7 +7986,6 @@ 3158461A281B08F5004ADB8B /* AutofillLoginListViewModel.swift in Sources */, 31C138A827A3E9C900FFD4B2 /* URLDownloadSession.swift in Sources */, 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */, - 7B1680FF2D106333005EAE24 /* VPNIntentTunnelController.swift in Sources */, 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */, 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */, CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */, @@ -8068,6 +8076,7 @@ 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 1D200C972BA3157A00108701 /* SettingsNextStepsView.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, + 7B020B9A2D11F99D00876178 /* FavoritesDisplayMode+UserDefaults.swift in Sources */, 7B1C892C2CF714AA0008224E /* VPNTipsModel.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */, @@ -8154,7 +8163,7 @@ 8590CB67268A2E520089F6BF /* RootDebugViewController.swift in Sources */, 1DEAADEA2BA4539800E25A97 /* SettingsAppearanceView.swift in Sources */, B623C1C22862CA9E0043013E /* DownloadSession.swift in Sources */, - 7B16810C2D10CF44005EAE24 /* VPNWidgetIntents.swift in Sources */, + 7B16810C2D10CF44005EAE24 /* WidgetVPNIntents.swift in Sources */, 9F7CFF7F2C8A94F70012833E /* OnboardingView+AddressBarPositionContent.swift in Sources */, 985892522260B1B200EEB31B /* ProgressView.swift in Sources */, 85BA585A1F3506AE00C6E8CA /* AppSettings.swift in Sources */, @@ -8426,17 +8435,16 @@ 853273AE24FEF49600E3C778 /* ColorExtension.swift in Sources */, 4BD96E0F2C4DCFEB003BC32C /* VPNSnoozeActivityAttributes.swift in Sources */, 7B1681012D106CB9005EAE24 /* UserTextShared.swift in Sources */, + 7B10FF242D11A56300F36BF2 /* ControlWidgetVPNIntents.swift in Sources */, 373608932ABB432600629E7F /* FavoritesDisplayMode+UserDefaults.swift in Sources */, 4BD96E102C4DF329003BC32C /* VPNSnoozeLiveActivityManager.swift in Sources */, 7B16810A2D10C680005EAE24 /* VPNControlWidget.swift in Sources */, 853273B324FF114700E3C778 /* DeepLinks.swift in Sources */, - 7B4DC5BD2CB29D8400EE5CC2 /* VPNToggleIntent.swift in Sources */, 7B4DC5C22CB2AE4600EE5CC2 /* WidgetKind.swift in Sources */, 853273B424FFB36100E3C778 /* UIColorExtension.swift in Sources */, 853273AB24FEF27500E3C778 /* WidgetViews.swift in Sources */, 7B1681092D10C678005EAE24 /* VPNStatusValueProvider.swift in Sources */, - 7B1681032D106E1D005EAE24 /* VPNIntentTunnelController.swift in Sources */, - 7B16810D2D10CF44005EAE24 /* VPNWidgetIntents.swift in Sources */, + 7B16810D2D10CF44005EAE24 /* WidgetVPNIntents.swift in Sources */, 4BB7CBB02AF59C310014A35F /* VPNWidget.swift in Sources */, 8512EA5424ED30D20073EE19 /* Widgets.swift in Sources */, 85DB12EB2A1FE2A4000A4A72 /* LockScreenWidgets.swift in Sources */, @@ -11522,6 +11530,22 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Common; }; + 7B2CCBA22D11ABB100FE5852 /* VPNWidgetSupport */ = { + isa = XCSwiftPackageProductDependency; + productName = VPNWidgetSupport; + }; + 7B2CCBA42D11ABBA00FE5852 /* VPNWidgetSupport */ = { + isa = XCSwiftPackageProductDependency; + productName = VPNWidgetSupport; + }; + 7B2CCBA62D11F01F00FE5852 /* VPNAppIntents */ = { + isa = XCSwiftPackageProductDependency; + productName = VPNAppIntents; + }; + 7B2CCBA82D11F02800FE5852 /* VPNAppIntents */ = { + isa = XCSwiftPackageProductDependency; + productName = VPNAppIntents; + }; 851481872A600EFC00ABC65F /* RemoteMessaging */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo/VPNAutoShortcuts.swift b/DuckDuckGo/VPNAutoShortcuts.swift index 05fd7479e2..6d2a916c61 100644 --- a/DuckDuckGo/VPNAutoShortcuts.swift +++ b/DuckDuckGo/VPNAutoShortcuts.swift @@ -19,13 +19,21 @@ import AppIntents import Foundation +import VPNAppIntents +/* +@available(iOS 17.0, *) +public struct VPNAppIntents: AppIntentsPackage { + public static var includedPackages: [any AppIntentsPackage.Type] { + [VPNAppIntents.self] + } +}*/ @available(iOS 17.0, *) struct VPNAutoShortcutsiOS17: AppShortcutsProvider { @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { - AppShortcut(intent: EnableVPNAppIntent(), + AppShortcut(intent: EnableVPNIntent(), phrases: [ "Connect \(.applicationName) VPN", "Connect the \(.applicationName) VPN", @@ -42,7 +50,7 @@ struct VPNAutoShortcutsiOS17: AppShortcutsProvider { "Protect my connection with \(.applicationName)" ], systemImageName: "globe") - AppShortcut(intent: DisableVPNAppIntent(), + AppShortcut(intent: DisableVPNIntent(), phrases: [ "Disconnect \(.applicationName) VPN", "Disconnect the \(.applicationName) VPN", diff --git a/DuckDuckGo/VPNIntentTunnelController.swift b/DuckDuckGo/VPNIntentTunnelController.swift deleted file mode 100644 index 03884adfe9..0000000000 --- a/DuckDuckGo/VPNIntentTunnelController.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// VPNIntentTunnelController.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 Core -import NetworkExtension - -@available(iOS 17.0, *) -struct VPNIntentTunnelController { - - enum StartFailure: CustomNSError { - case vpnNotConfigured - } - - enum StopFailure: CustomNSError { - case vpnNotConfigured - } - - func start() async throws { - let managers = try await NETunnelProviderManager.loadAllFromPreferences() - guard let manager = managers.first else { - throw StartFailure.vpnNotConfigured - } - - manager.isOnDemandEnabled = true - try await manager.saveToPreferences() - try manager.connection.startVPNTunnel() - - await VPNSnoozeLiveActivityManager().endSnoozeActivity() - - VPNReloadStatusWidgets() - } - - func stop() async throws { - let managers = try await NETunnelProviderManager.loadAllFromPreferences() - guard let manager = managers.first else { - throw StopFailure.vpnNotConfigured - } - - manager.isOnDemandEnabled = false - try await manager.saveToPreferences() - manager.connection.stopVPNTunnel() - - await VPNSnoozeLiveActivityManager().endSnoozeActivity() - - VPNReloadStatusWidgets() - } -} diff --git a/DuckDuckGo/VPNAppIntents.swift b/DuckDuckGo/VPNIntents.swift similarity index 84% rename from DuckDuckGo/VPNAppIntents.swift rename to DuckDuckGo/VPNIntents.swift index 9d31388256..d68319b8ab 100644 --- a/DuckDuckGo/VPNAppIntents.swift +++ b/DuckDuckGo/VPNIntents.swift @@ -22,17 +22,18 @@ import NetworkExtension import NetworkProtection import WidgetKit import Core +import VPNWidgetSupport // MARK: - Enable & Disable /// App intent to disable the VPN /// /// This is used in App Shortcuts, for things like Shortcuts.app, Spotlight and Siri. -/// This is very similar to ``WidgetVPNDisableIntent``, but this runs in-app, allows continuation in the app if needed, +/// This is very similar to ``WidgetDisableVPNIntent``, but this runs in-app, allows continuation in the app if needed, /// and provides a result dialog. /// @available(iOS 17.0, *) -struct DisableVPNAppIntent: AppIntent { +struct DisableVPNIntent: AppIntent { private enum DisableAttemptFailure: CustomNSError { case cancelled @@ -49,12 +50,15 @@ struct DisableVPNAppIntent: AppIntent { do { DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetDisconnectAttempt) - let controller = VPNIntentTunnelController() + let controller = VPNWidgetTunnelController() try await controller.stop() + await VPNSnoozeLiveActivityManager().endSnoozeActivity() + VPNReloadStatusWidgets() + DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetDisconnectSuccess) return .result(dialog: "DuckDuckGo VPN is disconnecting...") - } catch VPNIntentTunnelController.StopFailure.vpnNotConfigured { + } catch VPNWidgetTunnelController.StopFailure.vpnNotConfigured { DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetDisconnectCancelled) return .result(dialog: "The DuckDuckGo VPN is not connected") } catch { @@ -67,12 +71,12 @@ struct DisableVPNAppIntent: AppIntent { /// App intent to enable the VPN /// /// This is used in App Shortcuts, for things like Shortcuts.app, Spotlight and Siri. -/// This is very similar to ``VPNWidgetEnableIntent``, but this runs in-app, allows continuation in the app if needed, +/// This is very similar to ``WidgetEnableVPNIntent``, but this runs in-app, allows continuation in the app if needed, /// and provides a result dialog. /// @available(iOS 17.0, *) @available(iOSApplicationExtension, unavailable) -struct EnableVPNAppIntent: ForegroundContinuableIntent { +struct EnableVPNIntent: ForegroundContinuableIntent { static let title: LocalizedStringResource = "Enable DuckDuckGo VPN" static let description: LocalizedStringResource = "Enables the DuckDuckGo VPN" static let openAppWhenRun: Bool = false @@ -84,14 +88,17 @@ struct EnableVPNAppIntent: ForegroundContinuableIntent { do { DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetConnectAttempt) - let controller = VPNIntentTunnelController() + let controller = VPNWidgetTunnelController() try await controller.start() + await VPNSnoozeLiveActivityManager().endSnoozeActivity() + VPNReloadStatusWidgets() + DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetConnectSuccess) return .result(dialog: "DuckDuckGo VPN is connecting...") } catch { switch error { - case VPNIntentTunnelController.StartFailure.vpnNotConfigured: + case VPNWidgetTunnelController.StartFailure.vpnNotConfigured: DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetConnectCancelled) let dialog = IntentDialog(stringLiteral: UserText.vpnNeedsToBeEnabledFromApp) diff --git a/DuckDuckGo/VPNSnoozeActivityAttributes.swift b/DuckDuckGo/VPNSnoozeActivityAttributes.swift index b5155f2ea8..772aad8a8e 100644 --- a/DuckDuckGo/VPNSnoozeActivityAttributes.swift +++ b/DuckDuckGo/VPNSnoozeActivityAttributes.swift @@ -19,7 +19,6 @@ import Foundation import ActivityKit -import SwiftUI struct VPNSnoozeActivityAttributes: ActivityAttributes { struct ContentState: Codable & Hashable { diff --git a/LocalPackages/VPNiOS/.gitignore b/LocalPackages/VPNiOS/.gitignore new file mode 100644 index 0000000000..0023a53406 --- /dev/null +++ b/LocalPackages/VPNiOS/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/LocalPackages/VPNiOS/Package.swift b/LocalPackages/VPNiOS/Package.swift new file mode 100644 index 0000000000..691eaf6434 --- /dev/null +++ b/LocalPackages/VPNiOS/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "VPNiOS", + platforms: [ + .iOS(.v15) + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "VPNWidgetSupport", + targets: ["VPNWidgetSupport"]), + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "VPNAppIntents", + targets: ["VPNAppIntents"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "VPNWidgetSupport" + ), + .target( + name: "VPNAppIntents", + dependencies: ["VPNWidgetSupport"] + ), + ] +) diff --git a/LocalPackages/VPNiOS/Sources/VPNAppIntents/VPNAppIntents.swift b/LocalPackages/VPNiOS/Sources/VPNAppIntents/VPNAppIntents.swift new file mode 100644 index 0000000000..936d09f160 --- /dev/null +++ b/LocalPackages/VPNiOS/Sources/VPNAppIntents/VPNAppIntents.swift @@ -0,0 +1,92 @@ +// +// VPNAppIntents.swift +// VPNiOS +// +// Created by ddg on 12/17/24. +// + +// MARK: - Toggle + +/// `ForegroundContinuableIntent` isn't available for extensions, which makes it impossible to call +/// from extensions. This is the recommended workaround from: +/// https://mastodon.social/@mgorbach/110812347476671807 +/// + +import AppIntents +import VPNWidgetSupport +import WidgetKit + +public struct VPNAppIntents: AppIntentsPackage { } +/* +@available(iOS 17.0, *) +public struct ControlWidgetToggleVPNIntent: SetValueIntent { + public static let title: LocalizedStringResource = "Toggle DuckDuckGo VPN from the Control Center Widget" + public static let description: LocalizedStringResource = "Toggles the DuckDuckGo VPN from the Control Center widget" + public static let isDiscoverable = false + public static let openAppWhenRun = false + + @Parameter(title: "Enabled") + public var value: Bool + + public init() {} + + public func perform() async throws -> some IntentResult { + if value { + try await startVPN() + } else { + try await stopVPN() + } + + return .result() + } + + private func startVPN() async throws { + do { + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectAttempt) + + let controller = VPNWidgetTunnelController() + try await controller.start() + + WidgetCenter.shared.reloadAllTimelines() + + //await VPNSnoozeLiveActivityManager().endSnoozeActivity() + //VPNReloadStatusWidgets() + + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectSuccess) + } catch { + switch error { + case VPNWidgetTunnelController.StartFailure.vpnNotConfigured: + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectCancelled) + throw error + default: + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectFailure, error: error) + throw error + } + } + } + + private func stopVPN() async throws { + do { + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterDisconnectAttempt) + + let controller = VPNWidgetTunnelController() + try await controller.stop() + + WidgetCenter.shared.reloadAllTimelines() + //await VPNSnoozeLiveActivityManager().endSnoozeActivity() + //VPNReloadStatusWidgets() + + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterDisconnectSuccess) + } catch { + switch error { + case VPNWidgetTunnelController.StopFailure.vpnNotConfigured: + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterDisconnectCancelled) + throw error + default: + //DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterDisconnectFailure, error: error) + throw error + } + } + } +} +*/ diff --git a/LocalPackages/VPNiOS/Sources/VPNWidgetSupport/VPNWidgetTunnelController.swift b/LocalPackages/VPNiOS/Sources/VPNWidgetSupport/VPNWidgetTunnelController.swift new file mode 100644 index 0000000000..f95644af83 --- /dev/null +++ b/LocalPackages/VPNiOS/Sources/VPNWidgetSupport/VPNWidgetTunnelController.swift @@ -0,0 +1,96 @@ +// +// VPNIntentTunnelController.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 NetworkExtension + +@available(iOS 17.0, *) +public struct VPNWidgetTunnelController: Sendable { + + public enum StartFailure: CustomNSError { + case vpnNotConfigured + } + + public enum StopFailure: CustomNSError { + case vpnNotConfigured + } + + public init() {} + + public var status: NEVPNStatus { + get async { + guard let manager = try? await NETunnelProviderManager.loadAllFromPreferences().first else { + return .invalid + } + + return manager.connection.status + } + } + + public func start() async throws { + let managers = try await NETunnelProviderManager.loadAllFromPreferences() + guard let manager = managers.first else { + throw StartFailure.vpnNotConfigured + } + + manager.isOnDemandEnabled = true + try await manager.saveToPreferences() + try manager.connection.startVPNTunnel() + + try await awaitUntilStatusIsNoLongerTransitioning(manager: manager) + } + + public func stop() async throws { + let managers = try await NETunnelProviderManager.loadAllFromPreferences() + guard let manager = managers.first else { + throw StopFailure.vpnNotConfigured + } + + manager.isOnDemandEnabled = false + try await manager.saveToPreferences() + manager.connection.stopVPNTunnel() + + try await awaitUntilStatusIsNoLongerTransitioning(manager: manager) + } + + private func awaitUntilStatusIsNoLongerTransitioning(manager: NETunnelProviderManager) async throws { + + while true { + try await Task.sleep(for: .milliseconds(500)) + + if manager.connection.status != .connecting + && manager.connection.status != .disconnecting { + + break + } + } +/* + for await notification in NotificationCenter.default.notifications(named: NSNotification.Name.NEVPNStatusDidChange) { + + try Task.checkCancellation() + + /// If there's no connection in the notification, or the connection status is no longer + /// `connecting` we just bail out here. + guard let connection = notification.object as? NEVPNConnection, + connection.status == .connecting || connection.status == .disconnecting else { + + break + } + }*/ + } +} diff --git a/DuckDuckGo/VPNToggleIntent.swift b/Widgets/ControlWidgetVPNIntents.swift similarity index 78% rename from DuckDuckGo/VPNToggleIntent.swift rename to Widgets/ControlWidgetVPNIntents.swift index c44a76dd66..11d9cfb366 100644 --- a/DuckDuckGo/VPNToggleIntent.swift +++ b/Widgets/ControlWidgetVPNIntents.swift @@ -23,6 +23,8 @@ import NetworkProtection import WidgetKit import Core import OSLog +import VPNWidgetSupport +import VPNAppIntents // MARK: - Toggle @@ -31,10 +33,10 @@ import OSLog /// https://mastodon.social/@mgorbach/110812347476671807 /// @available(iOS 17.0, *) -struct VPNToggleIntent: SetValueIntent { - static let title: LocalizedStringResource = "Toggle DuckDuckGo VPN" - static let description: LocalizedStringResource = "Toggles the DuckDuckGo VPN" - static let isDiscoverable: Bool = false +struct ControlWidgetToggleVPNIntent: SetValueIntent { + static let title: LocalizedStringResource = "Toggle DuckDuckGo VPN from the Control Center Widget" + static let description: LocalizedStringResource = "Toggles the DuckDuckGo VPN from the Control Center widget" + static let isDiscoverable = false @Parameter(title: "Enabled") var value: Bool @@ -43,25 +45,28 @@ struct VPNToggleIntent: SetValueIntent { func perform() async throws -> some IntentResult { if value { try await startVPN() - return .result() } else { try await stopVPN() - return .result() } + + return .result() } private func startVPN() async throws { do { DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectAttempt) - let controller = VPNIntentTunnelController() + let controller = VPNWidgetTunnelController() try await controller.start() + + await VPNSnoozeLiveActivityManager().endSnoozeActivity() + VPNReloadStatusWidgets() + DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectSuccess) } catch { switch error { - case VPNIntentTunnelController.StartFailure.vpnNotConfigured: + case VPNWidgetTunnelController.StartFailure.vpnNotConfigured: DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectCancelled) - throw error default: DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterConnectFailure, error: error) @@ -74,12 +79,16 @@ struct VPNToggleIntent: SetValueIntent { do { DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterDisconnectAttempt) - let controller = VPNIntentTunnelController() + let controller = VPNWidgetTunnelController() try await controller.stop() + + await VPNSnoozeLiveActivityManager().endSnoozeActivity() + VPNReloadStatusWidgets() + DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterDisconnectSuccess) } catch { switch error { - case VPNIntentTunnelController.StopFailure.vpnNotConfigured: + case VPNWidgetTunnelController.StopFailure.vpnNotConfigured: DailyPixel.fireDailyAndCount(pixel: .vpnControlCenterDisconnectCancelled) throw error default: diff --git a/Widgets/VPNControlWidget.swift b/Widgets/VPNControlWidget.swift index 4495e41897..d016a061b3 100644 --- a/Widgets/VPNControlWidget.swift +++ b/Widgets/VPNControlWidget.swift @@ -19,11 +19,12 @@ import Foundation import SwiftUI +import VPNAppIntents import WidgetKit @available(iOSApplicationExtension 18.0, *) public struct VPNControlWidget: ControlWidget { - static let displayName = LocalizedStringResource(stringLiteral: "VPN") + static let displayName = LocalizedStringResource(stringLiteral: "DuckDuckGo VPN") static let description = LocalizedStringResource(stringLiteral: "View and manage your VPN connection. Requires a Privacy Pro subscription.") public init() {} @@ -32,7 +33,7 @@ public struct VPNControlWidget: ControlWidget { StaticControlConfiguration(kind: .vpn, provider: VPNControlStatusValueProvider()) { status in - ControlWidgetToggle("DuckDuckGo VPN", isOn: status.isConnected, action: VPNToggleIntent()) { isOn in + ControlWidgetToggle("DuckDuckGo VPN", isOn: status.isConnected, action: ControlWidgetToggleVPNIntent()) { isOn in if isOn { Label("Enabled", image: "ControlCenter-VPN-on") } else { diff --git a/Widgets/VPNStatusValueProvider.swift b/Widgets/VPNStatusValueProvider.swift index 1da8a1f9a1..b5d3ba1c7a 100644 --- a/Widgets/VPNStatusValueProvider.swift +++ b/Widgets/VPNStatusValueProvider.swift @@ -27,6 +27,7 @@ struct VPNControlStatusValueProvider: ControlValueProvider { func currentValue() async throws -> VPNStatus { guard let manager = try await NETunnelProviderManager.loadAllFromPreferences().first else { + return .notConfigured } diff --git a/Widgets/VPNWidget.swift b/Widgets/VPNWidget.swift index a2f5af1ab4..35dc033842 100644 --- a/Widgets/VPNWidget.swift +++ b/Widgets/VPNWidget.swift @@ -174,7 +174,7 @@ struct VPNStatusView: View { switch status { case .connected: let buttonTitle = snoozeTimingStore.isSnoozing ? UserText.vpnWidgetLiveActivityWakeUpButton : UserText.vpnWidgetDisconnectButton - let intent: any AppIntent = snoozeTimingStore.isSnoozing ? CancelSnoozeVPNIntent() : VPNWidgetDisableIntent() + let intent: any AppIntent = snoozeTimingStore.isSnoozing ? CancelSnoozeVPNIntent() : WidgetDisableVPNIntent() Button(buttonTitle, intent: intent) .font(.system(size: 14, weight: .semibold)) @@ -192,7 +192,7 @@ struct VPNStatusView: View { .padding(.top, 6) .padding(.bottom, 16) case .connecting, .reasserting: - Button(UserText.vpnWidgetDisconnectButton, intent: VPNWidgetDisableIntent()) + Button(UserText.vpnWidgetDisconnectButton, intent: WidgetDisableVPNIntent()) .font(.system(size: 14, weight: .semibold)) .foregroundStyle(disconnectButtonForegroundColor(isDisabled: status != .connected)) .buttonStyle(.borderedProminent) @@ -234,7 +234,7 @@ struct VPNStatusView: View { private var connectButton: Button { switch entry.status { case .status: - Button(UserText.vpnWidgetConnectButton, intent: VPNWidgetEnableIntent()) + Button(UserText.vpnWidgetConnectButton, intent: WidgetEnableVPNIntent()) case .error, .notConfigured: Button(UserText.vpnWidgetConnectButton) { openURL(DeepLinks.openVPN) diff --git a/Widgets/WidgetKind.swift b/Widgets/WidgetKind.swift index 42c748f139..2c388c5855 100644 --- a/Widgets/WidgetKind.swift +++ b/Widgets/WidgetKind.swift @@ -46,8 +46,8 @@ extension ControlCenter { extension StaticControlConfiguration { @MainActor @preconcurrency init(kind: ControlWidgetKind, - provider: Provider, @ControlWidgetTemplateBuilder - content: @escaping (Provider.Value) -> Content) + provider: Provider, + @ControlWidgetTemplateBuilder content: @escaping (Provider.Value) -> Content) where Provider: ControlValueProvider { self.init(kind: kind.rawValue, provider: provider, content: content) } diff --git a/Widgets/VPNWidgetIntents.swift b/Widgets/WidgetVPNIntents.swift similarity index 88% rename from Widgets/VPNWidgetIntents.swift rename to Widgets/WidgetVPNIntents.swift index 70d77eb270..32609cd25c 100644 --- a/Widgets/VPNWidgetIntents.swift +++ b/Widgets/WidgetVPNIntents.swift @@ -22,6 +22,7 @@ import NetworkExtension import NetworkProtection import WidgetKit import Core +import VPNWidgetSupport // MARK: - Enable & Disable @@ -32,7 +33,7 @@ import Core /// does not support continuation in the app and does not provide any result dialog. /// @available(iOS 17.0, *) -struct VPNWidgetDisableIntent: AppIntent { +struct WidgetDisableVPNIntent: AppIntent { private enum DisableAttemptFailure: CustomNSError { case cancelled @@ -41,7 +42,7 @@ struct VPNWidgetDisableIntent: AppIntent { static let title: LocalizedStringResource = "Disable DuckDuckGo VPN" static let description: LocalizedStringResource = "Disables the DuckDuckGo VPN" static let openAppWhenRun: Bool = false - static let isDiscoverable: Bool = true + static let isDiscoverable: Bool = false static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication @MainActor @@ -49,12 +50,15 @@ struct VPNWidgetDisableIntent: AppIntent { do { DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetDisconnectAttempt) - let controller = VPNIntentTunnelController() + let controller = VPNWidgetTunnelController() try await controller.stop() + await VPNSnoozeLiveActivityManager().endSnoozeActivity() + VPNReloadStatusWidgets() + DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetDisconnectSuccess) return .result() - } catch VPNIntentTunnelController.StopFailure.vpnNotConfigured { + } catch VPNWidgetTunnelController.StopFailure.vpnNotConfigured { DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetDisconnectCancelled) return .result() } catch { @@ -71,11 +75,11 @@ struct VPNWidgetDisableIntent: AppIntent { /// does not support continuation in the app and does not provide any result dialog. /// @available(iOS 17.0, *) -struct VPNWidgetEnableIntent: AppIntent { +struct WidgetEnableVPNIntent: AppIntent { static let title: LocalizedStringResource = "Enable DuckDuckGo VPN" static let description: LocalizedStringResource = "Enables the DuckDuckGo VPN" static let openAppWhenRun: Bool = false - static let isDiscoverable: Bool = true + static let isDiscoverable: Bool = false static var authenticationPolicy: IntentAuthenticationPolicy = .alwaysAllowed @MainActor @@ -83,14 +87,17 @@ struct VPNWidgetEnableIntent: AppIntent { do { DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetConnectAttempt) - let controller = VPNIntentTunnelController() + let controller = VPNWidgetTunnelController() try await controller.start() + await VPNSnoozeLiveActivityManager().endSnoozeActivity() + VPNReloadStatusWidgets() + DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetConnectSuccess) return .result() } catch { switch error { - case VPNIntentTunnelController.StartFailure.vpnNotConfigured: + case VPNWidgetTunnelController.StartFailure.vpnNotConfigured: DailyPixel.fireDailyAndCount(pixel: .networkProtectionWidgetConnectCancelled) throw error default: @@ -128,7 +135,6 @@ struct CancelSnoozeVPNIntent: AppIntent { return .result() } } - } @available(iOS 17.0, *) diff --git a/Widgets/Widgets.swift b/Widgets/Widgets.swift index 8cc9844e41..e1f0cfccbb 100644 --- a/Widgets/Widgets.swift +++ b/Widgets/Widgets.swift @@ -17,6 +17,7 @@ // limitations under the License. // +import AppIntents import Common import WidgetKit import SwiftUI @@ -27,6 +28,14 @@ import Bookmarks import Persistence import NetworkExtension import os.log +import VPNAppIntents + +@available(iOS 17.0, *) +public struct WidgetsExtension: AppIntentsPackage { + public static var includedPackages: [any AppIntentsPackage.Type] { + [VPNAppIntents.self] + } +} struct Favorite { diff --git a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/Contents.json b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/Contents.json index 87a30f4e23..ca0236fb00 100644 --- a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/Contents.json +++ b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/Contents.json @@ -3,12 +3,9 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { - "filename" : "VPN-Off.svg", + "filename" : "VPNOFF4.svg", "idiom" : "universal" } ] diff --git a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/VPN-Off.svg b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/VPN-Off.svg deleted file mode 100644 index 489c1ead02..0000000000 --- a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/VPN-Off.svg +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - Weight/Scale Variations - Ultralight - Thin - Light - Regular - Medium - Semibold - Bold - Heavy - Black - - - - - Design Variations - Symbols are supported in up to nine weights and three scales. - For optimal layout with text and other symbols, vertically align - symbols with the adjacent text. - - - - Margins - Leading and trailing margins on the left and right side of each symbol - can be adjusted by modifying the x-location of the margin guidelines. - Modifications are automatically applied proportionally to all - scales and weights. - - Exporting - Symbols should be outlined when exporting to ensure the - design is preserved when submitting to Xcode. - Template v.6.0 - Requires Xcode 16 or greater - Generated from circle - Typeset at 100.0 points - Small - Medium - Large - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/VPNOFF4.svg b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/VPNOFF4.svg new file mode 100644 index 0000000000..123cf8d738 --- /dev/null +++ b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-off.symbolset/VPNOFF4.svg @@ -0,0 +1,119 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.6.0 + Requires Xcode 16 or greater + Generated from trash.fill + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/Contents.json b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/Contents.json index 3cf25de919..7bc2896a16 100644 --- a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/Contents.json +++ b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/Contents.json @@ -3,12 +3,9 @@ "author" : "xcode", "version" : 1 }, - "properties" : { - "symbol-rendering-intent" : "template" - }, "symbols" : [ { - "filename" : "VPN-On.svg", + "filename" : "VPNON4.svg", "idiom" : "universal" } ] diff --git a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/VPN-On.svg b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/VPN-On.svg deleted file mode 100644 index 253c613363..0000000000 --- a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/VPN-On.svg +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - Weight/Scale Variations - Ultralight - Thin - Light - Regular - Medium - Semibold - Bold - Heavy - Black - - - - - Design Variations - Symbols are supported in up to nine weights and three scales. - For optimal layout with text and other symbols, vertically align - symbols with the adjacent text. - - - - Margins - Leading and trailing margins on the left and right side of each symbol - can be adjusted by modifying the x-location of the margin guidelines. - Modifications are automatically applied proportionally to all - scales and weights. - - Exporting - Symbols should be outlined when exporting to ensure the - design is preserved when submitting to Xcode. - Template v.6.0 - Requires Xcode 16 or greater - Generated from circle - Typeset at 100.0 points - Small - Medium - Large - - - - - Weight/Scale Variations - Ultralight - Thin - Light - Regular - Medium - Semibold - Bold - Heavy - Black - - - - - Design Variations - Symbols are supported in up to nine weights and three scales. - For optimal layout with text and other symbols, vertically align - symbols with the adjacent text. - - - - Margins - Leading and trailing margins on the left and right side of each symbol - can be adjusted by modifying the x-location of the margin guidelines. - Modifications are automatically applied proportionally to all - scales and weights. - - Exporting - Symbols should be outlined when exporting to ensure the - design is preserved when submitting to Xcode. - Template v.6.0 - Requires Xcode 16 or greater - Generated from circle - Typeset at 100.0 points - Small - Medium - Large - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/VPNON4.svg b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/VPNON4.svg new file mode 100644 index 0000000000..63d2a85180 --- /dev/null +++ b/Widgets/WidgetsShared.xcassets/SFSymbols/ControlCenter-VPN-on.symbolset/VPNON4.svg @@ -0,0 +1,119 @@ + + + + + + + + + + Weight/Scale Variations + Ultralight + Thin + Light + Regular + Medium + Semibold + Bold + Heavy + Black + + + + + + + + + + + Design Variations + Symbols are supported in up to nine weights and three scales. + For optimal layout with text and other symbols, vertically align + symbols with the adjacent text. + + + + + + Margins + Leading and trailing margins on the left and right side of each symbol + can be adjusted by modifying the x-location of the margin guidelines. + Modifications are automatically applied proportionally to all + scales and weights. + + + + Exporting + Symbols should be outlined when exporting to ensure the + design is preserved when submitting to Xcode. + Template v.6.0 + Requires Xcode 16 or greater + Generated from trash.fill + Typeset at 100.0 points + Small + Medium + Large + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +