From 432d14bd954862d42ebe8ee8db19b40432089fa0 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Mon, 2 Sep 2024 21:19:26 +0500 Subject: [PATCH 1/4] Fix bookmarks bar issues (#3187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1201048563534612/1208204239267609/f https://app.asana.com/0/1201048563534612/1208160187803231/f Tech Design URL: CC: **Description**: - Fix "New Folder" button not working in Bookmarks Popover - Fix bookmarks popover not closed when dragging a bookmark out of it - Fix Esc key not closing "New Bookmark" dialog - Fix couple warnings **Steps to test this PR**: 1. Validate New Folder button works in the Bookmarks Popover 2. Drag a bookmark from the Bookmarks Popover over a Bookmarks Bar folder, validate the popover is closed 3. Click "Add new bookmark", hit Esc key, validate the dialog is closed 4. Add a bookmark, click the "bookmark" button in the address bar, validate Esc key doesn‘t delete the bookmark **Definition of Done**: * [ ] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift | 2 +- DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift | 1 + DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift | 7 +++---- .../Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift | 1 + DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift | 2 ++ DuckDuckGo/BookmarksBar/View/BookmarksBarMenuPopover.swift | 4 ++-- DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift | 2 +- DuckDuckGo/Tab/View/BrowserTabViewController.swift | 2 +- 8 files changed, 12 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift b/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift index 24e6b3ef28..18902d8122 100644 --- a/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift +++ b/DuckDuckGo/Bookmarks/Services/BookmarksContextMenu.swift @@ -143,7 +143,7 @@ extension BookmarksContextMenu { static func menuItems(for folder: BookmarkFolder, target: AnyObject?, forSearch: Bool, includeManageBookmarksItem: Bool) -> [NSMenuItem] { // disable "Open All" if no Bookmarks in folder - var hasBookmarks = folder.children.contains(where: { $0 is Bookmark }) + let hasBookmarks = folder.children.contains(where: { $0 is Bookmark }) var items = [ openInNewTabsMenuItem(folder: folder, target: target, enabled: hasBookmarks), openAllInNewWindowMenuItem(folder: folder, target: target, enabled: hasBookmarks), diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift index bcef29a4c5..2783fe14b4 100644 --- a/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift +++ b/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift @@ -51,6 +51,7 @@ struct AddBookmarkPopoverView: View { otherActionTitle: UserText.delete, isOtherActionDisabled: false, otherAction: model.removeButtonAction, + isOtherActionTriggeredByEscKey: false, defaultActionTitle: UserText.done, isDefaultActionDisabled: model.isDefaultActionButtonDisabled, defaultAction: model.doneButtonAction diff --git a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift index 208e47b3ec..234404402c 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift @@ -158,6 +158,9 @@ final class BookmarkListViewController: NSViewController { boxDivider.setContentHuggingPriority(.defaultHigh, for: .vertical) boxDivider.translatesAutoresizingMaskIntoConstraints = false + // keep OutlineView menu declaration before buttons as it‘s used as target + outlineView.menu = BookmarksContextMenu(bookmarkManager: bookmarkManager, delegate: self) + stackView.orientation = .horizontal stackView.spacing = 4 stackView.setHuggingPriority(.defaultHigh, for: .horizontal) @@ -170,9 +173,6 @@ final class BookmarkListViewController: NSViewController { stackView.addArrangedSubview(buttonsDivider) stackView.addArrangedSubview(manageBookmarksButton) - // keep OutlineView menu declaration before the buttons as it‘s their target - outlineView.menu = BookmarksContextMenu(bookmarkManager: bookmarkManager, delegate: self) - newBookmarkButton.bezelStyle = .shadowlessSquare newBookmarkButton.cornerRadius = 4 newBookmarkButton.normalTintColor = .button @@ -254,7 +254,6 @@ final class BookmarkListViewController: NSViewController { outlineView.usesAutomaticRowHeights = true outlineView.target = self outlineView.action = #selector(handleClick) - outlineView.menu = BookmarksContextMenu(bookmarkManager: bookmarkManager, delegate: self) outlineView.dataSource = dataSource outlineView.delegate = dataSource diff --git a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift index 78cdc6efcd..114ecb6858 100644 --- a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift +++ b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift @@ -51,6 +51,7 @@ struct AddEditBookmarkDialogView: ModalView { otherActionTitle: viewModel.bookmarkModel.cancelActionTitle, isOtherActionDisabled: viewModel.bookmarkModel.isOtherActionDisabled, otherAction: viewModel.bookmarkModel.cancel, + isOtherActionTriggeredByEscKey: true, defaultActionTitle: viewModel.bookmarkModel.defaultActionTitle, isDefaultActionDisabled: viewModel.bookmarkModel.isDefaultActionDisabled, defaultAction: viewModel.bookmarkModel.addOrSave diff --git a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift index 8d34889432..7831248391 100644 --- a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift +++ b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift @@ -36,6 +36,7 @@ struct AddEditBookmarkView: View { let otherActionTitle: String let isOtherActionDisabled: Bool let otherAction: @MainActor (_ dismiss: () -> Void) -> Void + let isOtherActionTriggeredByEscKey: Bool let defaultActionTitle: String let isDefaultActionDisabled: Bool @@ -78,6 +79,7 @@ struct AddEditBookmarkView: View { viewState: .init(buttonsState), otherButtonAction: .init( title: otherActionTitle, + keyboardShortCut: isOtherActionTriggeredByEscKey ? .cancelAction : nil, isDisabled: isOtherActionDisabled, action: otherAction ), diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuPopover.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuPopover.swift index 2de6a55def..5da88072c1 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuPopover.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuPopover.swift @@ -113,12 +113,12 @@ final class BookmarksBarMenuPopover: NSPopover { return frame } - /// close other BookmarkListPopover-s shown from the main window when opening a new one + /// close other `BookmarksBarMenuPopover`-s and `BookmarkListPopover`-s shown from the main window when opening a new one static func closeBookmarkListPopovers(shownIn window: NSWindow?, except popoverToKeep: BookmarksBarMenuPopover? = nil) { guard let window, // ignore when opening a submenu from another BookmarkListPopover !(window.contentViewController?.nextResponder is Self) else { return } - for case let .some(popover as Self) in (window.childWindows ?? []).map(\.contentViewController?.nextResponder) where popover !== popoverToKeep && popover.isShown { + for case let .some(popover as NSPopover) in (window.childWindows ?? []).map(\.contentViewController?.nextResponder) where popover !== popoverToKeep && popover.isShown { popover.close() } } diff --git a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift index 21f8dc1cc2..65a9134400 100644 --- a/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/DuckPlayerTabExtension.swift @@ -362,7 +362,7 @@ extension DuckPlayerTabExtension: NavigationResponder { return } if navigation.url.isDuckPlayer { - var setting = preferences.duckPlayerMode == .enabled ? "always" : "default" + let setting = preferences.duckPlayerMode == .enabled ? "always" : "default" let newTabSettings = preferences.duckPlayerOpenInNewTab ? "true" : "false" let autoplay = preferences.duckPlayerAutoplay ? "true" : "false" diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index 0d1f226caf..d1e20e9248 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -439,7 +439,7 @@ final class BrowserTabViewController: NSViewController { private func subscribeToDuckPlayerOnboardingPrompt(of tabViewModel: TabViewModel?) { tabViewModel?.tab.duckPlayerOnboardingPublisher.sink { [weak self, weak tab = tabViewModel?.tab] onboardingState in - guard let self, let tab, let onboardingState = onboardingState, onboardingState.onboardingDecider.canDisplayOnboarding else { + guard let self, tab != nil, let onboardingState = onboardingState, onboardingState.onboardingDecider.canDisplayOnboarding else { self?.duckPlayerOnboardingModalManager.close(animated: false, completion: nil) return } From f6ef398f23ef4e6a91ed62a860b42fd02ac2ab6d Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Tue, 3 Sep 2024 05:35:49 +0000 Subject: [PATCH 2/4] Bump version to 1.105.0 (253) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 27a1c8bc72..a2a5610bdd 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 252 +CURRENT_PROJECT_VERSION = 253 From 4e6c672381013ee7c4e7e670698d04c34bc1c200 Mon Sep 17 00:00:00 2001 From: Juan Manuel Pereira Date: Tue, 3 Sep 2024 10:11:38 -0300 Subject: [PATCH 3/4] Bug: Disable boomark reordering when searching (#3188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1204006570077678/1208204239267626/f Tech Design URL: CC: **Description**: - Fix a bug where bookmark reordering was possible during search (both in the panel and the manager) - Fix a bug where the folder was not correctly highlighted after being tapped in the bookmarks panel **Steps to test this PR**: 1. Start a search (either in the bookmarks manager o panel) 2. Check that you cannot reorder items 3. You should be able to drop items in a folder **Definition of Done**: * [x] Does this PR satisfy our [Definition of Done](https://app.asana.com/0/1202500774821704/1207634633537039/f)? — ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- .../Bookmarks/Model/BookmarkOutlineViewDataSource.swift | 7 +++++++ DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift | 2 +- .../View/BookmarkManagementDetailViewController.swift | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift index c80c02f163..9a6d7b840c 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift @@ -264,6 +264,13 @@ final class BookmarkOutlineViewDataSource: NSObject, BookmarksOutlineViewDataSou } let destination = destinationNode.isRoot ? PseudoFolder.bookmarks : destinationNode.representedObject + + guard !isSearching || destination is BookmarkFolder else { return .none } + + if let destinationFolder = destination as? BookmarkFolder { + self.dragDestinationFolder = destinationFolder + } + let operation = dragDropManager.validateDrop(info, to: destination) self.dragDestinationFolder = (operation == .none || item == nil) ? nil : destinationNode.representedObject as? BookmarkFolder diff --git a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift index 234404402c..4b4bd45bef 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift @@ -451,7 +451,7 @@ final class BookmarkListViewController: NSViewController { expandFoldersAndScrollUntil(folder) outlineView.scrollToAdjustedPositionInOutlineView(folder) - guard let node = treeController.node(representing: folder) else { return } + guard let node = treeController.findNodeWithId(representing: folder) else { return } outlineView.highlight(node) } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift index f857da6728..cc251ff4d6 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift @@ -473,6 +473,9 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { let destination = destination(for: dropOperation, at: row) + + guard !isSearching || destination is BookmarkFolder else { return .none } + return dragDropManager.validateDrop(info, to: destination) } From ba241fc4355b328be996f1fd1cc1d7d148610135 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Tue, 3 Sep 2024 16:33:08 +0100 Subject: [PATCH 4/4] UI Ship review feedback for Duck Player onboarding (#3186) Task/Issue URL: https://app.asana.com/0/1204167627774280/1208188855592767/f **Description**: * Change font style * Update copy --- .../SystemGray90.colorset/Contents.json | 38 ++ DuckDuckGo/Common/Localizables/UserText.swift | 6 +- DuckDuckGo/Localizable.xcstrings | 532 +++++++++++++++++- .../DuckPlayerOnboardingModalView.swift | 52 +- .../YoutubePlayer/TabModal/TabModal.swift | 20 +- 5 files changed, 626 insertions(+), 22 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Colors/SystemGray90.colorset/Contents.json diff --git a/DuckDuckGo/Assets.xcassets/Colors/SystemGray90.colorset/Contents.json b/DuckDuckGo/Assets.xcassets/Colors/SystemGray90.colorset/Contents.json new file mode 100644 index 0000000000..e035ee6e40 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Colors/SystemGray90.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.133", + "green" : "0.133", + "red" : "0.133" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index c397dcfa68..dd6c175855 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -362,8 +362,10 @@ struct UserText { 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") - static let duckPlayerOnboardingChoiceModalTitle = NSLocalizedString("duck-player.onboarding-choice-modal-title", value: "Drowning in ads on YouTube?", comment: "Title for a Duck Player onboarding modal screen") - static let duckPlayerOnboardingChoiceModalMessage = NSLocalizedString("duck-player.onboarding-choice-modal-message", value: "Duck Player lets you watch without targeted ads and comes free to use in DuckDuckGo.", comment: "Message for a Duck Player onboarding modal screen") + static let duckPlayerOnboardingChoiceModalTitleTop = NSLocalizedString("duck-player.onboarding-choice-modal-title-top", value: "Drowning in ads on YouTube?", comment: "Top title for a Duck Player onboarding modal screen") + static let duckPlayerOnboardingChoiceModalTitleBottom = NSLocalizedString("duck-player.onboarding-choice-modal-title-bottom", value: "Try Duck Player!", comment: "Bottom title for a Duck Player onboarding modal screen") + + static let duckPlayerOnboardingChoiceModalMessage = NSLocalizedString("duck-player.onboarding-choice-modal-message-body", value: "Duck Player lets you watch YouTube without targeted ads in DuckDuckGo and what you watch won't influence your recommendations.", comment: "Message for a Duck Player onboarding modal screen") static let duckPlayerOnboardingChoiceModalCTAConfirm = NSLocalizedString("duck-player.onboarding-choice-modal-CTA-confirm", value: "Turn on Duck Player", comment: "Confirm Button to enable Duck Player. -Duck Player- should not be translated") static let duckPlayerOnboardingChoiceModalCTADeny = NSLocalizedString("duck-player.onboarding-choice-modal-CTA-deny", value: "Not Now", comment: "Deny Button to enable Duck Player") diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 8cd2dcad72..8eda5f4a91 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -13541,7 +13541,7 @@ "it" : { "stringUnit" : { "state" : "translated", - "value" : "È DuckDuckGo Search." + "value" : "Vantaggi di DuckDuckGo Search." } }, "nl" : { @@ -18701,6 +18701,486 @@ } } }, + "duck-player.onboarding-choice-modal-CTA-confirm" : { + "comment" : "Confirm Button to enable Duck Player. -Duck Player- should not be translated", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Duck Player aktivieren" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Turn on Duck Player" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activar Duck Player" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activez Duck Player" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Attiva Duck Player" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Duck Player aanzetten" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Włącz Duck Player" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ligar o Duck Player" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Включить Duck Player" + } + } + } + }, + "duck-player.onboarding-choice-modal-CTA-deny" : { + "comment" : "Deny Button to enable Duck Player", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jetzt nicht" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Not Now" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ahora no" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pas maintenant" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non adesso" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Niet nu" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nie teraz" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agora não" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не сейчас" + } + } + } + }, + "duck-player.onboarding-choice-modal-message-body" : { + "comment" : "Message for a Duck Player onboarding modal screen", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mit dem Duck Player kannst du YouTube ohne gezielte Werbung in DuckDuckGo ansehen und was du dir ansiehst, hat keinen Einfluss auf deine Empfehlungen." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Duck Player lets you watch YouTube without targeted ads in DuckDuckGo and what you watch won't influence your recommendations." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Duck Player te permite ver YouTube sin anuncios segmentados en DuckDuckGo y lo que veas no influirá en tus recomendaciones." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Duck Player vous permet de regarder YouTube sans publicités ciblées dans DuckDuckGo. De plus, ce que vous regardez n'influence pas vos recommandations." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Duck Player ti consente di guardare YouTube senza annunci mirati in DuckDuckGo e ciò che guardi non inciderà sui consigli che ricevi." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Met Duck Player kun je YouTube bekijken in DuckDuckGo, zonder gerichte advertenties. Wat je bekijkt heeft geen invloed op je aanbevelingen." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Duck Player umożliwia oglądanie YouTube bez ukierunkowanych reklam w DuckDuckGo i wpływu oglądanych treści na rekomendacje." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "O Duck Player permite-te ver o YouTube sem anúncios segmentados no DuckDuckGo, e o que vês não vai influenciar as tuas recomendações." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Проигрыватель Duck Player позволяет смотреть видео из YouTube в браузере DuckDuckGo без целевой рекламы. Просмотренные ролики не влияют на рекомендации." + } + } + } + }, + "duck-player.onboarding-choice-modal-title-bottom" : { + "comment" : "Bottom title for a Duck Player onboarding modal screen", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Teste Duck Player!" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Try Duck Player!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Prueba Duck Player!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Essayez Duck Player !" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prova Duck Player!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Probeer Duck Player!" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wypróbuj Duck Player!" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Experimenta o Duck Player!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Попробуйте Duck Player!" + } + } + } + }, + "duck-player.onboarding-choice-modal-title-top" : { + "comment" : "Top title for a Duck Player onboarding modal screen", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ertrinkst du in Werbung auf YouTube?" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Drowning in ads on YouTube?" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Te ahogas en anuncios en YouTube?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "YouTube vous inonde de publicités ?" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "YouTube ti inonda di annunci?" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Te veel advertenties op YouTube?" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Za dużo reklam na YouTube?" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Já não suportas ver anúncios no YouTube?" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Шквал рекламы на YouTube?" + } + } + } + }, + "duck-player.onboarding-confirmation-modal-CTA-confirm" : { + "comment" : "Button to confirm on Duck Player onboarding modal confirmation screen", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Verstanden" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Got it" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entendido" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "J'ai compris" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ho capito" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ik snap het" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rozumiem" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entendi" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Понятно" + } + } + } + }, + "duck-player.onboarding-confirmation-modal-message" : { + "comment" : "Message for a Duck Player onboarding modal confirmation screen", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wähl ein Video aus, um die Magie des Duck Players zu erleben." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Pick a video to see Duck Player work its magic." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Elige un vídeo para ver cómo Duck Player hace su magia." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez une vidéo pour voir opérer la magie de Duck Player." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Scegli un video per vedere Duck Player fare la sua magia." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kies een video om te zien hoe Duck Player voor je werkt." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wybierz film, aby zobaczyć, jak działa Duck Player." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Escolhe um vídeo para o Duck Player fazer a sua magia." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выберите видео, чтобы оценить потрясающие возможности Duck Player в деле." + } + } + } + }, + "duck-player.onboarding-confirmation-modal-title" : { + "comment" : "Title for a Duck Player onboarding modal confirmation screen", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alles bereit!" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "All set!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Todo listo!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tout est prêt !" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tutto pronto!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Helemaal klaar!" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wszystko gotowe!" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tudo pronto!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Всё готово!" + } + } + } + }, "duck-player.show-buttons" : { "comment" : "Private YouTube Player option", "extractionState" : "extracted_with_value", @@ -52648,11 +53128,59 @@ "comment" : "Menu with feedback commands", "extractionState" : "extracted_with_value", "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback von Privacy Pro senden" + } + }, "en" : { "stringUnit" : { "state" : "new", "value" : "Send Privacy Pro Feedback" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar comentarios sobre Privacy Pro" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Envoyer des commentaires sur Privacy Pro" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invia feedback su Privacy Pro" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Feedback sturen naar Privacy Pro" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyślij opinię na temat Privacy Pro" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enviar feedback sobre o Privacy Pro" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Отправить отзыв о Privacy Pro" + } } } }, @@ -58493,4 +59021,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/DuckDuckGo/YoutubePlayer/Onboarding/DuckPlayerOnboardingModal/DuckPlayerOnboardingModalView.swift b/DuckDuckGo/YoutubePlayer/Onboarding/DuckPlayerOnboardingModal/DuckPlayerOnboardingModalView.swift index 8f7ecbae99..82d6cefffc 100644 --- a/DuckDuckGo/YoutubePlayer/Onboarding/DuckPlayerOnboardingModal/DuckPlayerOnboardingModalView.swift +++ b/DuckDuckGo/YoutubePlayer/Onboarding/DuckPlayerOnboardingModal/DuckPlayerOnboardingModalView.swift @@ -21,8 +21,8 @@ import SwiftUI struct DuckPlayerOnboardingModalView: View { private enum Constants { static let outerContainerWidth: CGFloat = 504 - static let smallContainerHeight: CGFloat = 182 - static let bigContainerHeight: CGFloat = 286 + static let smallContainerHeight: CGFloat = 166 + static let bigContainerHeight: CGFloat = 350 static let containerCornerRadius: CGFloat = 12 static let darkModeBorderColor: Color = .white.opacity(0.2) static let whiteModeBorderColor: Color = .black.opacity(0.1) @@ -64,28 +64,46 @@ struct DuckPlayerOnboardingModalView: View { case .onboardingOptions: DuckPlayerOnboardingChoiceView(turnOnButtonPressed: { - viewModel.currentView = .confirmation + withAnimation { + viewModel.currentView = .confirmation + } viewModel.handleTurnOnCTA() }, notNowPressed: viewModel.handleNotNowCTA) } } } +private enum Constants { + enum FontSize { + static let title: CGFloat = 17 + static let body: CGFloat = 13 + } + + enum Layout { + static let modalOuterVerticalSpacing: CGFloat = 20 + static let modalInnerVerticalSpacing: CGFloat = 8 + } +} + private struct DuckPlayerOnboardingChoiceView: View { let turnOnButtonPressed: () -> Void let notNowPressed: () -> Void var body: some View { - VStack(spacing: 20) { + VStack(spacing: Constants.Layout.modalOuterVerticalSpacing) { DaxSpeechBubble { - VStack (alignment: .leading, spacing: 8) { - Text(UserText.duckPlayerOnboardingChoiceModalTitle) - .font(.title) - .padding(.horizontal) + VStack (alignment: .leading, spacing: Constants.Layout.modalInnerVerticalSpacing) { + VStack (alignment: .leading, spacing: 0) { + Text(UserText.duckPlayerOnboardingChoiceModalTitleTop) + Text(UserText.duckPlayerOnboardingChoiceModalTitleBottom) + } + .font(.system(size: Constants.FontSize.title).weight(.bold)) + .padding(.horizontal) Text(UserText.duckPlayerOnboardingChoiceModalMessage) - .font(.body) + .font(.system(size: Constants.FontSize.body)) .multilineText() + .lineSpacing(4) .padding(.horizontal) HStack { @@ -113,6 +131,7 @@ private struct DuckPlayerOnboardingChoiceView: View { Text(UserText.duckPlayerOnboardingChoiceModalCTAConfirm) } .buttonStyle(PrimaryCTAStyle()) + } } } @@ -121,20 +140,21 @@ private struct DuckPlayerOnboardingChoiceView: View { private struct DuckPlayerOnboardingConfirmationView: View { let voidButtonPressed: () -> Void var body: some View { - VStack(spacing: 20) { + VStack(spacing: Constants.Layout.modalOuterVerticalSpacing) { DaxSpeechBubble { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: Constants.Layout.modalInnerVerticalSpacing) { Text(UserText.duckPlayerOnboardingConfirmationModalTitle) - .font(.title) + .foregroundColor(.systemGray90) + .font(.system(size: Constants.FontSize.title).weight(.bold)) .padding(.horizontal) Text(UserText.duckPlayerOnboardingConfirmationModalMessage) - .font(.body) + .foregroundColor(.systemGray90) + .font(.system(size: Constants.FontSize.body)) .padding(.horizontal) } .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal) - } Button { @@ -279,7 +299,6 @@ private struct SecondaryCTAStyle: ButtonStyle { }, notNowPressed: { }) - .frame(width: 504, height: 286) Divider() .padding() @@ -287,7 +306,8 @@ private struct SecondaryCTAStyle: ButtonStyle { DuckPlayerOnboardingConfirmationView(voidButtonPressed: { }) - .frame(width: 504, height: 152) } + .frame(width: 504) + .fixedSize() .padding() } diff --git a/DuckDuckGo/YoutubePlayer/TabModal/TabModal.swift b/DuckDuckGo/YoutubePlayer/TabModal/TabModal.swift index 1c6422ba8e..ff8cbd0ddc 100644 --- a/DuckDuckGo/YoutubePlayer/TabModal/TabModal.swift +++ b/DuckDuckGo/YoutubePlayer/TabModal/TabModal.swift @@ -19,7 +19,7 @@ import Cocoa import Combine private enum AnimationConsts { - static let yAnimationOffset: CGFloat = 65 + static let yAnimationOffset: CGFloat = 70 static let duration: CGFloat = 0.6 } @@ -36,6 +36,10 @@ public final class TabModal { window.isOpaque = false window.hasShadow = true window.level = .floating + + window.contentView?.wantsLayer = true + window.contentView?.layer?.cornerRadius = 12 + window.contentView?.layer?.masksToBounds = true } modalViewController.view.wantsLayer = true return windowController @@ -125,13 +129,25 @@ extension TabModal: TabModalPresentable { overlayWindow.setFrameOrigin(NSPoint(x: xPosition, y: yPosition)) overlayWindow.alphaValue = 0 + /// There's a bug in macOS 14.x where, if a window's alpha value is animated from X to Y, the final value will always be X. + /// This is a workaround to prevent that. + var titleWindowOffset: CGFloat = 0 + if #unavailable(macOS 15) { + overlayWindow.styleMask.insert(.titled) + titleWindowOffset = 28 + } + NSAnimationContext.runAnimationGroup { context in context.duration = AnimationConsts.duration - let newOrigin = NSPoint(x: xPosition, y: yPosition - AnimationConsts.yAnimationOffset) + let newOrigin = NSPoint(x: xPosition, y: yPosition - AnimationConsts.yAnimationOffset - titleWindowOffset) let size = overlayWindow.frame.size overlayWindow.animator().alphaValue = 1 overlayWindow.animator().setFrame(NSRect(origin: newOrigin, size: size), display: true) + } + /// Second part of the workaround mentioned above + if #unavailable(macOS 15) { + overlayWindow.styleMask.remove(.titled) } } else { overlayWindow.setFrameOrigin(NSPoint(x: xPosition, y: yPosition - AnimationConsts.yAnimationOffset))