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 0000000000..ef64b0d873 Binary files /dev/null and b/DuckDuckGo/DuckPlayer/Resources/DuckPlayer.xcassets/WarningYoutube.imageset/WarningYoutube.pdf differ diff --git a/DuckDuckGo/SettingsDuckPlayerView.swift b/DuckDuckGo/SettingsDuckPlayerView.swift index 3280da7f7e..be6e684611 100644 --- a/DuckDuckGo/SettingsDuckPlayerView.swift +++ b/DuckDuckGo/SettingsDuckPlayerView.swift @@ -20,6 +20,7 @@ import Core import SwiftUI import DesignResourcesKit +import DuckUI struct SettingsDuckPlayerView: View { private static let learnMoreURL = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/duck-player/")! @@ -27,6 +28,14 @@ struct SettingsDuckPlayerView: View { @EnvironmentObject var viewModel: SettingsViewModel var body: some View { List { + if viewModel.shouldDisplayDuckPlayerContingencyMessage() { + Section { + ContingencyMessageView { + viewModel.openDuckPlayerContingencyMessageSite() + } + } + } + VStack(alignment: .center) { Image("SettingsDuckPlayerHero") .padding(.top, -20) // Adjust for the image padding @@ -62,3 +71,48 @@ struct SettingsDuckPlayerView: View { viewModel: viewModel) } } + +private struct ContingencyMessageView: View { + let buttonCallback: () -> 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";