From 6fb037f34025f58e3a3f290fd56c6550b6d4a2fe Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Tue, 6 Aug 2024 14:25:30 +0100 Subject: [PATCH] Add UI for displaying Duck Player contingency message #3065 (#3181) Task/Issue URL: https://app.asana.com/0/1204167627774280/1207871109767442/f Tech Design URL: CC: **Description**: Add UI for displaying Duck Player contingency message --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++ .../DuckPlayerContingencyHandler.swift | 36 ++++++++++++ .../WarningYoutube.imageset/Contents.json | 12 ++++ .../WarningYoutube.pdf | Bin 0 -> 1930 bytes DuckDuckGo/SettingsDuckPlayerView.swift | 54 ++++++++++++++++++ DuckDuckGo/SettingsViewModel.swift | 14 ++++- DuckDuckGo/UserText.swift | 4 ++ DuckDuckGo/en.lproj/Localizable.strings | 9 +++ 8 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 DuckDuckGo/DuckPlayer/DuckPlayerContingencyHandler.swift create mode 100644 DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/Contents.json create mode 100644 DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/WarningYoutube.pdf diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 63c9dda682..5961fc7aa6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -118,6 +118,7 @@ 1EFDCBC127D2393C00916BC5 /* DownloadsDeleteHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFDCBC027D2393C00916BC5 /* DownloadsDeleteHelper.swift */; }; 22CB1ED8203DDD2C00D2C724 /* AppDeepLinksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22CB1ED7203DDD2C00D2C724 /* AppDeepLinksTests.swift */; }; 2DC3FC65C6D9DA634426672D /* AutofillNoAuthAvailableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DC3FBD62FBAF21E87610FA8 /* AutofillNoAuthAvailableView.swift */; }; + 31009B382C5D32D100510782 /* DuckPlayerContingencyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31009B372C5D32D100510782 /* DuckPlayerContingencyHandler.swift */; }; 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310742A52848CD780012660B /* BackForwardMenuHistoryItem.swift */; }; 310742AB2848E6FD0012660B /* BackForwardMenuHistoryItemURLSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310742A92848E5B70012660B /* BackForwardMenuHistoryItemURLSanitizerTests.swift */; }; 310C4B45281B5A9A00BA79A9 /* AutofillLoginDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310C4B44281B5A9A00BA79A9 /* AutofillLoginDetailsView.swift */; }; @@ -1332,6 +1333,7 @@ 1EFDCBC027D2393C00916BC5 /* DownloadsDeleteHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsDeleteHelper.swift; sourceTree = ""; }; 22CB1ED7203DDD2C00D2C724 /* AppDeepLinksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDeepLinksTests.swift; sourceTree = ""; }; 2DC3FBD62FBAF21E87610FA8 /* AutofillNoAuthAvailableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillNoAuthAvailableView.swift; sourceTree = ""; }; + 31009B372C5D32D100510782 /* DuckPlayerContingencyHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerContingencyHandler.swift; sourceTree = ""; }; 310742A52848CD780012660B /* BackForwardMenuHistoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackForwardMenuHistoryItem.swift; sourceTree = ""; }; 310742A92848E5B70012660B /* BackForwardMenuHistoryItemURLSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackForwardMenuHistoryItemURLSanitizerTests.swift; sourceTree = ""; }; 310C4B44281B5A9A00BA79A9 /* AutofillLoginDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillLoginDetailsView.swift; sourceTree = ""; }; @@ -4948,6 +4950,7 @@ D63FF8922C1B67D1006DE24D /* DuckPlayer */ = { isa = PBXGroup; children = ( + 31009B372C5D32D100510782 /* DuckPlayerContingencyHandler.swift */, 31DE43C82C2DAA8F00F8C51F /* Modal */, 31DE43C72C2DAA7F00F8C51F /* Resources */, D63FF8972C1B6A45006DE24D /* DuckPlayer.swift */, @@ -7018,6 +7021,7 @@ F1386BA41E6846C40062FC3C /* TabDelegate.swift in Sources */, 37CF91602BB4737300BADCAE /* CrashCollectionOnboarding.swift in Sources */, C1B924B72ACD6E6800EE7B06 /* AutofillNeverSavedTableViewCell.swift in Sources */, + 31009B382C5D32D100510782 /* DuckPlayerContingencyHandler.swift in Sources */, 3132FA2A27A0788F00DD7A12 /* QuickLookPreviewHelper.swift in Sources */, D670E5BB2BB6A75300941A42 /* SubscriptionNavigationCoordinator.swift in Sources */, 6F9FFE262C579BCD00A238BE /* NewTabPageShortcutsSettingsStorage.swift in Sources */, diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerContingencyHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerContingencyHandler.swift new file mode 100644 index 0000000000..1e42f1c4d7 --- /dev/null +++ b/DuckDuckGo/DuckPlayer/DuckPlayerContingencyHandler.swift @@ -0,0 +1,36 @@ +// +// DuckPlayerContingencyHandler.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 + +protocol DuckPlayerContingencyHandler { + var shouldDisplayContingencyMessage: Bool { get } + var learnMoreURL: URL { get } +} + +struct DefaultDuckPlayerContingencyHandler: DuckPlayerContingencyHandler { + var shouldDisplayContingencyMessage: Bool { + false + } + + var learnMoreURL: URL { +#warning("DuckPlayer - Replace this with real URL") + return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/duck-player/")! + } +} diff --git a/DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/Contents.json b/DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/Contents.json new file mode 100644 index 0000000000..01e3b61ed5 --- /dev/null +++ b/DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "WarningYoutube.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/WarningYoutube.pdf b/DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/WarningYoutube.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ef64b0d8737aa0062262048643a8d5fe1a6e7bec GIT binary patch literal 1930 zcmZXVc~sI_6vxwI#;{zXjHa=ESc+D%q=1;3BCeGSTDhf>{8UPzSVqNZ(=>C_v7q$D#;O zC>Fo~%sqRd0AOPS*x5lr22H(%Zv0k9JJTaMAPYb{M^ZT;84P98Kvjk+&|5)9I41(I zP#veJB-IUU4hy74L1Q#7^0#EH#pxv}V^f0G+_>LUQk!882!F`%66m|yt~sA&wK;?a z%H_Hm^Mg4qA794Ynd-TpdHBhy@?i{YZ7Xf-iQ9%k=@nF$XHpMRaiD}^L5@aK0P&?qw_2~BO1hq& zdC)gCQ(ke|{%o?zqaA^+))Vd|$5xGkwb5ofgjWNU0m|?a*^R;7X9TGv4l{U>W_L!! zGM&5i(H&Z8$zwm)1a*`c2(1xr&!zibbkzH2!^e&%)>S4-SLyh`iabHGIJT3+E2?#A)S9^Q zmbFLR72*C^+I^e+K(9c%hi50SY22M+v(PClQNBc8ek7Z1ct1JjZOGJ=?1E(MoGihL zH4J&T8#gMul+Cy|RYx3qmo@+I5FIo0DSk=&Q%xL2|9CL0HJ}3$K+@_bpU!o<_E%~+ z^`YqaTb@SI!YvKQ_$TW$J`)?Oa*|hQKPc-g@H&=vPGT)cGrSB#mA)r>t+ySv#|GE8 zc{D(GCRoc9C(7KmHYa+I^Ecps?1Wv!q)G&@*9nWZHuEv?L=M5nr@kv*V8cj z({Pi!Bu(4#3M#UR^eajC7r%E1A>SZr>!4#$o`<%RNw(lwb$6brfn*~zQ1{h&WMxUP zpL44b?KZ6J3BvD)Y5RJ&c}?aGP#@!+BB8xad~%%Lv9KV-;Z9QXf;?f0o{{M82^$7V zs-3X1xw5AP0!gr9piaOlF8>bT&zg9|Hf9yaki7j1ZpSIAuN**P?>Hr8h)%mbta(4# ze}*jHAF36Wj-}LlZ(6lF(#KPpULAPuoy|$v1$jDhYjHpfAe64l%qd?9YuW?6dYYqb zf#m;YmT4GFcDYYcJmelNE~!GbOOGqcLJF6sxz`8hC89B?NBa3}hk8CQ48Dh$O3@|? zu3WokU^{I7>LwO~)A?xW%}?-NZ{+)ebZS+o2+O@lmg-7&#R8T~@5Rwa4H^Ym&IuL$ z8AiPHd6~QX0~4h1OzZgOx(s`_9$Rh(5^=L+qikJA_X46YOCcW`sq7=Xu+?gp0FQch_l;50w$GC1mkfE*q(Z*s#IiCc%VP+Rzu_ z)untBbfGe@b3|NuBwBMA-V`nRtl9nfa=99tU${35P&E-}z>3|7$Ifxpa!yw97VW@b zFga`>aMu@_`o``D0kju2d={_UKpLG&V#WZ000xDbj;{+MepdRrn!SkvvYA{~D98rX zgjb_kO>0Lcg99>DFRJlAD|%5`Dh90}3$vjw6$+%EkV# ze)h~TnC%AsyPMPo*>ex73!5?SNaau?nc?53#<0LJCePev+>Gi?V;z*s?zjh#H5{{ Void + + private enum Copy { + static let title: String = UserText.duckPlayerContingencyMessageTitle + static let message: String = UserText.duckPlayerContingencyMessageBody + static let buttonTitle: String = UserText.duckPlayerContingencyMessageCTA + } + private enum Constants { + static let imageName: String = "WarningYoutube" + static let imageSize: CGSize = CGSize(width: 50, height: 50) + static let buttonCornerRadius: CGFloat = 8.0 + } + + var body: some View { + VStack(alignment: .center, spacing: 8) { + Image(Constants.imageName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.imageSize.width, height: Constants.imageSize.height) + .padding(.bottom, 8) + + Text(Copy.title) + .daxHeadline() + .foregroundColor(Color(designSystemColor: .textPrimary)) + + Text(Copy.message) + .daxBodyRegular() + .multilineTextAlignment(.center) + .lineLimit(nil) + .foregroundColor(Color(designSystemColor: .textPrimary)) + + Button { + buttonCallback() + } label: { + Text(Copy.buttonTitle) + .foregroundColor(Color(designSystemColor: .textPrimary)) + .bold() + } + .buttonStyle(SecondaryFillButtonStyle(fullWidth: false)) + .padding(10) + } + } +} diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index cbbfcc6983..36cec12c58 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -42,11 +42,11 @@ final class SettingsViewModel: ObservableObject { var emailManager: EmailManager { EmailManager() } private let historyManager: HistoryManaging let privacyProDataReporter: PrivacyProDataReporting? - // Subscription Dependencies private let subscriptionManager: SubscriptionManager private var subscriptionSignOutObserver: Any? - + var duckPlayerContingencyHandler: DuckPlayerContingencyHandler { DefaultDuckPlayerContingencyHandler() } + private enum UserDefaultsCacheKey: String, UserDefaultsCacheKeyStore { case subscriptionState = "com.duckduckgo.ios.subscription.state" } @@ -539,6 +539,16 @@ extension SettingsViewModel { completionHandler: nil) } + func shouldDisplayDuckPlayerContingencyMessage() -> Bool { + duckPlayerContingencyHandler.shouldDisplayContingencyMessage + } + + func openDuckPlayerContingencyMessageSite() { + UIApplication.shared.open(duckPlayerContingencyHandler.learnMoreURL, + options: [:], + completionHandler: nil) + } + @MainActor func openCookiePopupManagement() { pushViewController(legacyViewProvider.autoConsent) } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index ff21c9e1d3..21d50699ba 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1187,6 +1187,10 @@ But if you *do* want a peek under the hood, you can find more information about public static let duckPlayerPresentationModalBody = NSLocalizedString("duckplayer.presentation.modal.body", value: "Duck Player lets you watch YouTube without targeted ads in a theater-like experience in DuckDuckGo and what you watch won’t influence your recommendations.", comment: "Body text for the modal feature explanation") public static let duckPlayerPresentationModalDismissButton = NSLocalizedString("duckplayer.presentation.modal.dismiss-button", value: "Got it!", comment: "Button that will dismiss the modal") + static let duckPlayerContingencyMessageTitle = NSLocalizedString("duck-player.contingency-title", value: "Duck Player Unavailable", comment: "Title for message explaining to the user that Duck Player is not available") + static let duckPlayerContingencyMessageBody = NSLocalizedString("duck-player.video-contingency-message", value: "Duck Player's functionality has been affected by recent changes to YouTube. We’re working to fix these issues and appreciate your understanding.", comment: "Message explaining to the user that Duck Player is not available") + static let duckPlayerContingencyMessageCTA = NSLocalizedString("duck-player.video-contingency-cta", value: "Learn More", comment: "Button for the message explaining to the user that Duck Player is not available so the user can learn more") + // MARK: - New Tab Page // MARK: Shortcuts diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index a78fb7d714..b2fb25888e 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -958,6 +958,15 @@ /* Message confirming that the download process has started. Parameter is downloaded file's filename */ "downloads.message.download-started" = "Download started for %@"; +/* Title for message explaining to the user that Duck Player is not available */ +"duck-player.contingency-title" = "Duck Player Unavailable"; + +/* Button for the message explaining to the user that Duck Player is not available so the user can learn more */ +"duck-player.video-contingency-cta" = "Learn More"; + +/* Message explaining to the user that Duck Player is not available */ +"duck-player.video-contingency-message" = "Duck Player's functionality has been affected by recent changes to YouTube. We’re working to fix these issues and appreciate your understanding."; + /* Text displayed when DuckPlayer is always enabled */ "duckPlayer.alwaysEnabled.label" = "Always";