diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bb1472bf42..5a4dbfe3b6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -257,9 +257,6 @@ 37DF000F29F9D635002B7D3E /* SyncBookmarksAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DF000E29F9D635002B7D3E /* SyncBookmarksAdapter.swift */; }; 37E615752A5F533E00ACD63D /* SyncCredentialsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E615742A5F533E00ACD63D /* SyncCredentialsAdapter.swift */; }; 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAAA29911BF1000E420A /* WaitlistExtensions.swift */; }; - 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAB129914232000E420A /* WindowsBrowserWaitlistView.swift */; }; - 37FCAAB429914C77000E420A /* WindowsWaitlistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAB329914C77000E420A /* WindowsWaitlistViewController.swift */; }; - 37FCAAB629919CEB000E420A /* WindowsBrowserWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAB529919CEB000E420A /* WindowsBrowserWaitlist.swift */; }; 37FCAABC2992F592000E420A /* MultilineScrollableTextFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAABB2992F592000E420A /* MultilineScrollableTextFix.swift */; }; 37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAABF29930E26000E420A /* FailedAssertionView.swift */; }; 37FD780F2A29E28B00B36DB1 /* SyncErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD780E2A29E28B00B36DB1 /* SyncErrorHandler.swift */; }; @@ -278,12 +275,7 @@ 4B60AC97252EC07B00E8D219 /* fullscreenvideo.js in Resources */ = {isa = PBXBuildFile; fileRef = 4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */; }; 4B60ACA1252EC0B100E8D219 /* FullScreenVideoUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */; }; 4B62C4BA25B930DD008912C6 /* AppConfigurationFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */; }; - 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E027FD1E340050A7A1 /* MacBrowserWaitlistView.swift */; }; - 4B6484ED27FD1E350050A7A1 /* MacBrowserWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E327FD1E340050A7A1 /* MacBrowserWaitlist.swift */; }; - 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E427FD1E340050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift */; }; - 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E527FD1E340050A7A1 /* MacWaitlistViewController.swift */; }; 4B6484F327FD1E350050A7A1 /* MenuControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E927FD1E340050A7A1 /* MenuControllerView.swift */; }; - 4B6484FC27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484FB27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift */; }; 4B75EA9226A266CB00018634 /* PrintingUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B75EA9126A266CB00018634 /* PrintingUserScript.swift */; }; 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B78074D2B183A1F009DB2CF /* SurveyURLBuilder.swift */; }; 4B83396C29AC0701003F7EA9 /* AppTrackingProtectionStoringModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B470EE2299C6DD10086EBDC /* AppTrackingProtectionStoringModel.swift */; }; @@ -315,7 +307,6 @@ 4BEF656D2989C2FC00B650CB /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D307E2989C0C800918636 /* EventType.swift */; }; 4BEF656E2989C2FC00B650CB /* ProxySocketEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D307B2989C0C600918636 /* ProxySocketEvent.swift */; }; 4BFB911B29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFB911A29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift */; }; - 56244C1D2A137B1900EDF259 /* WaitlistViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */; }; 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */; }; 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */; }; 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */; }; @@ -797,6 +788,8 @@ D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; D69DBB502B72B1D300156310 /* View+TopMostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */; }; D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; + D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; + D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8B2B291CA90054390C /* URL+Subscription.swift */; }; D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */; }; D6D12CA12B291CA90054390C /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8D2B291CA90054390C /* Logging.swift */; }; @@ -813,6 +806,10 @@ D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9E2B291CA90054390C /* PurchaseManager.swift */; }; D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */; }; D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; + D6E0C1832B7A2B1E00D5E1E9 /* DesktopDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */; }; + D6E0C1852B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */; }; + D6E0C1872B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1862B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift */; }; + D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1882B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift */; }; D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; @@ -1363,9 +1360,6 @@ 37DF000E29F9D635002B7D3E /* SyncBookmarksAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncBookmarksAdapter.swift; sourceTree = ""; }; 37E615742A5F533E00ACD63D /* SyncCredentialsAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncCredentialsAdapter.swift; sourceTree = ""; }; 37FCAAAA29911BF1000E420A /* WaitlistExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistExtensions.swift; sourceTree = ""; }; - 37FCAAB129914232000E420A /* WindowsBrowserWaitlistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlistView.swift; sourceTree = ""; }; - 37FCAAB329914C77000E420A /* WindowsWaitlistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsWaitlistViewController.swift; sourceTree = ""; }; - 37FCAAB529919CEB000E420A /* WindowsBrowserWaitlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlist.swift; sourceTree = ""; }; 37FCAABB2992F592000E420A /* MultilineScrollableTextFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineScrollableTextFix.swift; sourceTree = ""; }; 37FCAABF29930E26000E420A /* FailedAssertionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAssertionView.swift; sourceTree = ""; }; 37FCAACB2993149A000E420A /* Waitlist */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Waitlist; sourceTree = ""; }; @@ -1383,12 +1377,7 @@ 4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = fullscreenvideo.js; sourceTree = ""; }; 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenVideoUserScript.swift; sourceTree = ""; }; 4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationFetchTests.swift; sourceTree = ""; }; - 4B6484E027FD1E340050A7A1 /* MacBrowserWaitlistView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacBrowserWaitlistView.swift; sourceTree = ""; }; - 4B6484E327FD1E340050A7A1 /* MacBrowserWaitlist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacBrowserWaitlist.swift; sourceTree = ""; }; - 4B6484E427FD1E340050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlistDebugViewController.swift; sourceTree = ""; }; - 4B6484E527FD1E340050A7A1 /* MacWaitlistViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacWaitlistViewController.swift; sourceTree = ""; }; 4B6484E927FD1E340050A7A1 /* MenuControllerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuControllerView.swift; sourceTree = ""; }; - 4B6484FB27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlistTests.swift; sourceTree = ""; }; 4B75EA9126A266CB00018634 /* PrintingUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintingUserScript.swift; sourceTree = ""; }; 4B78074B2B1823C5009DB2CF /* VPNWaitlistActivationDateStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNWaitlistActivationDateStore.swift; sourceTree = ""; }; 4B78074D2B183A1F009DB2CF /* SurveyURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyURLBuilder.swift; sourceTree = ""; }; @@ -1410,7 +1399,6 @@ 4BCD146C2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAccessControllerTests.swift; sourceTree = ""; }; 4BE27566272F878F006B20B0 /* URLRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLRequestExtension.swift; path = ../DuckDuckGo/URLRequestExtension.swift; sourceTree = ""; }; 4BFB911A29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionStoringModelPerformanceTests.swift; sourceTree = ""; }; - 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistViews.swift; sourceTree = ""; }; 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimator.swift; sourceTree = ""; }; 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimatorTests.swift; sourceTree = ""; }; 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = ""; }; @@ -2461,6 +2449,8 @@ D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+TopMostController.swift"; sourceTree = ""; }; D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; + D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; + D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; D6D12C8B2B291CA90054390C /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPurchaseEnvironment.swift; sourceTree = ""; }; D6D12C8D2B291CA90054390C /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; @@ -2477,6 +2467,10 @@ D6D12C9E2B291CA90054390C /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootPresentationMode.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; + D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadView.swift; sourceTree = ""; }; + D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadPlatformConstants.swift; sourceTree = ""; }; + D6E0C1862B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadViewButtonStyle.swift; sourceTree = ""; }; + D6E0C1882B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadViewModel.swift; sourceTree = ""; }; D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -3483,27 +3477,6 @@ name = Sync; sourceTree = ""; }; - 37FCAAA0299117F9000E420A /* MacBrowser */ = { - isa = PBXGroup; - children = ( - 4B6484E327FD1E340050A7A1 /* MacBrowserWaitlist.swift */, - 4B6484E027FD1E340050A7A1 /* MacBrowserWaitlistView.swift */, - 4B6484E527FD1E340050A7A1 /* MacWaitlistViewController.swift */, - ); - name = MacBrowser; - sourceTree = ""; - }; - 37FCAAA129911801000E420A /* WindowsBrowser */ = { - isa = PBXGroup; - children = ( - 37FCAAB529919CEB000E420A /* WindowsBrowserWaitlist.swift */, - 37FCAAB129914232000E420A /* WindowsBrowserWaitlistView.swift */, - 37FCAAB329914C77000E420A /* WindowsWaitlistViewController.swift */, - 4B6484E427FD1E340050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift */, - ); - name = WindowsBrowser; - sourceTree = ""; - }; 4B274F5E2AFEAEB3003F0745 /* Widget */ = { isa = PBXGroup; children = ( @@ -3539,9 +3512,6 @@ isa = PBXGroup; children = ( 37FCAAAA29911BF1000E420A /* WaitlistExtensions.swift */, - 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */, - 37FCAAA0299117F9000E420A /* MacBrowser */, - 37FCAAA129911801000E420A /* WindowsBrowser */, 4BBBBA882B031B3300D965DA /* VPN */, 8524AAAB2A3888FE00EEC6D2 /* Waitlist.xcassets */, ); @@ -3551,7 +3521,6 @@ 4B6484F927FFCF520050A7A1 /* Waitlist */ = { isa = PBXGroup; children = ( - 4B6484FB27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift */, ); name = Waitlist; sourceTree = ""; @@ -3777,6 +3746,7 @@ F1668BCC1E798025008CBA04 /* Bookmarks */, 9830A05725ED0C5D00DB64DE /* BrowsingMenu */, B652DF02287C01EE00C12A9C /* ContentBlocking */, + D6E0C1812B7A2B0700D5E1E9 /* DesktopDownloads */, 310D09192799EF5C00DC0060 /* Downloads */, F143C2C51E4A08F300CFDE3A /* DuckDuckGo.entitlements */, C159DF052A430B36007834BB /* EmailProtection */, @@ -4563,6 +4533,7 @@ D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */, D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */, D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */, + D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -4584,6 +4555,7 @@ D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */, D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */, D668D9242B693778008E2FF2 /* SubscriptionITPView.swift */, + D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */, D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */, ); @@ -4594,8 +4566,8 @@ isa = PBXGroup; children = ( D668D92C2B696945008E2FF2 /* Subscription.swift */, - D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, + D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, ); @@ -4667,6 +4639,17 @@ path = AsyncHeadlessWebview; sourceTree = ""; }; + D6E0C1812B7A2B0700D5E1E9 /* DesktopDownloads */ = { + isa = PBXGroup; + children = ( + D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */, + D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */, + D6E0C1862B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift */, + D6E0C1882B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift */, + ); + name = DesktopDownloads; + sourceTree = ""; + }; D6E83C322B1F1279006C8AFB /* Sections */ = { isa = PBXGroup; children = ( @@ -6659,6 +6642,7 @@ 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, 8536A1FD2ACF114B003AC5BA /* Theme+DesignSystem.swift in Sources */, F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */, + D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */, D668D9252B693778008E2FF2 /* SubscriptionITPView.swift in Sources */, C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */, 982E5630222C3D5B008D861B /* FeedbackPickerViewController.swift in Sources */, @@ -6760,13 +6744,14 @@ CBFCB30E2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, + D6E0C1852B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift in Sources */, + D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */, 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */, 8C4724502217A14B004C9B2D /* TabViewControllerLongPressBookmarkExtension.swift in Sources */, 1EDE39D22705D4A200C99C72 /* FileSizeDebugViewController.swift in Sources */, 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */, - 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */, @@ -6798,7 +6783,6 @@ CBD4F13D279EBFA000B20FD7 /* HomeMessageCollectionViewCell.swift in Sources */, 8505836D219F424500ED4EDB /* Point.swift in Sources */, 3158461A281B08F5004ADB8B /* AutofillLoginListViewModel.swift in Sources */, - 37FCAAB429914C77000E420A /* WindowsWaitlistViewController.swift in Sources */, 31C138A827A3E9C900FFD4B2 /* URLDownloadSession.swift in Sources */, 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */, D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */, @@ -6809,7 +6793,6 @@ D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, - 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */, C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */, 8531A08E1F9950E6000484F0 /* UnprotectedSitesViewController.swift in Sources */, CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */, @@ -6853,6 +6836,7 @@ 311BD1B12836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift in Sources */, 4BBBBA902B031B4200D965DA /* VPNWaitlist.swift in Sources */, B652DF13287C373A00C12A9C /* ScriptSourceProviding.swift in Sources */, + D6E0C1872B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift in Sources */, 854A012B2A54412600FCC628 /* ActivityViewController.swift in Sources */, F1CA3C391F045885005FADB3 /* PrivacyUserDefaults.swift in Sources */, BD862E072B30F5E30073E2EE /* VPNFeedbackSender.swift in Sources */, @@ -6894,12 +6878,12 @@ 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, 858566FB252E55D6007501B8 /* ImageCacheDebugViewController.swift in Sources */, 0290472E29E99A2F0008FE3C /* GenericIconView.swift in Sources */, + D6E0C1832B7A2B1E00D5E1E9 /* DesktopDownloadView.swift in Sources */, 1E7A71172934EB6400B7EA19 /* OmniBarNotificationAnimator.swift in Sources */, 85C2971A248162CA0063A335 /* DaxOnboardingPadViewController.swift in Sources */, F4F6DFB826EA9AA600ED7E12 /* BookmarksTextFieldCell.swift in Sources */, 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, - 4B6484ED27FD1E350050A7A1 /* MacBrowserWaitlist.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, 1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */, 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */, @@ -6907,10 +6891,8 @@ F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */, F44D279C27F331BB0037F371 /* AutofillLoginPromptView.swift in Sources */, CBD4F13E279EBFAB00B20FD7 /* HomeMessageView.swift in Sources */, - 56244C1D2A137B1900EDF259 /* WaitlistViews.swift in Sources */, 851DFD87212C39D300D95F20 /* TabSwitcherButton.swift in Sources */, 8505836A219F424500ED4EDB /* UIAlertControllerExtension.swift in Sources */, - 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */, C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, @@ -6927,7 +6909,6 @@ 027F48782A4B663C001A1C6C /* AppTPFAQView.swift in Sources */, D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, - 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */, 1EE411F12857C3640003FE64 /* TrackerAnimationImageProvider.swift in Sources */, 1E7A711C2934EEBC00B7EA19 /* OmniBarNotification.swift in Sources */, @@ -6974,12 +6955,12 @@ EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */, + D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */, 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, F17669D71E43401C003D3222 /* MainViewController.swift in Sources */, 984D60B2222A1284003B9E3B /* FeedbackFormViewController.swift in Sources */, 31A42564285A09E800049386 /* FaviconView.swift in Sources */, 85374D3821AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift in Sources */, - 37FCAAB629919CEB000E420A /* WindowsBrowserWaitlist.swift in Sources */, 98E888F2223FCC4A00B608A4 /* OnboardingViewController.swift in Sources */, C1B7B51C28941E980098FD6A /* HomeMessageViewModelBuilder.swift in Sources */, 85BA58551F34F49E00C6E8CA /* AppUserDefaults.swift in Sources */, @@ -7113,7 +7094,6 @@ 98983096255B5019003339A2 /* BookmarksCachingSearchTests.swift in Sources */, EE7917912A83DE93008DFF28 /* CombineTestUtilities.swift in Sources */, 85480CB429226B3B007E8F13 /* CrashCollectionExtensionTests.swift in Sources */, - 4B6484FC27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift in Sources */, 8540BD5223D8C2220057FDD2 /* PreserveLoginsTests.swift in Sources */, 85F200072217032E006BB258 /* AddressDisplayHelperTests.swift in Sources */, B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index 5c1c37c51f..db54ce6826 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -34,7 +34,6 @@ extension AppDelegate { } func checkWaitlists() { - checkWindowsWaitlist() #if NETWORK_PROTECTION checkNetworkProtectionWaitlist() @@ -43,13 +42,6 @@ extension AppDelegate { } - private func checkWindowsWaitlist() { - WindowsBrowserWaitlist.shared.fetchInviteCodeIfAvailable { error in - guard error == nil else { return } - WindowsBrowserWaitlist.shared.sendInviteCodeAvailableNotification() - } - } - #if NETWORK_PROTECTION private func checkNetworkProtectionWaitlist() { let accessController = NetworkProtectionAccessController() @@ -96,10 +88,6 @@ extension AppDelegate { private func checkWaitlistBackgroundTasks() { BGTaskScheduler.shared.getPendingTaskRequests { tasks in - let hasWindowsBrowserWaitlistTask = tasks.contains { $0.identifier == WindowsBrowserWaitlist.backgroundRefreshTaskIdentifier } - if !hasWindowsBrowserWaitlistTask { - WindowsBrowserWaitlist.shared.scheduleBackgroundRefreshTask() - } #if NETWORK_PROTECTION let hasVPNWaitlistTask = tasks.contains { $0.identifier == VPNWaitlist.backgroundRefreshTaskIdentifier } diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index bc1a067368..f84dedc923 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -290,7 +290,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Task handler registration needs to happen before the end of `didFinishLaunching`, otherwise submitting a task can throw an exception. // Having both in `didBecomeActive` can sometimes cause the exception when running on a physical device, so registration happens here. AppConfigurationFetch.registerBackgroundRefreshTaskHandler() - WindowsBrowserWaitlist.shared.registerBackgroundRefreshTaskHandler() #if NETWORK_PROTECTION VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() @@ -805,9 +804,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate { withCompletionHandler completionHandler: @escaping () -> Void) { if response.actionIdentifier == UNNotificationDefaultActionIdentifier { let identifier = response.notification.request.identifier - if identifier == WindowsBrowserWaitlist.notificationIdentifier { - presentWindowsBrowserWaitlistSettingsModal() - } #if NETWORK_PROTECTION if NetworkProtectionNotificationIdentifier(rawValue: identifier) != nil { @@ -823,11 +819,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate { completionHandler() } - - private func presentWindowsBrowserWaitlistSettingsModal() { - let waitlistViewController = WindowsWaitlistViewController(nibName: nil, bundle: nil) - presentSettings(with: waitlistViewController) - } #if NETWORK_PROTECTION private func presentNetworkProtectionWaitlistModal() { diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index b359cd0648..ecddf0d91a 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -101,28 +101,8 @@ - - - - - - - - - - - - - - - + @@ -142,7 +122,7 @@ - + @@ -162,7 +142,7 @@ - + @@ -182,7 +162,7 @@ - + @@ -202,7 +182,7 @@ - + @@ -222,7 +202,7 @@ - + @@ -242,7 +222,7 @@ - + @@ -251,7 +231,7 @@ - + @@ -260,7 +240,7 @@ - + @@ -269,7 +249,7 @@ - + @@ -278,7 +258,7 @@ - + @@ -287,7 +267,7 @@ - + @@ -631,69 +611,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -879,17 +796,17 @@ - + - + - + - + diff --git a/DuckDuckGo/DesktopDownloadPlatformConstants.swift b/DuckDuckGo/DesktopDownloadPlatformConstants.swift new file mode 100644 index 0000000000..15cfea36ae --- /dev/null +++ b/DuckDuckGo/DesktopDownloadPlatformConstants.swift @@ -0,0 +1,84 @@ +// +// DesktopDownloadPlatformConstants.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. +// + +enum DesktopDownloadPlatform { + case windows + case mac +} + +struct DesktopDownloadPlatformConstants { + let platform: DesktopDownloadPlatform + + var imageName: String { + switch platform { + case .windows: + return "WindowsWaitlistJoinWaitlist" + case .mac: + return "WaitlistMacComputer" + } + } + var title: String { + switch platform { + case .windows: + return UserText.windowsWaitlistTryDuckDuckGoForWindowsDownload + case .mac: + return UserText.macWaitlistTryDuckDuckGoForMac + } + } + var summary: String { + switch platform { + case .windows: + return UserText.windowsWaitlistSummary + case .mac: + return UserText.macWaitlistSummary + } + } + var onYourString: String { + switch platform { + case .windows: + return UserText.windowsWaitlistOnYourComputerGoTo + case .mac: + return UserText.macWaitlistOnYourMacGoTo + } + } + var downloadURL: String { + switch platform { + case .windows: + return "duckduckgo.com/windows" + case .mac: + return "duckduckgo.com/mac" + } + } + var otherPlatformText: String { + switch platform { + case .windows: + return UserText.windowsWaitlistMac + case .mac: + return UserText.macWaitlistWindows + } + } + var viewTitle: String { + switch platform { + case .windows: + return UserText.windowsWaitlistTitle + case .mac: + return UserText.macBrowserTitle + } + } +} diff --git a/DuckDuckGo/WaitlistViews.swift b/DuckDuckGo/DesktopDownloadView.swift similarity index 54% rename from DuckDuckGo/WaitlistViews.swift rename to DuckDuckGo/DesktopDownloadView.swift index 0ced5a5e9f..f089c1b21b 100644 --- a/DuckDuckGo/WaitlistViews.swift +++ b/DuckDuckGo/DesktopDownloadView.swift @@ -1,8 +1,8 @@ // -// WaitlistViews.swift +// DesktopDownloadView.swift // DuckDuckGo // -// Copyright © 2023 DuckDuckGo. All rights reserved. +// 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. @@ -17,20 +17,14 @@ // limitations under the License. // +import Foundation import SwiftUI -import Waitlist -struct WaitlistDownloadBrowserContentView: View { - - let action: WaitlistViewActionHandler - let constants: BrowserDownloadLinkConstants - - init(platform: BrowserDownloadLink, action: @escaping WaitlistViewActionHandler) { - self.action = action - self.constants = BrowserDownloadLinkConstants(platform: platform) - } +struct DesktopDownloadView: View { + @StateObject var viewModel: DesktopDownloadViewModel @State private var shareButtonFrame: CGRect = .zero + @State private var isShareSheetVisible = false let padding = UIDevice.current.localizedModel == "iPad" ? 100.0 : 0.0 @@ -38,41 +32,38 @@ struct WaitlistDownloadBrowserContentView: View { GeometryReader { proxy in ScrollView { VStack(alignment: .center, spacing: 8) { - HeaderView(imageName: constants.imageName, title: constants.title) + headerView - Text(constants.summary) + Text(viewModel.browserDetails.summary) .daxBodyRegular() .foregroundColor(.waitlistTextSecondary) .multilineTextAlignment(.center) .lineSpacing(6) .padding(.horizontal, padding) - Text(constants.onYourString) + Text(viewModel.browserDetails.onYourString) .daxBodyRegular() .foregroundColor(.waitlistTextSecondary) .multilineTextAlignment(.center) .lineSpacing(6) .padding(.top, 18) - - Text(constants.downloadURL) + + menuView .daxHeadline() .foregroundColor(.waitlistBlue) - .menuController(UserText.macWaitlistCopy) { - action(.copyDownloadURLToPasteboard) - } .fixedSize() - + Button( action: { - action(.openShareSheet(shareButtonFrame)) + self.isShareSheetVisible = true }, label: { HStack { Image("Share-16") - Text(UserText.macWaitlistShareLink) + Text(viewModel.browserDetails.downloadURL) } } ) - .buttonStyle(RoundedButtonStyle(enabled: true)) + .buttonStyle(DesktopDownloadViewButtonStyle(enabled: true)) .padding(.horizontal, padding) .padding(.top, 24) .background( @@ -86,14 +77,19 @@ struct WaitlistDownloadBrowserContentView: View { self.shareButtonFrame = newFrame } } + .sheet(isPresented: $isShareSheetVisible) { + DesktopDownloadShareSheet(items: [viewModel.downloadURL]) + } Spacer(minLength: 24) Button( action: { - action(.custom(constants.customAction)) + withAnimation { + viewModel.switchPlatform() + } }, label: { - Text(constants.otherPlatformText) + Text(viewModel.browserDetails.otherPlatformText) .daxHeadline() .foregroundColor(.waitlistBlue) .multilineTextAlignment(.center) @@ -106,6 +102,42 @@ struct WaitlistDownloadBrowserContentView: View { .padding([.leading, .trailing], 24) .frame(minHeight: proxy.size.height) } + .navigationTitle(viewModel.browserDetails.viewTitle) + } + } + + @ViewBuilder + private var headerView: some View { + VStack(spacing: 18) { + Image(viewModel.browserDetails.imageName) + + Text(viewModel.browserDetails.title) + .daxTitle2() + .foregroundColor(.waitlistTextPrimary) + .lineSpacing(6) + .multilineTextAlignment(.center) + .fixMultilineScrollableText() + } + .padding(.top, 24) + .padding(.bottom, 12) + } + + @ViewBuilder + private var menuView: some View { + + // The .menuController modifier prevents the Text view from + // updating when viewModel.browserDetails.downloadURL changes + // so this is a hack to render another view + if viewModel.browserDetails.platform == .mac { + Text(viewModel.browserDetails.downloadURL) + .menuController(UserText.macWaitlistCopy) { + viewModel.copyLink() + } + } else { + Text(viewModel.browserDetails.downloadURL) + .menuController(UserText.macWaitlistCopy) { + viewModel.copyLink() + } } } } @@ -115,68 +147,12 @@ private struct ShareButtonFramePreferenceKey: PreferenceKey { static func reduce(value: inout CGRect, nextValue: () -> CGRect) {} } -enum BrowserDownloadLink { - case windows - case mac -} - -struct BrowserDownloadLinkConstants { - let platform: BrowserDownloadLink +struct DesktopDownloadShareSheet: UIViewControllerRepresentable { + var items: [Any] - var imageName: String { - switch platform { - case .windows: - return "WindowsWaitlistJoinWaitlist" - case .mac: - return "WaitlistMacComputer" - } - } - var title: String { - switch platform { - case .windows: - return UserText.windowsWaitlistTryDuckDuckGoForWindowsDownload - case .mac: - return UserText.macWaitlistTryDuckDuckGoForMac - } - } - var summary: String { - switch platform { - case .windows: - return UserText.windowsWaitlistSummary - case .mac: - return UserText.macWaitlistSummary - } - } - var onYourString: String { - switch platform { - case .windows: - return UserText.windowsWaitlistOnYourComputerGoTo - case .mac: - return UserText.macWaitlistOnYourMacGoTo - } - } - var downloadURL: String { - switch platform { - case .windows: - return "duckduckgo.com/windows" - case .mac: - return "duckduckgo.com/mac" - } - } - var customAction: WaitlistViewModel.ViewCustomAction { - switch platform { - case .windows: - return .openMacBrowserWaitlist - case .mac: - return .openWindowsBrowserWaitlist - } - } - var otherPlatformText: String { - switch platform { - case .windows: - return UserText.windowsWaitlistMac - case .mac: - return UserText.macWaitlistWindows - } + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: items, applicationActivities: nil) } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} } diff --git a/DuckDuckGo/DesktopDownloadViewButtonStyle.swift b/DuckDuckGo/DesktopDownloadViewButtonStyle.swift new file mode 100644 index 0000000000..42a50ad708 --- /dev/null +++ b/DuckDuckGo/DesktopDownloadViewButtonStyle.swift @@ -0,0 +1,67 @@ +// +// DesktopDownloadViewButtonStyle.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 SwiftUI + +public struct DesktopDownloadViewButtonStyle: ButtonStyle { + + public enum Style { + case solid + case bordered + } + + public let enabled: Bool + private let style: Style + + public init(enabled: Bool, style: Style = .solid) { + self.enabled = enabled + self.style = style + } + + public func makeBody(configuration: Self.Configuration) -> some View { + let backgroundColor: Color + let foregroundColor: Color + let borderColor: Color + let borderWidth: CGFloat + + switch style { + case .solid: + backgroundColor = enabled ? Color.waitlistBlue : Color.waitlistBlue.opacity(0.2) + foregroundColor = Color.waitlistButtonText + borderColor = Color.clear + borderWidth = 0 + case .bordered: + backgroundColor = Color.clear + foregroundColor = Color.waitlistBlue + borderColor = Color.waitlistBlue + borderWidth = 2 + } + + return configuration.label + .daxHeadline() + .frame(maxWidth: .infinity) + .padding([.top, .bottom], 16) + .background(backgroundColor) + .foregroundColor(foregroundColor) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(borderColor, lineWidth: borderWidth)) + } + +} diff --git a/DuckDuckGo/DesktopDownloadViewModel.swift b/DuckDuckGo/DesktopDownloadViewModel.swift new file mode 100644 index 0000000000..6d52883345 --- /dev/null +++ b/DuckDuckGo/DesktopDownloadViewModel.swift @@ -0,0 +1,50 @@ +// +// DesktopDownloadViewModel.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 UIKit + +final class DesktopDownloadViewModel: ObservableObject { + + static let defaultURL = URL(string: "https://duckduckgo.com/")! + static let prefix = "https://" + + private var platform: DesktopDownloadPlatform + @Published var browserDetails: DesktopDownloadPlatformConstants + + var downloadURL: URL { + guard let url = URL(string: "\(Self.prefix)\(browserDetails.downloadURL)") else { return Self.defaultURL } + return url + } + + init(platform: DesktopDownloadPlatform) { + self.platform = platform + self.browserDetails = .init(platform: platform) + } + + func copyLink() { + UIPasteboard.general.url = downloadURL + } + + func switchPlatform() { + self.platform = (platform == .mac) ? .windows : .mac + self.browserDetails = .init(platform: platform) + } + +} diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 94ad683aca..b61e407988 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -5,7 +5,6 @@ BGTaskSchedulerPermittedIdentifiers com.duckduckgo.app.vpnWaitlistStatus - com.duckduckgo.app.windowsBrowserWaitlistStatus com.duckduckgo.app.configurationRefresh com.duckduckgo.app.remoteMessageRefresh diff --git a/DuckDuckGo/MacBrowserWaitlist.swift b/DuckDuckGo/MacBrowserWaitlist.swift deleted file mode 100644 index 0779de05b9..0000000000 --- a/DuckDuckGo/MacBrowserWaitlist.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// MacBrowserWaitlist.swift -// DuckDuckGo -// -// Copyright © 2022 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 Core -import Waitlist - -struct MacBrowserWaitlist: Waitlist { - - let isAvailable: Bool = true - - static let identifier: String = "mac" - let isWaitlistRemoved: Bool = true - static let apiProductName: String = "macosbrowser" - static let downloadURL: URL = URL.mac - - static let shared: MacBrowserWaitlist = .init() - - static let backgroundTaskName = "Mac Browser Waitlist Status Task" - static let backgroundRefreshTaskIdentifier = "com.duckduckgo.app.macBrowserWaitlistStatus" - static let notificationIdentifier = "com.duckduckgo.ios.mac-browser.invite-code-available" - static let inviteAvailableNotificationTitle = UserText.macWaitlistAvailableNotificationTitle - static let inviteAvailableNotificationBody = UserText.waitlistAvailableNotificationBody - - let settingsSubtitle: String = UserText.macWaitlistBrowsePrivately - - let waitlistStorage: WaitlistStorage - let waitlistRequest: WaitlistRequest - - init(store: WaitlistStorage, request: WaitlistRequest) { - self.waitlistStorage = store - self.waitlistRequest = request - } -} - -extension WaitlistViewModel.ViewCustomAction { - static var openWindowsBrowserWaitlist = WaitlistViewModel.ViewCustomAction(identifier: "openWindowsBrowserWaitlist") -} diff --git a/DuckDuckGo/MacBrowserWaitlistView.swift b/DuckDuckGo/MacBrowserWaitlistView.swift deleted file mode 100644 index 5ef9077f28..0000000000 --- a/DuckDuckGo/MacBrowserWaitlistView.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// MacBrowserWaitlistView.swift -// DuckDuckGo -// -// Copyright © 2022 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 -import Waitlist -import DesignResourcesKit - -struct MacBrowserWaitlistView: View { - - @EnvironmentObject var viewModel: WaitlistViewModel - - var body: some View { - WaitlistDownloadBrowserContentView(platform: .mac) { action in - Task { await viewModel.perform(action: action) } - } - } - -} - -// MARK: - Previews - -private struct MacBrowserWaitlistView_Previews: PreviewProvider { - - static var previews: some View { - Group { - PreviewView("Mac Browser Beta") { - WaitlistDownloadBrowserContentView(platform: .mac) { _ in } - } - - if #available(iOS 15.0, *) { - WaitlistDownloadBrowserContentView(platform: .mac) { _ in } - .previewInterfaceOrientation(.landscapeLeft) - } - } - } - - private struct PreviewView: View { - let title: String - var content: () -> Content - - init(_ title: String, @ViewBuilder content: @escaping () -> Content) { - self.title = title - self.content = content - } - - var body: some View { - NavigationView { - content() - .navigationTitle("DuckDuckGo Desktop App") - .navigationBarTitleDisplayMode(.inline) - .overlay(Divider(), alignment: .top) - } - .previewDisplayName(title) - } - } -} diff --git a/DuckDuckGo/MacWaitlistViewController.swift b/DuckDuckGo/MacWaitlistViewController.swift deleted file mode 100644 index ee6f58d99b..0000000000 --- a/DuckDuckGo/MacWaitlistViewController.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// MacWaitlistViewController.swift -// DuckDuckGo -// -// Copyright © 2022 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 -import SwiftUI -import LinkPresentation -import Core -import Waitlist -import DesignResourcesKit - -final class MacWaitlistViewController: UIViewController { - - private let viewModel: WaitlistViewModel - - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - self.viewModel = WaitlistViewModel(waitlist: MacBrowserWaitlist.shared) - super.init(nibName: nil, bundle: nil) - self.viewModel.delegate = self - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = UserText.macBrowserTitle - addHostingControllerToViewHierarchy() - } - - private func addHostingControllerToViewHierarchy() { - let waitlistView = MacBrowserWaitlistView().environmentObject(viewModel) - let waitlistViewController = UIHostingController(rootView: waitlistView) - waitlistViewController.view.backgroundColor = UIColor(designSystemColor: .background) - - addChild(waitlistViewController) - waitlistViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(waitlistViewController.view) - waitlistViewController.didMove(toParent: self) - - NSLayoutConstraint.activate([ - waitlistViewController.view.widthAnchor.constraint(equalTo: view.widthAnchor), - waitlistViewController.view.heightAnchor.constraint(equalTo: view.heightAnchor), - waitlistViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - waitlistViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) - ]) - } - -} - -extension MacWaitlistViewController: WaitlistViewModelDelegate { - func waitlistViewModelDidAskToReceiveJoinedNotification(_ viewModel: WaitlistViewModel) async -> Bool { - assertionFailure("Mac Waitlist is removed") - return true - } - - func waitlistViewModelDidJoinQueueWithNotificationsAllowed(_ viewModel: WaitlistViewModel) { - assertionFailure("Mac Waitlist is removed") - } - - func waitlistViewModelDidOpenInviteCodeShareSheet(_ viewModel: WaitlistViewModel, inviteCode: String, senderFrame: CGRect) { - assertionFailure("Mac Waitlist is removed") - } - - func waitlistViewModelDidOpenDownloadURLShareSheet(_ viewModel: WaitlistViewModel, senderFrame: CGRect) { - let linkMetadata = MacWaitlistLinkMetadata() - let activityViewController = UIActivityViewController(activityItems: [linkMetadata], applicationActivities: nil) - - if UIDevice.current.userInterfaceIdiom == .pad { - activityViewController.popoverPresentationController?.sourceView = UIApplication.shared.windows.first - activityViewController.popoverPresentationController?.permittedArrowDirections = .right - activityViewController.popoverPresentationController?.sourceRect = senderFrame - } - - present(activityViewController, animated: true, completion: nil) - } - - func waitlistViewModel(_ viewModel: WaitlistViewModel, didTriggerCustomAction action: WaitlistViewModel.ViewCustomAction) { - if action == .openWindowsBrowserWaitlist { - let windowsWaitlistViewController = WindowsWaitlistViewController(nibName: nil, bundle: nil) - navigationController?.popToRootViewController(animated: true) - navigationController?.pushViewController(windowsWaitlistViewController, animated: true) - } - } -} - -private final class MacWaitlistLinkMetadata: NSObject, UIActivityItemSource { - - fileprivate let metadata: LPLinkMetadata = { - let metadata = LPLinkMetadata() - metadata.originalURL = MacBrowserWaitlist.downloadURL - metadata.url = MacBrowserWaitlist.downloadURL - metadata.title = UserText.macWaitlistShareSheetTitle - metadata.imageProvider = NSItemProvider(object: UIImage(named: "WaitlistShareSheetLogo")!) - - return metadata - }() - - func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? { - return self.metadata - } - - public func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any { - return self.metadata.originalURL as Any - } - - public func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - guard let type = activityType else { - return self.metadata.originalURL as Any - } - - switch type { - case .message, .mail: return UserText.macWaitlistShareSheetMessage - default: return self.metadata.originalURL as Any - } - } - -} diff --git a/DuckDuckGo/SettingsDebugView.swift b/DuckDuckGo/SettingsDebugView.swift index 8baacecbf5..582a33c319 100644 --- a/DuckDuckGo/SettingsDebugView.swift +++ b/DuckDuckGo/SettingsDebugView.swift @@ -21,7 +21,7 @@ import SwiftUI import UIKit struct SettingsDebugView: View { - + @EnvironmentObject var viewModel: SettingsViewModel var body: some View { diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index db6de8cc18..4a7ec0de32 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -37,6 +37,7 @@ struct SettingsGeneralView: View { NavigationLink(destination: WidgetEducationView()) { SettingsCellView(label: UserText.settingsAddWidget) } + } } diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 91cc4f888b..025139dc8c 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -55,8 +55,6 @@ class SettingsLegacyViewProvider: ObservableObject { fireproofSites, autoclearData, keyboard, - macApp, - windowsApp, netP, about, feedback, debug @@ -78,8 +76,6 @@ class SettingsLegacyViewProvider: ObservableObject { var autoclearData: UIViewController { instantiate("AutoClearSettingsViewController", fromStoryboard: "Settings") } var keyboard: UIViewController { instantiate("Keyboard", fromStoryboard: "Settings") } var feedback: UIViewController { instantiate("Feedback", fromStoryboard: "Feedback") } - var mac: UIViewController { MacWaitlistViewController(nibName: nil, bundle: nil) } - var windows: UIViewController { WindowsWaitlistViewController(nibName: nil, bundle: nil) } var about: UIViewController { AboutViewController() } @available(iOS 15.0, *) diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index d3a770fa19..c0af552453 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -34,17 +34,15 @@ struct SettingsMoreView: View { disclosureIndicator: true, isButton: true) - SettingsCellView(label: UserText.macBrowserTitle, - subtitle: UserText.macWaitlistBrowsePrivately, - action: { viewModel.presentLegacyView(.macApp) }, - disclosureIndicator: true, - isButton: true) + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .mac))) { + SettingsCellView(label: UserText.macBrowserTitle, + subtitle: UserText.macWaitlistBrowsePrivately) + } - SettingsCellView(label: UserText.windowsWaitlistTitle, - subtitle: UserText.windowsWaitlistBrowsePrivately, - action: { viewModel.presentLegacyView(.windowsApp) }, - disclosureIndicator: true, - isButton: true) + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .windows))) { + SettingsCellView(label: UserText.windowsWaitlistTitle, + subtitle: UserText.windowsWaitlistBrowsePrivately) + } #if NETWORK_PROTECTION if viewModel.state.networkProtection.enabled { diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index b74e57c6b9..d03b65ce41 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -72,11 +72,13 @@ struct SettingsSubscriptionView: View { disclosureIndicator: true, isButton: true) - /* - NavigationLink(destination: Text("Data Broker Protection"), isActive: $viewModel.shouldNavigateToDBP) { - SettingsCellView(label: UserText.settingsPProDBPTitle, subtitle: UserText.settingsPProDBPSubTitle) + + SettingsCellView(label: UserText.settingsPProDBPTitle, + subtitle: UserText.settingsPProDBPSubTitle, + action: { isShowingDBP.toggle() }, isButton: true) + .sheet(isPresented: $isShowingDBP) { + SubscriptionPIRView() } - */ SettingsCellView(label: UserText.settingsPProITRTitle, subtitle: UserText.settingsPProITRSubTitle, @@ -94,7 +96,6 @@ struct SettingsSubscriptionView: View { } var body: some View { - if viewModel.state.subscription.enabled { Section(header: Text(UserText.settingsPProSection)) { if viewModel.state.subscription.hasActiveSubscription { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index c379365941..f4f426b1fe 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -457,8 +457,6 @@ extension SettingsViewModel { case .fireproofSites: pushViewController(legacyViewProvider.fireproofSites) case .autoclearData: pushViewController(legacyViewProvider.autoclearData) case .keyboard: pushViewController(legacyViewProvider.keyboard) - case .windowsApp: pushViewController(legacyViewProvider.windows) - case .macApp: pushViewController(legacyViewProvider.mac) case .about: pushViewController(legacyViewProvider.about) case .debug: pushViewController(legacyViewProvider.debug) diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Contents.json new file mode 100644 index 0000000000..5c14d97759 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Information-Remover-320.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Information-Remover-320.pdf b/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Information-Remover-320.pdf new file mode 100644 index 0000000000..22fe60e150 Binary files /dev/null and b/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Information-Remover-320.pdf differ diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Contents.json new file mode 100644 index 0000000000..16a3bd837a --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Platform-Apple-16.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Platform-Apple-16.svg b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Platform-Apple-16.svg new file mode 100644 index 0000000000..57b70ef039 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Platform-Apple-16.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json new file mode 100644 index 0000000000..89531b8717 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Platform-Windows-16.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg new file mode 100644 index 0000000000..0f6492df8a --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg @@ -0,0 +1,3 @@ + + + diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift new file mode 100644 index 0000000000..2ce1c2674b --- /dev/null +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift @@ -0,0 +1,30 @@ +// +// SubscriptionPIRViewModel.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 Core + +#if SUBSCRIPTION +@available(iOS 15.0, *) +final class SubscriptionPIRViewModel: ObservableObject { + + var viewTitle = UserText.subscriptionTitle + +} +#endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift new file mode 100644 index 0000000000..10072d9f8a --- /dev/null +++ b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift @@ -0,0 +1,201 @@ +// +// SubscriptionPIRView.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 SUBSCRIPTION +import SwiftUI +import Foundation +import DesignResourcesKit +import DuckUI + +@available(iOS 15.0, *) +struct SubscriptionPIRView: View { + + @Environment(\.dismiss) var dismiss + @Environment(\.colorScheme) var colorScheme + @StateObject var viewModel = SubscriptionPIRViewModel() + @State private var isShowingWindowsView = false + @State private var isShowingMacView = false + + enum Constants { + static let daxLogo = "Home" + static let daxLogoSize: CGFloat = 24.0 + static let empty = "" + static let navButtonPadding: CGFloat = 20.0 + static let lightMask: [Color] = [Color.init(0xFFFFFF, alpha: 0), Color.init(0xFFFFFF, alpha: 0)] + static let lightColors = [Color.init(0xF9F1F4), Color.init(0xF1F0FF)] + static let darkMask = [Color.init(0x2F2F2F, alpha: 0), Color.init(0x2F2F2F, alpha: 1)] + static let darkColors = [Color.init(0x3C184E), Color.init(0x3F1844), Color.init(0x3B1A36)] + static let titleMaxWidth = 200.0 + static let headerPadding = 5.0 + static let generalSpacing = 20.0 + static let cornerRadius = 10.0 + static let windowsIcon = "Platform-Windows-16" + static let macOSIcon = "Platform-Apple-16" + } + + var body: some View { + NavigationView { + ZStack { + gradientBackground + ScrollView { + VStack { + header + .padding(.top, Constants.headerPadding) + baseView + .frame(maxWidth: 600) + } + } + + } + .edgesIgnoringSafeArea(.all) + } + } + + private var header: some View { + GeometryReader { geometry in + HStack { + Spacer().frame(width: geometry.size.width / 3) + HStack(alignment: .center) { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(viewModel.viewTitle).daxBodyRegular() + } + .frame(width: geometry.size.width / 3, alignment: .center) + dismissButton + .frame(width: geometry.size.width / 3, alignment: .trailing) + } + } + } + + private var gradientBackground: some View { + ZStack { + LinearGradient(colors: colorScheme == . dark ? Constants.darkColors : Constants.lightColors, + startPoint: .top, + endPoint: .bottom) + LinearGradient(colors: colorScheme == . dark ? Constants.darkMask : Constants.lightMask, + startPoint: .top, + endPoint: .bottom) + } + } + + private var baseView: some View { + VStack(alignment: .center, spacing: Constants.generalSpacing) { + Image("PersonalInformationHero") + .aspectRatio(contentMode: .fill) + .padding(.top, Constants.generalSpacing) + VStack { + Text(UserText.subscriptionPIRHeroText) + .daxTitle2() + .multilineTextAlignment(.center) + .padding(.horizontal, Constants.generalSpacing*2) + .foregroundColor(Color(designSystemColor: .textPrimary)) + .padding(.bottom, Constants.generalSpacing) + attributedDescription + .padding(.horizontal, Constants.generalSpacing) + .multilineTextAlignment(.center) + .padding(.horizontal, Constants.generalSpacing) + } + Spacer() + Spacer() + VStack { + macOSButton + windowsButton + } + .padding(.bottom, Constants.generalSpacing*2) + + } + } + + private var attributedDescription: some View { + let baseStringFormat = UserText.subscriptionPIRHeroDetail + let insertString1 = UserText.subscriptionPIRHeroDesktopMenuLocation + let insertString2 = UserText.subscriptionPIRHeroDesktopMenuItem + + let highlightFont = Font(uiFont: .daxBodyBold()) + + let fullString = String(format: baseStringFormat, insertString1, insertString2) + var attributedString = AttributedString(fullString) + attributedString.font = .daxBodyRegular() + + if let range1 = attributedString.range(of: insertString1) { + attributedString[range1].font = highlightFont + } + + if let range2 = attributedString.range(of: insertString2) { + attributedString[range2].font = highlightFont} + + return Text(attributedString) + } + + @ViewBuilder + private var windowsButton: some View { + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .windows)), + isActive: $isShowingWindowsView) { + HStack { + Image(Constants.windowsIcon) + Text(UserText.subscriptionPIRWindows) + } + .frame(maxWidth: .infinity) + .padding() + .foregroundColor(Color(designSystemColor: .accent)) + .daxButton() + .overlay( + RoundedRectangle(cornerRadius: Constants.cornerRadius) + .stroke(Color(designSystemColor: .accent), lineWidth: 1) + + ) + .padding(.horizontal, Constants.generalSpacing) + } + } + + @ViewBuilder + private var macOSButton: some View { + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .mac)), + isActive: $isShowingMacView) { + HStack { + Image(Constants.macOSIcon) + Text(UserText.subscriptionPIRMacOS) + } + .frame(maxWidth: .infinity) + .padding() + .foregroundColor(Color(designSystemColor: .accent)) + .daxButton() + .overlay( + RoundedRectangle(cornerRadius: Constants.cornerRadius) + .stroke(Color(designSystemColor: .accent), lineWidth: 1) + + ) + .padding(.horizontal, Constants.generalSpacing) + } + } + + @ViewBuilder + private var dismissButton: some View { + Button(action: { dismiss() }, label: { Text(UserText.subscriptionCloseButton) }) + .padding(Constants.navButtonPadding) + .contentShape(Rectangle()) + .daxBodyRegular() + .tint(Color(designSystemColor: .textPrimary)) + } + +} + +#endif diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 9c24c77556..3578eb72cf 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -445,7 +445,6 @@ public struct UserText { public static let macWaitlistSummary = NSLocalizedString("mac-browser.waitlist.summary", value: "DuckDuckGo for Mac has the speed you need, the browsing features you expect, and comes packed with our best-in-class privacy essentials.", comment: "Summary text for the macOS browser waitlist") public static let macWaitlistTryDuckDuckGoForMac = NSLocalizedString("mac-waitlist.join-waitlist-screen.try-duckduckgo-for-mac", value: "Get DuckDuckGo for Mac!", comment: "Title for the Join Waitlist screen") public static let macWaitlistOnYourMacGoTo = NSLocalizedString("mac-waitlist.join-waitlist-screen.on-your-mac-go-to", value: "On your Mac, go to:", comment: "Description text above the Share Link button") - public static let macWaitlistWindowsComingSoon = NSLocalizedString("mac-waitlist.join-waitlist-screen.windows", value: "Windows coming soon!", comment: "Disclaimer for the Join Waitlist screen") public static let macWaitlistWindows = NSLocalizedString("mac-waitlist.join-waitlist-screen.windows-waitlist", value: "Looking for the Windows version?", comment: "Title for the macOS waitlist button redirecting to Windows waitlist") public static let macWaitlistCopy = NSLocalizedString("mac-waitlist.copy", value: "Copy", comment: "Title for the copy action") public static let macWaitlistShareLink = NSLocalizedString("mac-waitlist.join-waitlist-screen.share-link", value: "Share Link", comment: "Title for the Share Link button") @@ -456,41 +455,9 @@ public struct UserText { public static let windowsWaitlistSummary = NSLocalizedString("windows-waitlist.summary", value: "DuckDuckGo for Windows has what you need to browse with more privacy — private search, tracker blocking, forced encryption, and cookie pop-up blocking, plus more best-in-class protections on the way.", comment: "Summary text for the Windows browser waitlist") public static let windowsWaitlistOnYourComputerGoTo = NSLocalizedString("mac-waitlist.join-waitlist-screen.on-your-computer-go-to", value: "On your Windows computer, go to:", comment: "Description text above the Share Link button") public static let windowsWaitlistTryDuckDuckGoForWindowsDownload = NSLocalizedString("windows-waitlist.waitlist-download-screen.try-duckduckgo-for-windows", value: "Get DuckDuckGo for Windows!", comment: "Title for the Windows browser download link page") - public static let windowsWaitlistTryDuckDuckGoForWindows = NSLocalizedString("windows-waitlist.join-waitlist-screen.try-duckduckgo-for-windows", value: "Get early access to try DuckDuckGo for Windows!", comment: "Title for the Join Windows Waitlist screen") - public static let windowsWaitlistMac = NSLocalizedString("windows-waitlist.join-waitlist-screen.mac-waitlist", value: "Looking for the Mac version?", comment: "Title for the Windows waitlist button redirecting to Mac waitlist") + public static let windowsWaitlistMac = NSLocalizedString("windows-waitlist.join-waitlist-screen.mac-waitlist", value: "Looking for the Mac version?", comment: "Title for the Windows waitlist button redirecting to Mac waitlist") public static let windowsWaitlistBrowsePrivately = NSLocalizedString("windows-waitlist.settings.browse-privately", value: "Browse privately with our app for Windows", comment: "Title for the settings subtitle") - public static let windowsWaitlistJoinedWithNotifications = NSLocalizedString("windows-waitlist.joined.notifications-enabled", - value: "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download.", - comment: "Label text for the Joined Waitlist state with notifications enabled") - public static let windowsWaitlistJoinedWithoutNotifications = NSLocalizedString("windows-waitlist.joined.notifications-declined", - value: "Your invite to try DuckDuckGo for Windows will arrive here. Check back soon, or we can send you a notification when it’s your turn.", - comment: "Label text for the Joined Waitlist state with notifications declined") - public static let windowsWaitlistNotifyMeConfirmationMessage = NSLocalizedString("windows-waitlist.joined.no-notification.get-notification-confirmation-message", value: "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download. ", comment: "Message for the alert to confirm enabling notifications") - public static let windowsWaitlistInviteScreenSubtitle = NSLocalizedString("windows-waitlist.invite-screen.subtitle", value: "Ready to use DuckDuckGo on Windows?", comment: "Subtitle for the Windows Waitlist Invite screen") - public static let windowsWaitlistInviteScreenStep1Description = NSLocalizedString("windows-waitlist.invite-screen.step-1.description", value: "Visit this URL on your Windows device to download:", comment: "Description on the invite screen") - public static let windowsWaitlistInviteScreenStep2Description = NSLocalizedString("windows-waitlist.invite-screen.step-2.description", value: "Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code.", comment: "Description on the invite screen") - public static let windowsWaitlistAvailableNotificationTitle = NSLocalizedString("windows-waitlist.available.notification.title", value: "Try DuckDuckGo for Windows!", comment: "Title for the Windows waitlist notification") - public static func windowsWaitlistShareSheetMessage(code: String) -> String { - let localized = NSLocalizedString("windows-waitlist.share-sheet.invite-code-message", value: """ - You’re invited! - - Ready to use DuckDuckGo on Windows? - - Step 1 - Visit this URL on your Windows device to download: - https://duckduckgo.com/windows - - Step 2 - Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code. - - Invite code: %@ - """, comment: "Message used when sharing to iMessage. Parameter is an eight digit invite code.") - - return localized.format(arguments: code) - } - - // MARK: App Tracking Protection public static let appTPOnboardingTitle1 = NSLocalizedString("appTP.onboarding.title1", value: "One easy step for better app privacy!", comment: "Title for first AppTP onboarding page") @@ -1099,5 +1066,13 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionRestoreSuccessfulTitle = NSLocalizedString("subscription.restore.success.alert.title", value: "You’re all set.", comment: "Alert title for restored purchase") public static let subscriptionRestoreSuccessfulMessage = NSLocalizedString("subscription.restore.success.alert.message", value: "Your purchases have been restored.", comment: "Alert message for restored purchase") public static let subscriptionRestoreSuccessfulButton = NSLocalizedString("subscription.restore.success.alert.button", value: "OK", comment: "Alert button text for restored purchase alert") - + + // PIR: + public static let subscriptionPIRHeroText = NSLocalizedString("subscription.pir.hero", value: "Activate Privacy Pro on desktop to set up Personal Information Removal", comment: "Hero Text for Personal information removal") + public static let subscriptionPIRHeroDetail = NSLocalizedString("subscription.pir.heroText", value: "In the DuckDuckGo browser for desktop, go to %@ and click %@ to get started.", comment: "Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. ") + public static let subscriptionPIRHeroDesktopMenuLocation = NSLocalizedString("subscription.pir.heroTextLocation", value: "Settings > Privacy Pro", comment: "Settings references a menu in the Desktop app, Privacy Pro, references our product name") + public static let subscriptionPIRHeroDesktopMenuItem = NSLocalizedString("subscription.pir.heroTextMenyEntry", value: "I have a subscription", comment: "Menu item for enabling Personal Information Removal on Desktop") + public static let subscriptionPIRWindows = NSLocalizedString("subscription.pir.windows", value: "Windows", comment: "Text for the 'Windows' button") + public static let subscriptionPIRMacOS = NSLocalizedString("subscription.pir.macos", value: "macOS", comment: "Text for the 'macOS' button") + } diff --git a/DuckDuckGo/WindowsBrowserWaitlist.swift b/DuckDuckGo/WindowsBrowserWaitlist.swift deleted file mode 100644 index ad628591e1..0000000000 --- a/DuckDuckGo/WindowsBrowserWaitlist.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// WindowsBrowserWaitlist.swift -// DuckDuckGo -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import BrowserServicesKit -import Combine -import Core -import Waitlist - -final class WindowsBrowserWaitlist: Waitlist { - static let identifier: String = "windows" - static let apiProductName: String = "windowsbrowser" - static let downloadURL: URL = URL.windows - - static let shared: WindowsBrowserWaitlist = .init() - - static let backgroundTaskName = "Windows Browser Waitlist Status Task" - static let backgroundRefreshTaskIdentifier = "com.duckduckgo.app.windowsBrowserWaitlistStatus" - static let notificationIdentifier = "com.duckduckgo.ios.windows-browser.invite-code-available" - static let inviteAvailableNotificationTitle = UserText.windowsWaitlistAvailableNotificationTitle - static let inviteAvailableNotificationBody = UserText.waitlistAvailableNotificationBody - - var isAvailable: Bool { - isFeatureEnabled - } - - var isWaitlistRemoved: Bool = false - let waitlistStorage: WaitlistStorage - let waitlistRequest: WaitlistRequest - - init(store: WaitlistStorage, request: WaitlistRequest, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager) { - self.waitlistStorage = store - self.waitlistRequest = request - - isFeatureEnabled = privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .windowsWaitlist) - isWaitlistRemoved = privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .windowsDownloadLink) - - isFeatureEnabledCancellable = privacyConfigurationManager.updatesPublisher - .map { [weak privacyConfigurationManager] in - privacyConfigurationManager?.privacyConfig.isEnabled(featureKey: .windowsWaitlist) == true - } - .receive(on: DispatchQueue.main) - .assign(to: \.isFeatureEnabled, onWeaklyHeld: self) - - isWaitlistRemovedCancellable = privacyConfigurationManager.updatesPublisher - .map { [weak privacyConfigurationManager] in - privacyConfigurationManager?.privacyConfig.isEnabled(featureKey: .windowsDownloadLink) == true - } - .receive(on: DispatchQueue.main) - .assign(to: \.isWaitlistRemoved, onWeaklyHeld: self) - } - - convenience init(store: WaitlistStorage, request: WaitlistRequest) { - self.init(store: store, request: request, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager) - } - - var settingsSubtitle: String { - if isWaitlistRemoved { - return UserText.windowsWaitlistBrowsePrivately - } - - if waitlistStorage.isInvited { - return UserText.waitlistDownloadAvailable - } - - if waitlistStorage.isOnWaitlist { - return UserText.waitlistOnTheList - } - - return UserText.windowsWaitlistBrowsePrivately - } - - // MARK: - - - private var isFeatureEnabled: Bool = false - private var modeCancellable: AnyCancellable? - private var isFeatureEnabledCancellable: AnyCancellable? - private var isWaitlistRemovedCancellable: AnyCancellable? -} - -extension WaitlistViewModel.ViewCustomAction { - static var openMacBrowserWaitlist = WaitlistViewModel.ViewCustomAction(identifier: "openMacBrowserWaitlist") -} diff --git a/DuckDuckGo/WindowsBrowserWaitlistDebugViewController.swift b/DuckDuckGo/WindowsBrowserWaitlistDebugViewController.swift deleted file mode 100644 index ce1732786e..0000000000 --- a/DuckDuckGo/WindowsBrowserWaitlistDebugViewController.swift +++ /dev/null @@ -1,190 +0,0 @@ -// -// WindowsBrowserWaitlistDebugViewController.swift -// DuckDuckGo -// -// Copyright © 2022 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 -import Core -import BackgroundTasks -import Waitlist - -final class WindowsBrowserWaitlistDebugViewController: UITableViewController { - - enum Sections: Int, CaseIterable { - - case waitlistInformation - case debuggingActions - - } - - private let waitlistInformationTitles = [ - WaitlistInformationRows.waitlistTimestamp: "Timestamp", - WaitlistInformationRows.waitlistToken: "Token", - WaitlistInformationRows.waitlistInviteCode: "Invite Code", - WaitlistInformationRows.backgroundTask: "Earliest Refresh Date" - ] - - enum WaitlistInformationRows: Int, CaseIterable { - - case waitlistTimestamp - case waitlistToken - case waitlistInviteCode - case backgroundTask - - } - - private let debuggingActionTitles = [ - DebuggingActionRows.scheduleWaitlistNotification: "Fire Waitlist Notification in 3 seconds", - DebuggingActionRows.setMockInviteCode: "Set Mock Invite Code", - DebuggingActionRows.deleteInviteCode: "Delete Invite Code" - ] - - enum DebuggingActionRows: Int, CaseIterable { - - case scheduleWaitlistNotification - case setMockInviteCode - case deleteInviteCode - - } - - private let storage = WaitlistKeychainStore(waitlistIdentifier: WindowsBrowserWaitlist.identifier) - - private var backgroundTaskExecutionDate: String? - - override func viewDidLoad() { - super.viewDidLoad() - - let clearDataItem = UIBarButtonItem(image: UIImage(systemName: "trash")!, - style: .done, - target: self, - action: #selector(presentClearDataPrompt(_:))) - clearDataItem.tintColor = .systemRed - navigationItem.rightBarButtonItem = clearDataItem - - BGTaskScheduler.shared.getPendingTaskRequests { tasks in - if let task = tasks.first(where: { $0.identifier == WindowsBrowserWaitlist.backgroundRefreshTaskIdentifier }) { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .medium - - self.backgroundTaskExecutionDate = formatter.string(from: task.earliestBeginDate!) - - DispatchQueue.main.async { - self.tableView.reloadData() - } - } - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return Sections.allCases.count - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch Sections(rawValue: section)! { - case .waitlistInformation: return WaitlistInformationRows.allCases.count - case .debuggingActions: return DebuggingActionRows.allCases.count - } - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let section = Sections(rawValue: indexPath.section)! - - switch section { - case .waitlistInformation: - let cell = tableView.dequeueReusableCell(withIdentifier: "DetailCell", for: indexPath) - let row = WaitlistInformationRows(rawValue: indexPath.row)! - cell.textLabel?.text = waitlistInformationTitles[row] - - switch row { - case .waitlistTimestamp: - if let timestamp = storage.getWaitlistTimestamp() { - cell.detailTextLabel?.text = String(timestamp) - } else { - cell.detailTextLabel?.text = "None" - } - - case .waitlistToken: - cell.detailTextLabel?.text = storage.getWaitlistToken() ?? "None" - - case .waitlistInviteCode: - cell.detailTextLabel?.text = storage.getWaitlistInviteCode() ?? "None" - - case .backgroundTask: - cell.detailTextLabel?.text = backgroundTaskExecutionDate ?? "None" - } - - return cell - - case .debuggingActions: - let cell = tableView.dequeueReusableCell(withIdentifier: "ActionCell", for: indexPath) - let row = DebuggingActionRows(rawValue: indexPath.row)! - cell.textLabel?.text = debuggingActionTitles[row] - - return cell - } - - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let section = Sections(rawValue: indexPath.section)! - - switch section { - case .waitlistInformation: break - case .debuggingActions: - let row = DebuggingActionRows(rawValue: indexPath.row)! - - switch row { - case .scheduleWaitlistNotification: - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { - self.storage.store(inviteCode: "ABCD1234") - WindowsBrowserWaitlist.shared.sendInviteCodeAvailableNotification() - } - case .setMockInviteCode: - storage.store(inviteCode: "ABCD1234") - case .deleteInviteCode: - storage.delete(field: .inviteCode) - tableView.reloadData() - } - } - - tableView.deselectRow(at: indexPath, animated: true) - tableView.reloadData() - } - - @objc - private func presentClearDataPrompt(_ sender: AnyObject) { - let alert = UIAlertController(title: "Clear Waitlist Data?", message: nil, preferredStyle: .actionSheet) - - if UIDevice.current.userInterfaceIdiom == .pad { - alert.popoverPresentationController?.barButtonItem = (sender as? UIBarButtonItem) - } - - alert.addAction(UIAlertAction(title: "Clear Data", style: .destructive, handler: { _ in - self.clearDataAndReload() - })) - - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - - present(alert, animated: true) - } - - private func clearDataAndReload() { - storage.deleteWaitlistState() - tableView.reloadData() - } -} diff --git a/DuckDuckGo/WindowsBrowserWaitlistView.swift b/DuckDuckGo/WindowsBrowserWaitlistView.swift deleted file mode 100644 index f6a0796f21..0000000000 --- a/DuckDuckGo/WindowsBrowserWaitlistView.swift +++ /dev/null @@ -1,348 +0,0 @@ -// -// WindowsBrowserWaitlistView.swift -// DuckDuckGo -// -// Copyright © 2023 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import SwiftUI -import Core -import Waitlist -import DesignResourcesKit - -struct WindowsBrowserWaitlistView: View { - - @EnvironmentObject var viewModel: WaitlistViewModel - - var body: some View { - switch viewModel.viewState { - case .notJoinedQueue: - WindowsBrowserWaitlistSignUpView(requestInFlight: false) { action in - Task { await viewModel.perform(action: action) } - } - case .joiningQueue: - WindowsBrowserWaitlistSignUpView(requestInFlight: true) { action in - Task { await viewModel.perform(action: action) } - } - case .joinedQueue(let state): - WindowsBrowserWaitlistJoinedWaitlistView(notificationState: state) { action in - Task { await viewModel.perform(action: action) } - } - case .invited(let inviteCode): - WindowsBrowserWaitlistInvitedView(inviteCode: inviteCode) { action in - Task { await viewModel.perform(action: action) } - } - case .waitlistRemoved: - WaitlistDownloadBrowserContentView(platform: .windows) { action in - Task { await viewModel.perform(action: action) } - } - } - } -} - -struct WindowsBrowserWaitlistSignUpView: View { - - let requestInFlight: Bool - - let action: WaitlistViewActionHandler - - var body: some View { - GeometryReader { proxy in - ScrollView { - VStack(alignment: .center, spacing: 8) { - HeaderView(imageName: "WindowsWaitlistJoinWaitlist", title: UserText.windowsWaitlistTryDuckDuckGoForWindows) - - Text(UserText.windowsWaitlistSummary) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .multilineTextAlignment(.center) - .lineSpacing(6) - - Button(UserText.waitlistJoin, action: { action(.joinQueue) }) - .buttonStyle(RoundedButtonStyle(enabled: !requestInFlight)) - .padding(.top, 24) - - if requestInFlight { - HStack { - Text(UserText.waitlistJoining) - .daxSubheadRegular() - .foregroundColor(.waitlistTextSecondary) - - ActivityIndicator(style: .medium) - } - .padding(.top, 14) - } - - Spacer(minLength: 24) - - Button( - action: { - action(.custom(.openMacBrowserWaitlist)) - }, label: { - Text(UserText.windowsWaitlistMac) - .daxHeadline() - .foregroundColor(.waitlistBlue) - .multilineTextAlignment(.center) - .lineSpacing(5) - } - ) - .padding(.bottom, 12) - .fixedSize(horizontal: false, vertical: true) - - Text(UserText.waitlistPrivacyDisclaimer) - .daxFootnoteRegular() - .foregroundColor(.waitlistTextSecondary) - .multilineTextAlignment(.center) - .lineSpacing(5) - .padding(.bottom, 12) - .fixedSize(horizontal: false, vertical: true) - } - .padding([.leading, .trailing], 24) - .frame(minHeight: proxy.size.height) - } - } - } - -} - -// MARK: - Joined Waitlist Views - -struct WindowsBrowserWaitlistJoinedWaitlistView: View { - - let notificationState: WaitlistViewModel.NotificationPermissionState - - let action: (WaitlistViewModel.ViewAction) -> Void - - var body: some View { - VStack(spacing: 16) { - HeaderView(imageName: "WaitlistJoined", title: UserText.waitlistOnTheList) - - switch notificationState { - case .notificationAllowed: - Text(UserText.windowsWaitlistJoinedWithNotifications) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - default: - Text(UserText.windowsWaitlistJoinedWithoutNotifications) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - if notificationState == .notificationsDisabled { - AllowNotificationsView(action: action) - .padding(.top, 4) - } else { - Button(UserText.waitlistNotifyMe) { - action(.requestNotificationPermission) - } - .buttonStyle(RoundedButtonStyle(enabled: true)) - .padding(.top, 32) - } - } - - Spacer() - } - .padding([.leading, .trailing], 24) - .multilineTextAlignment(.center) - } - -} - -private struct AllowNotificationsView: View { - - let action: (WaitlistViewModel.ViewAction) -> Void - - var body: some View { - - VStack(spacing: 20) { - - Text(UserText.waitlistNotificationDisabled) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .fixMultilineScrollableText() - .lineSpacing(5) - - Button(UserText.waitlistAllowNotifications) { - action(.openNotificationSettings) - } - .buttonStyle(RoundedButtonStyle(enabled: true)) - - } - .padding(24) - .background(Color.waitlistNotificationBackground) - .cornerRadius(8) - .shadow(color: .black.opacity(0.05), radius: 12, x: 0, y: 4) - - } - -} - -// MARK: - Invite Available Views - -private struct ShareButtonFramePreferenceKey: PreferenceKey { - static var defaultValue: CGRect = .zero - static func reduce(value: inout CGRect, nextValue: () -> CGRect) {} -} - -struct WindowsBrowserWaitlistInvitedView: View { - - let inviteCode: String - let action: (WaitlistViewModel.ViewAction) -> Void - - @State private var shareButtonFrame: CGRect = .zero - - var body: some View { - GeometryReader { proxy in - ScrollView { - VStack(alignment: .center, spacing: 0) { - HeaderView(imageName: "WaitlistInvited", title: UserText.waitlistYoureInvited) - - Text(UserText.windowsWaitlistInviteScreenSubtitle) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .padding(.top, 16) - .lineSpacing(6) - .fixedSize(horizontal: false, vertical: true) - - Text(UserText.waitlistInviteScreenStepTitle(step: 1)) - .daxHeadline() - .foregroundColor(.waitlistTextSecondary) - .padding(.top, 28) - .padding(.bottom, 8) - - Text(UserText.windowsWaitlistInviteScreenStep1Description) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - Text(URL.windows.absoluteString.dropping(prefix: "https://")) - .daxHeadline() - .foregroundColor(.waitlistBlue) - .menuController(UserText.waitlistCopy) { - action(.copyDownloadURLToPasteboard) - } - .scaledToFit() - - Text(UserText.waitlistInviteScreenStepTitle(step: 2)) - .daxHeadline() - .foregroundColor(.waitlistTextSecondary) - .padding(.top, 22) - .padding(.bottom, 8) - - Text(UserText.windowsWaitlistInviteScreenStep2Description) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - InviteCodeView(title: UserText.waitlistInviteCode, inviteCode: inviteCode) - .menuController(UserText.waitlistCopy) { - action(.copyInviteCodeToPasteboard) - } - .fixedSize() - .padding(.top, 28) - - Spacer(minLength: 24) - - shareButton - .padding(.bottom, 26) - - } - .frame(maxWidth: .infinity, minHeight: proxy.size.height) - .padding([.leading, .trailing], 18) - .multilineTextAlignment(.center) - } - } - } - - var shareButton: some View { - - Button(action: { - action(.openShareSheet(shareButtonFrame)) - }, label: { - Image("Share") - .foregroundColor(.waitlistTextSecondary) - }) - .frame(width: 44, height: 44) - .background( - GeometryReader { proxy in - Color.clear - .preference(key: ShareButtonFramePreferenceKey.self, value: proxy.frame(in: .global)) - } - ) - .onPreferenceChange(ShareButtonFramePreferenceKey.self) { newFrame in - if UIDevice.current.userInterfaceIdiom == .pad { - self.shareButtonFrame = newFrame - } - } - - } - -} - -// MARK: - Previews - -private struct WindowsBrowserWaitlistView_Previews: PreviewProvider { - - static var previews: some View { - Group { - PreviewView("Sign Up") { - WindowsBrowserWaitlistSignUpView(requestInFlight: false) { _ in } - } - - PreviewView("Sign Up (API Request In Progress)") { - WindowsBrowserWaitlistSignUpView(requestInFlight: true) { _ in } - } - - PreviewView("Joined Waitlist (Notifications Allowed)") { - WindowsBrowserWaitlistJoinedWaitlistView(notificationState: .notificationAllowed) { _ in } - } - - PreviewView("Joined Waitlist (Notifications Not Allowed)") { - WindowsBrowserWaitlistJoinedWaitlistView(notificationState: .notificationsDisabled) { _ in } - } - - PreviewView("Invite Screen With Code") { - WindowsBrowserWaitlistInvitedView(inviteCode: "T3STC0DE") { _ in } - } - - if #available(iOS 15.0, *) { - WindowsBrowserWaitlistInvitedView(inviteCode: "T3STC0DE") { _ in } - .previewInterfaceOrientation(.landscapeLeft) - } - } - } - - private struct PreviewView: View { - let title: String - var content: () -> Content - - init(_ title: String, @ViewBuilder content: @escaping () -> Content) { - self.title = title - self.content = content - } - - var body: some View { - NavigationView { - content() - .navigationTitle("DuckDuckGo Desktop App") - .navigationBarTitleDisplayMode(.inline) - .overlay(Divider(), alignment: .top) - } - .previewDisplayName(title) - } - } -} diff --git a/DuckDuckGo/WindowsWaitlistViewController.swift b/DuckDuckGo/WindowsWaitlistViewController.swift deleted file mode 100644 index 9ba9d561e4..0000000000 --- a/DuckDuckGo/WindowsWaitlistViewController.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// WindowsWaitlistViewController.swift -// DuckDuckGo -// -// Copyright © 2022 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 -import SwiftUI -import LinkPresentation -import Core -import Waitlist - -final class WindowsWaitlistViewController: UIViewController { - - private let viewModel: WaitlistViewModel - - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - self.viewModel = WaitlistViewModel(waitlist: WindowsBrowserWaitlist.shared) - super.init(nibName: nil, bundle: nil) - self.viewModel.delegate = self - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = UserText.windowsWaitlistTitle - - addHostingControllerToViewHierarchy() - - NotificationCenter.default.addObserver(self, - selector: #selector(updateViewState), - name: UIApplication.didBecomeActiveNotification, - object: nil) - - NotificationCenter.default.addObserver(self, - selector: #selector(updateViewState), - name: WaitlistKeychainStore.inviteCodeDidChangeNotification, - object: WindowsBrowserWaitlist.identifier) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - Task { - await self.viewModel.updateViewState() - } - } - - @objc - private func updateViewState() { - Task { - await self.viewModel.updateViewState() - } - } - - private func addHostingControllerToViewHierarchy() { - let waitlistView = WindowsBrowserWaitlistView().environmentObject(viewModel) - let waitlistViewController = UIHostingController(rootView: waitlistView) - waitlistViewController.view.backgroundColor = UIColor(designSystemColor: .background) - - addChild(waitlistViewController) - waitlistViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(waitlistViewController.view) - waitlistViewController.didMove(toParent: self) - - NSLayoutConstraint.activate([ - waitlistViewController.view.widthAnchor.constraint(equalTo: view.widthAnchor), - waitlistViewController.view.heightAnchor.constraint(equalTo: view.heightAnchor), - waitlistViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - waitlistViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) - ]) - } - -} - -extension WindowsWaitlistViewController: WaitlistViewModelDelegate { - - func waitlistViewModelDidAskToReceiveJoinedNotification(_ viewModel: WaitlistViewModel) async -> Bool { - return await withCheckedContinuation { continuation in - let alertController = UIAlertController(title: UserText.waitlistNotifyMeConfirmationTitle, - message: UserText.windowsWaitlistNotifyMeConfirmationMessage, - preferredStyle: .alert) - alertController.overrideUserInterfaceStyle() - - alertController.addAction(title: UserText.waitlistNoThanks) { - continuation.resume(returning: false) - } - let notifyMeAction = UIAlertAction(title: UserText.waitlistNotifyMe, style: .default) { _ in - continuation.resume(returning: true) - } - - alertController.addAction(notifyMeAction) - alertController.preferredAction = notifyMeAction - - present(alertController, animated: true) - } - } - - func waitlistViewModelDidJoinQueueWithNotificationsAllowed(_ viewModel: WaitlistViewModel) { - WindowsBrowserWaitlist.shared.scheduleBackgroundRefreshTask() - } - - func waitlistViewModelDidOpenInviteCodeShareSheet(_ viewModel: WaitlistViewModel, inviteCode: String, senderFrame: CGRect) { - openShareSheetWithMetadata(WindowsWaitlistLinkMetadata(pageType: .waitlist), senderFrame: senderFrame) - } - - func waitlistViewModelDidOpenDownloadURLShareSheet(_ viewModel: WaitlistViewModel, senderFrame: CGRect) { - openShareSheetWithMetadata(WindowsWaitlistLinkMetadata(pageType: .download), senderFrame: senderFrame) - } - - private func openShareSheetWithMetadata(_ linkMetadata: WindowsWaitlistLinkMetadata, senderFrame: CGRect) { - let activityViewController = UIActivityViewController(activityItems: [linkMetadata], applicationActivities: nil) - - if UIDevice.current.userInterfaceIdiom == .pad { - activityViewController.popoverPresentationController?.sourceView = UIApplication.shared.windows.first - activityViewController.popoverPresentationController?.permittedArrowDirections = .right - activityViewController.popoverPresentationController?.sourceRect = senderFrame - } - - present(activityViewController, animated: true, completion: nil) - } - - func waitlistViewModel(_ viewModel: WaitlistViewModel, didTriggerCustomAction action: WaitlistViewModel.ViewCustomAction) { - if action == .openMacBrowserWaitlist { - let macWaitlistViewController = MacWaitlistViewController(nibName: nil, bundle: nil) - navigationController?.popToRootViewController(animated: true) - navigationController?.pushViewController(macWaitlistViewController, animated: true) - } - } -} - -private final class WindowsWaitlistLinkMetadata: NSObject, UIActivityItemSource { - - fileprivate let metadata: LPLinkMetadata = { - let metadata = LPLinkMetadata() - metadata.originalURL = WindowsBrowserWaitlist.downloadURL - metadata.url = WindowsBrowserWaitlist.downloadURL - metadata.title = UserText.waitlistShareSheetTitle - metadata.imageProvider = NSItemProvider(object: UIImage(named: "WaitlistShareSheetLogo")!) - - return metadata - }() - - private let inviteCode: String? - private let pageType: WindowsWaitlistPageType - - init(inviteCode: String? = nil, pageType: WindowsWaitlistPageType) { - self.inviteCode = inviteCode - self.pageType = pageType - } - - func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? { - return self.metadata - } - - public func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any { - return self.metadata.originalURL as Any - } - - public func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - guard let type = activityType else { - return self.metadata.originalURL as Any - } - - switch type { - case .message, .mail: - return pageType == .waitlist ? - UserText.windowsWaitlistShareSheetMessage(code: inviteCode!) : - UserText.windowsWaitlistDownloadLinkShareSheetMessage - default: - return self.metadata.originalURL as Any - } - } - - enum WindowsWaitlistPageType { - case waitlist - case download - } - -} diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 69faa3d347..db79bdb37c 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1372,9 +1372,6 @@ /* Title for the Join Waitlist screen */ "mac-waitlist.join-waitlist-screen.try-duckduckgo-for-mac" = "Get DuckDuckGo for Mac!"; -/* Disclaimer for the Join Waitlist screen */ -"mac-waitlist.join-waitlist-screen.windows" = "Windows coming soon!"; - /* Title for the macOS waitlist button redirecting to Windows waitlist */ "mac-waitlist.join-waitlist-screen.windows-waitlist" = "Looking for the Windows version?"; @@ -2022,6 +2019,24 @@ But if you *do* want a peek under the hood, you can find more information about /* View plans button text */ "subscription.notFound.view.plans" = "View Plans"; +/* Hero Text for Personal information removal */ +"subscription.pir.hero" = "Activate Privacy Pro on desktop to set up Personal Information Removal"; + +/* Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. */ +"subscription.pir.heroText" = "In the DuckDuckGo browser for desktop, go to %1$@ and click %2$@ to get started."; + +/* Settings references a menu in the Desktop app, Privacy Pro, references our product name */ +"subscription.pir.heroTextLocation" = "Settings > Privacy Pro"; + +/* Menu item for enabling Personal Information Removal on Desktop */ +"subscription.pir.heroTextMenyEntry" = "I have a subscription"; + +/* Text for the 'macOS' button */ +"subscription.pir.macos" = "macOS"; + +/* Text for the 'Windows' button */ +"subscription.pir.windows" = "Windows"; + /* Progress view title when completing the purchase */ "subscription.progress.view.completing.purchase" = "Completing purchase..."; @@ -2331,50 +2346,12 @@ But if you *do* want a peek under the hood, you can find more information about /* Alert title explaining the message is shown by a website */ "webJSAlert.website-message.format" = "A message from %@:"; -/* Title for the Windows waitlist notification */ -"windows-waitlist.available.notification.title" = "Try DuckDuckGo for Windows!"; - -/* Description on the invite screen */ -"windows-waitlist.invite-screen.step-1.description" = "Visit this URL on your Windows device to download:"; - -/* Description on the invite screen */ -"windows-waitlist.invite-screen.step-2.description" = "Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code."; - -/* Subtitle for the Windows Waitlist Invite screen */ -"windows-waitlist.invite-screen.subtitle" = "Ready to use DuckDuckGo on Windows?"; - /* Title for the Windows waitlist button redirecting to Mac waitlist */ "windows-waitlist.join-waitlist-screen.mac-waitlist" = "Looking for the Mac version?"; -/* Title for the Join Windows Waitlist screen */ -"windows-waitlist.join-waitlist-screen.try-duckduckgo-for-windows" = "Get early access to try DuckDuckGo for Windows!"; - -/* Message for the alert to confirm enabling notifications */ -"windows-waitlist.joined.no-notification.get-notification-confirmation-message" = "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download. "; - -/* Label text for the Joined Waitlist state with notifications declined */ -"windows-waitlist.joined.notifications-declined" = "Your invite to try DuckDuckGo for Windows will arrive here. Check back soon, or we can send you a notification when it’s your turn."; - -/* Label text for the Joined Waitlist state with notifications enabled */ -"windows-waitlist.joined.notifications-enabled" = "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download."; - /* Title for the settings subtitle */ "windows-waitlist.settings.browse-privately" = "Browse privately with our app for Windows"; -/* Message used when sharing to iMessage. Parameter is an eight digit invite code. */ -"windows-waitlist.share-sheet.invite-code-message" = "You’re invited! - -Ready to use DuckDuckGo on Windows? - -Step 1 -Visit this URL on your Windows device to download: -https://duckduckgo.com/windows - -Step 2 -Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code. - -Invite code: %@"; - /* Message used when sharing to iMessage */ "windows-waitlist.share-sheet.message" = "Ready to start browsing privately on Windows? diff --git a/DuckDuckGoTests/WindowsBrowserWaitlistTests.swift b/DuckDuckGoTests/WindowsBrowserWaitlistTests.swift deleted file mode 100644 index b7369ffdb0..0000000000 --- a/DuckDuckGoTests/WindowsBrowserWaitlistTests.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// WindowsBrowserWaitlistTests.swift -// DuckDuckGo -// -// Copyright © 2022 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 XCTest -import WaitlistMocks -@testable import DuckDuckGo -@testable import Core -import BrowserServicesKit - -class WindowsBrowserWaitlistTests: XCTestCase { - - func testWhenUserHasNotJoinedWaitlist_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - let request = MockWaitlistRequest.failure() - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: PrivacyConfigurationManagerMock()) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.windowsWaitlistBrowsePrivately) - } - - func testWhenUserIsOnWaitlist_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - store.store(waitlistToken: "abcd") - store.store(waitlistTimestamp: 12345) - - let request = MockWaitlistRequest.failure() - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: PrivacyConfigurationManagerMock()) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.waitlistOnTheList) - } - - func testWhenUserIsInvited_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - store.store(inviteCode: "code") - - let request = MockWaitlistRequest.failure() - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: PrivacyConfigurationManagerMock()) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.waitlistDownloadAvailable) - } - - func testWhenWindowsDownloadLinkEnabled_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - store.store(inviteCode: "code") - - let request = MockWaitlistRequest.failure() - let privacyConfigurationManager: PrivacyConfigurationManagerMock = PrivacyConfigurationManagerMock() - let privacyConfig = privacyConfigurationManager.privacyConfig as! PrivacyConfigurationMock // swiftlint:disable:this force_cast - privacyConfig.enabledFeaturesForVersions[.windowsDownloadLink] = Set([AppVersionProvider().appVersion()!]) - - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: privacyConfigurationManager) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.windowsWaitlistBrowsePrivately) - } - -} diff --git a/LocalPackages/DuckUI/Sources/DuckUI/Color.swift b/LocalPackages/DuckUI/Sources/DuckUI/Color.swift index 36c2fe13ad..c735ac88e7 100644 --- a/LocalPackages/DuckUI/Sources/DuckUI/Color.swift +++ b/LocalPackages/DuckUI/Sources/DuckUI/Color.swift @@ -108,7 +108,7 @@ public extension UIColor { } -private extension Color { +public extension Color { init(_ hex: UInt, alpha: Double = 1) { self.init( .sRGB,