diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c357b3fdd6..4a8f06593e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -751,7 +751,7 @@ 3706FC93293F65D500E42796 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AFCE8027DA2CA600471A10 /* PreferencesViewController.swift */; }; 3706FC94293F65D500E42796 /* FireproofDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B02198125E05FAC00ED7DEA /* FireproofDomains.swift */; }; 3706FC95293F65D500E42796 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B677440255DBEEA00025BD8 /* Database.swift */; }; - 3706FC96293F65D500E42796 /* LeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336D286915A10019DBFD /* LeftAlignedLayout.swift */; }; + 3706FC96293F65D500E42796 /* BookmarksBarCenterAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336D286915A10019DBFD /* BookmarksBarCenterAlignedLayout.swift */; }; 3706FC97293F65D500E42796 /* BookmarksOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928526670D1600AD2C21 /* BookmarksOutlineView.swift */; }; 3706FC98293F65D500E42796 /* CountryList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE65482271FCD53008D1D63 /* CountryList.swift */; }; 3706FC99293F65D500E42796 /* PreferencesSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C427F2FDD100F1F7B9 /* PreferencesSection.swift */; }; @@ -1502,7 +1502,7 @@ 4BE41A5E28446EAD00760399 /* BookmarksBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE41A5D28446EAD00760399 /* BookmarksBarViewModel.swift */; }; 4BE5336B286912D40019DBFD /* BookmarksBarCollectionViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BE53369286912D40019DBFD /* BookmarksBarCollectionViewItem.xib */; }; 4BE5336C286912D40019DBFD /* BookmarksBarCollectionViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */; }; - 4BE5336E286915A10019DBFD /* LeftAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336D286915A10019DBFD /* LeftAlignedLayout.swift */; }; + 4BE5336E286915A10019DBFD /* BookmarksBarCenterAlignedLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE5336D286915A10019DBFD /* BookmarksBarCenterAlignedLayout.swift */; }; 4BE53374286E39F10019DBFD /* ChromiumKeychainPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE53373286E39F10019DBFD /* ChromiumKeychainPrompt.swift */; }; 4BE65474271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE6546E271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift */; }; 4BE65476271FCD41008D1D63 /* PasswordManagementCreditCardItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BE65470271FCD40008D1D63 /* PasswordManagementCreditCardItemView.swift */; }; @@ -3720,7 +3720,7 @@ 4BE41A5D28446EAD00760399 /* BookmarksBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarViewModel.swift; sourceTree = ""; }; 4BE53369286912D40019DBFD /* BookmarksBarCollectionViewItem.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BookmarksBarCollectionViewItem.xib; sourceTree = ""; }; 4BE5336A286912D40019DBFD /* BookmarksBarCollectionViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarCollectionViewItem.swift; sourceTree = ""; }; - 4BE5336D286915A10019DBFD /* LeftAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeftAlignedLayout.swift; sourceTree = ""; }; + 4BE5336D286915A10019DBFD /* BookmarksBarCenterAlignedLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarCenterAlignedLayout.swift; sourceTree = ""; }; 4BE53373286E39F10019DBFD /* ChromiumKeychainPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChromiumKeychainPrompt.swift; sourceTree = ""; }; 4BE6546E271FCD40008D1D63 /* PasswordManagementIdentityItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordManagementIdentityItemView.swift; sourceTree = ""; }; 4BE65470271FCD40008D1D63 /* PasswordManagementCreditCardItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordManagementCreditCardItemView.swift; sourceTree = ""; }; @@ -6436,7 +6436,7 @@ 848648A02C76F4B20082282D /* BookmarksBarMenuViewController.swift */, 4BD18EFF283F0BC500058124 /* BookmarksBarViewController.swift */, 4BE41A5D28446EAD00760399 /* BookmarksBarViewModel.swift */, - 4BE5336D286915A10019DBFD /* LeftAlignedLayout.swift */, + 4BE5336D286915A10019DBFD /* BookmarksBarCenterAlignedLayout.swift */, 8400DC4A2C6E26AE006509D2 /* ItemCachingCollectionView.swift */, ); path = View; @@ -11386,7 +11386,7 @@ 3706FC94293F65D500E42796 /* FireproofDomains.swift in Sources */, 3706FC95293F65D500E42796 /* Database.swift in Sources */, 3707C71B294B5D0F00682A9F /* AutofillTabExtension.swift in Sources */, - 3706FC96293F65D500E42796 /* LeftAlignedLayout.swift in Sources */, + 3706FC96293F65D500E42796 /* BookmarksBarCenterAlignedLayout.swift in Sources */, 3706FC97293F65D500E42796 /* BookmarksOutlineView.swift in Sources */, 3706FC98293F65D500E42796 /* CountryList.swift in Sources */, 1DEF3BAE2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */, @@ -12945,7 +12945,7 @@ 4B02198A25E05FAC00ED7DEA /* FireproofDomains.swift in Sources */, 4B677442255DBEEA00025BD8 /* Database.swift in Sources */, 1DDC85032B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */, - 4BE5336E286915A10019DBFD /* LeftAlignedLayout.swift in Sources */, + 4BE5336E286915A10019DBFD /* BookmarksBarCenterAlignedLayout.swift in Sources */, B6BCC5232AFCDABB002C5499 /* DataImportSourceViewModel.swift in Sources */, 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */, 4BF01C00272AE74C00884A61 /* CountryList.swift in Sources */, diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBar.storyboard b/DuckDuckGo/BookmarksBar/View/BookmarksBar.storyboard index 82c7fae914..847e02cc50 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBar.storyboard +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBar.storyboard @@ -1,7 +1,7 @@ - + - + @@ -23,13 +23,13 @@ - + - + - + @@ -78,7 +78,7 @@ - + @@ -140,7 +140,7 @@ - + @@ -168,7 +168,7 @@ - + diff --git a/DuckDuckGo/BookmarksBar/View/LeftAlignedLayout.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarCenterAlignedLayout.swift similarity index 81% rename from DuckDuckGo/BookmarksBar/View/LeftAlignedLayout.swift rename to DuckDuckGo/BookmarksBar/View/BookmarksBarCenterAlignedLayout.swift index 190ca32747..e86f0333a0 100644 --- a/DuckDuckGo/BookmarksBar/View/LeftAlignedLayout.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarCenterAlignedLayout.swift @@ -1,5 +1,5 @@ // -// LeftAlignedLayout.swift +// BookmarksBarCenterAlignedLayout.swift // // Copyright © 2022 DuckDuckGo. All rights reserved. // @@ -23,15 +23,22 @@ extension NSCollectionView { extension NSCollectionLayoutGroup { - static func leftAligned(cellSizes: [CGSize], interItemSpacing: CGFloat) -> NSCollectionLayoutGroup { + static func align(cellSizes: [CGSize], interItemSpacing: CGFloat, centered: Bool) -> NSCollectionLayoutGroup { let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(28)) return custom(layoutSize: groupSize) { environment in let verticalPosition: CGFloat = environment.container.contentInsets.top + let totalWidth = cellSizes.map(\.width).reduce(0) { $0 == 0 ? $1 : $0 + interItemSpacing + $1 } let maxItemHeight = cellSizes.map(\.height).max() ?? 0 var items: [NSCollectionLayoutGroupCustomItem] = [] - var horizontalPosition: CGFloat = interItemSpacing + var horizontalPosition: CGFloat + + if centered { + horizontalPosition = (environment.container.effectiveContentSize.width - totalWidth) / 2 + environment.container.contentInsets.leading + } else { + horizontalPosition = interItemSpacing + } let rowItems: [NSCollectionLayoutGroupCustomItem] = cellSizes.map { size in let origin = CGPoint(x: ceil(horizontalPosition), y: verticalPosition + (maxItemHeight - size.height) / 2) @@ -48,9 +55,9 @@ extension NSCollectionLayoutGroup { } } -final class BookmarksBarLeftAlignedLayout: NSCollectionViewCompositionalLayout { +final class BookmarksBarCenterAlignedLayout: NSCollectionViewCompositionalLayout { - private static let interItemGapWidth: CGFloat = 2 + private static let interItemGapWidth: CGFloat = 2.0 private var lastKnownInterItemGapIndicatorLayoutAttributes: NSCollectionViewLayoutAttributes? diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift index c8d9f6c016..113b413204 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift @@ -54,8 +54,16 @@ struct BookmarksBarMenuFactory { }, BlockMenuItem(title: UserText.mainMenuBookmarksShowBookmarksBarNever, isChecked: !prefs.showBookmarksBar) { prefs.showBookmarksBar = false + }, + NSMenuItem.separator(), + BlockMenuItem(title: UserText.mainMenuBookmarksLeftAlignBookmarksBar, isChecked: !prefs.centerAlignedBookmarksBarBool) { + prefs.centerAlignedBookmarksBarBool = false + }, + BlockMenuItem(title: UserText.mainMenuBookmarksCenterAlignBookmarksBar, isChecked: prefs.centerAlignedBookmarksBarBool) { + prefs.centerAlignedBookmarksBarBool = true } ]) + return item } diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift index 257e217db5..217dd44b78 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift @@ -38,6 +38,7 @@ final class BookmarksBarViewController: NSViewController { private let dragDropManager: BookmarkDragDropManager private let viewModel: BookmarksBarViewModel private let tabCollectionViewModel: TabCollectionViewModel + private let appereancePreferences: AppearancePreferencesPersistor private var cancellables = Set() @@ -59,9 +60,14 @@ final class BookmarksBarViewController: NSViewController { }! } - init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, dragDropManager: BookmarkDragDropManager = BookmarkDragDropManager.shared) { + init?(coder: NSCoder, tabCollectionViewModel: TabCollectionViewModel, + bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, + dragDropManager: BookmarkDragDropManager = BookmarkDragDropManager.shared, + appereancePreferences: AppearancePreferencesPersistor = AppearancePreferencesUserDefaultsPersistor() + ) { self.bookmarkManager = bookmarkManager self.dragDropManager = dragDropManager + self.appereancePreferences = appereancePreferences self.tabCollectionViewModel = tabCollectionViewModel self.viewModel = BookmarksBarViewModel(bookmarkManager: bookmarkManager, dragDropManager: dragDropManager, tabCollectionViewModel: tabCollectionViewModel) @@ -98,7 +104,6 @@ final class BookmarksBarViewController: NSViewController { bookmarksBarCollectionView.delegate = viewModel bookmarksBarCollectionView.dataSource = viewModel - bookmarksBarCollectionView.collectionViewLayout = createLeftAlignedCollectionViewLayout() view.postsFrameChangedNotifications = true bookmarksBarCollectionView.setAccessibilityIdentifier("BookmarksBarViewController.bookmarksBarCollectionView") @@ -120,6 +125,7 @@ final class BookmarksBarViewController: NSViewController { subscribeToEvents() refreshFavicons() + bookmarksBarCollectionView.collectionViewLayout = createCenterAlignedCollectionViewLayout(centered: appereancePreferences.centerAlignedBookmarksBar) } override func viewDidAppear() { @@ -167,6 +173,13 @@ final class BookmarksBarViewController: NSViewController { } .store(in: &cancellables) + NotificationCenter.default.publisher(for: AppearancePreferences.Notifications.bookmarksBarAlignmentChanged) + .compactMap { $0.userInfo?[AppearancePreferences.Constants.bookmarksBarAlignmentChangedIsCenterAlignedParameter] as? Bool } + .sink { [weak self] isCenterAligned in + self?.bookmarksBarCollectionView.collectionViewLayout = self?.createCenterAlignedCollectionViewLayout(centered: isCenterAligned) + } + .store(in: &cancellables) + viewModel.$clippedItems .receive(on: RunLoop.main) .sink { [weak self] _ in @@ -228,14 +241,14 @@ final class BookmarksBarViewController: NSViewController { // MARK: - Layout - private func createLeftAlignedLayout() -> NSCollectionLayoutSection { - let group = NSCollectionLayoutGroup.leftAligned(cellSizes: viewModel.cellSizes, interItemSpacing: BookmarksBarViewModel.Constants.buttonSpacing) + private func createAlignedLayout(centered: Bool) -> NSCollectionLayoutSection { + let group = NSCollectionLayoutGroup.align(cellSizes: viewModel.cellSizes, interItemSpacing: BookmarksBarViewModel.Constants.buttonSpacing, centered: centered) return NSCollectionLayoutSection(group: group) } - private func createLeftAlignedCollectionViewLayout() -> NSCollectionViewLayout { - return BookmarksBarLeftAlignedLayout { [unowned self] _, _ in - return createLeftAlignedLayout() + private func createCenterAlignedCollectionViewLayout(centered: Bool) -> NSCollectionViewLayout { + return BookmarksBarCenterAlignedLayout { [unowned self] _, _ in + return createAlignedLayout(centered: centered && viewModel.clippedItems.isEmpty) } } diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index e674fe5041..ccfcdd83b1 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -186,6 +186,11 @@ struct UserText { static let mainMenuBookmarksShowBookmarksBarAlways = NSLocalizedString("Always Show", comment: "Preference for always showing the bookmarks bar") static let mainMenuBookmarksShowBookmarksBarNewTabOnly = NSLocalizedString("Only Show on New Tab", comment: "Preference for only showing the bookmarks bar on new tab") static let mainMenuBookmarksShowBookmarksBarNever = NSLocalizedString("Never Show", comment: "Preference for never showing the bookmarks bar on new tab") + static let mainMenuBookmarksLeftAlignBookmarksBar = NSLocalizedString("Align Bookmarks to Left", comment: "Preference for left aligning the bookmarks bar") + static let mainMenuBookmarksCenterAlignBookmarksBar = NSLocalizedString("Align Bookmarks to Center", comment: "Preference for center aligning the bookmarks bar") + static let preferencesBookmarksCenterAlignBookmarksBarTitle = NSLocalizedString("Align Bookmarks", comment: "Preference title aligning the bookmarks bar") + static let preferencesBookmarksCenterAlignBookmarksBar = NSLocalizedString("Center", comment: "Preference title aligning the bookmarks bar") + static let preferencesBookmarksLeftAlignBookmarksBare = NSLocalizedString("Left", comment: "Preference title aligning the bookmarks bar") // MARK: - Main Menu -> Window static let mainMenuWindow = NSLocalizedString("Window", comment: "Main Menu ") diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 9dc22e9c51..9de38a69d9 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -144,6 +144,7 @@ public struct UserDefaultsWrapper { case bookmarksBarPromptShown = "bookmarks.bar.prompt.shown" case showBookmarksBar = "bookmarks.bar.show" + case centerAlignedBookmarksBar = "bookmarks.bar.center.aligned" case lastBookmarksBarUsagePixelSendDate = "bookmarks.bar.last-usage-pixel-send-date" case pinnedViews = "pinning.pinned-views" diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 3eb6a5ee7b..0ca53556ae 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -2422,6 +2422,165 @@ } } }, + "Align Bookmarks" : { + "comment" : "Preference title aligning the bookmarks bar", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lesezeichen ausrichten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alinear marcadores" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aligner les signets" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Allinea segnalibri" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bladwijzers uitlijnen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyrównaj zakładki" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alinhar marcadores" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выравнивание панели закладок" + } + } + } + }, + "Align Bookmarks to Center" : { + "comment" : "Preference for center aligning the bookmarks bar", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lesezeichen zentriert ausrichten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alinear marcadores en el centro" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aligner les signets au centre" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Allinea i segnalibri al centro" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bladwijzers centraal uitlijnen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyrównaj zakładki do środka" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alinhar marcadores ao centro" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выровнять по центру" + } + } + } + }, + "Align Bookmarks to Left" : { + "comment" : "Preference for left aligning the bookmarks bar", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lesezeichen linksbündig ausrichten" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alinea marcadores a la izquierda" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aligner les signets à gauche" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Allinea i segnalibri a sinistra" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bladwijzers naar links uitlijnen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wyrównaj zakładki do lewej" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alinhar marcadores à esquerda" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выровнять по левому краю" + } + } + } + }, "allow.integration" : { "comment" : "Setup of the integration with Bitwarden app", "extractionState" : "extracted_with_value", @@ -12264,6 +12423,59 @@ } } }, + "Center" : { + "comment" : "Preference title aligning the bookmarks bar", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mittig" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Centro" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Centre" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Centro" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Midden" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Środek" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Centro" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "По центру" + } + } + } + }, "check.allow.integration" : { "comment" : "Setup of the integration with Bitwarden app", "extractionState" : "extracted_with_value", @@ -30353,6 +30565,59 @@ } } }, + "Left" : { + "comment" : "Preference title aligning the bookmarks bar", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Links" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Izquierda" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "À gauche" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Left" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Links" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lewo" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Esquerdo" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Left" + } + } + } + }, "Let’s try doing it manually. It won’t take long." : { "comment" : "Suggestion to switch to a Manual File Data Import when data import fails.", "localizations" : { @@ -37203,55 +37468,55 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Du bist bereit! Du kannst mich jederzeit im Dock antreffen.\nMöchtest du sehen, wie ich dich beschütze? Versuche, eine deiner Lieblingsseiten zu besuchen 👆\n\nBehalte die Adressleiste im Auge. Ich werde Tracker blockieren und die Sicherheit deiner Verbindung verbessern, wenn möglichu{00A0}🔒" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You’re all set! You can find me hanging out in the Dock anytime.\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" + "value" : "You’re all set! You can find me hanging out in the Dock anytime.\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possible 🔒" } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "¡Ya está todo listo! Puedes encontrarme en el Dock en cualquier momento.\n¿Quieres ver cómo te protejo? Prueba a visitar uno de tus sitios favoritos 👆\n\nNo pierdas de vista la barra de direcciones al navegar. Bloquearé los rastreadores y mejoraré la seguridad de tu conexión cuando sea posible{00A0}🔒" } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Tout est prêt ! Vous pouvez me trouver sur le Dock à tout moment.\nVous voulez voir comment je vous protège ? Essayez de visiter l'un de vos sites préférés 👆\n\nContinuez à regarder la barre d'adresse au fur et à mesure. Je bloquerai les traqueurs et mettrai à niveau la sécurité de votre connexion si possible 🔒" } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Tutto pronto! Puoi trovarmi nel dock in qualsiasi momento.\nVuoi vedere come ti proteggo? Prova a visitare uno dei tuoi siti preferiti 👆\n\nContinua a controllare la barra degli indirizzi mentre esplori. Bloccherò i sistemi di tracciamento e aggiornerò la sicurezza della tua connessione quando possibile{00A0} 🔒" } }, "nl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Je bent helemaal klaar! Je kunt me altijd vinden in het Dock.\nWil je zien hoe ik je bescherm? Ga eens naar een van je favoriete websites 👆\n\nKijk tijdens het surfen goed naar de adresbalk. Ik blokkeer trackers en werk de beveiliging van je verbinding bij wanneer mogelijk 🔒" } }, "pl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Wszystko gotowe! W każdej chwili możesz mnie znaleźć w Docku.\nChcesz zobaczyć, jak Cię chronię? Spróbuj odwiedzić jedną z ulubionych stron 👆\n\nW międzyczasie obserwuj pasek adresu. Będę blokować mechanizmy śledzące i w miarę możliwości poprawiać bezpieczeństwo połączenia 🔒" } }, "pt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Está tudo pronto! Podes encontrar-me na Dock em qualquer altura.\nQueres ver como te protejo? Experimenta visitar um dos teus sites favoritos 👆\n\nContinua a observar a barra de endereço à medida que vais avançando. Vou bloquear os rastreadores e melhorar a segurança da tua ligação sempre que possível 🔒" } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Готово! Теперь меня всегда можно найти на док-панели.\nВам интересно, как я защищаю вашу конфиденциальность? Зайдите на свой любимый сайт...👆\n\nИ следите за адресной строкой. По возможности я заблокирую все трекеры и сделаю соединение более безопасным {00A0}🔒" } } @@ -37263,55 +37528,55 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Du bist bereit!\n\nMöchtest du sehen, wie ich dich beschütze? Versuche, eine deiner Lieblingsseiten zu besuchen 👆\n\nBehalte die Adressleiste im Auge. Ich werde Tracker blockieren und die Sicherheit deiner Verbindung verbessern, wenn möglich 🔒" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "You’re all set!\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possibleu{00A0}🔒" + "value" : "You’re all set!\n\nWant to see how I protect you? Try visiting one of your favorite sites 👆\n\nKeep watching the address bar as you go. I’ll be blocking trackers and upgrading the security of your connection when possible 🔒" } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "¡Ya está todo listo!\n\n¿Quieres ver cómo te protejo? Prueba a visitar uno de tus sitios favoritos 👆\n\nSigue viendo la barra de direcciones sobre la marcha. Bloquearé los rastreadores y mejoraré la seguridad de tu conexión cuando sea posible 🔒" } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Tout est prêt !\n\nVous voulez voir comment je vous protège ? Essayez de visiter l'un de vos sites préférés 👆\n\n Continuez à regarder la barre d'adresse au fur et à mesure. Je bloquerai les traqueurs et mettrai à niveau la sécurité de votre connexion si possible 🔒" } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Tutto pronto.\n\nVuoi vedere come ti proteggo? Prova a visitare uno dei tuoi siti preferiti 👆Continua a controllare la barra degli indirizzi mentre esplori. Bloccherò i sistemi di tracciamento e aggiornerò la sicurezza della tua connessione quando possibile 🔒" } }, "nl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Je bent helemaal klaar!\n\nWil je zien hoe ik je bescherm? Ga eens naar een van je favoriete websites 👆 \n\n Kijk tijdens het surfen goed naar de adresbalk. Ik blokkeer trackers en werk de beveiliging van je verbinding bij wanneer mogelijk 🔒" } }, "pl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Wszystko gotowe!\n\nChcesz zobaczyć, jak Cię chronię? Spróbuj odwiedzić jedną z ulubionych stron 👆\n\nW międzyczasie obserwuj pasek adresu. Będę blokować mechanizmy śledzące i w miarę możliwości poprawiać bezpieczeństwo połączenia 🔒" } }, "pt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Estás tudo pronto!\n\nQueres ver como te protejo? Experimenta visitar um dos teus sites favoritos 👆\n\nContinua a observar a barra de endereço à medida que vais avançando. Vou bloquear os rastreadores e melhorar a segurança da tua ligação sempre que possível 🔒" } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Все готово!\n\nХотите увидеть, как я вас защищаю? Зайдите на один из любимых сайтов 👆\n\nСледите за адресной строкой. Я по возможности буду блокировать трекеры и обеспечивать вам более безопасное соединение 🔒" } } @@ -44668,60 +44933,60 @@ } }, "pm.empty.default.description.extended.v2" : { - "comment" : "Label for default empty state description\n Label for default empty state description when the autolock feature is off", + "comment" : "Label for default empty state description\nLabel for default empty state description when the autolock feature is off", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Passwörter sind verschlüsselt. Niemand außer dir kann sie sehen, nicht einmal wir." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Passwords are encrypted. Nobody but you can see them, not even us." + "value" : "Passwords are encrypted." } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Las contraseñas están cifradas. Nadie más que tú puede verlas, ni siquiera nosotros." } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Les mots de passe sont cryptés. Personne d'autre que vous ne peut les voir, pas même nous." } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Le password sono crittografate. Nessuno tranne te può vederle, nemmeno noi." } }, "nl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Wachtwoorden worden versleuteld. Niemand anders kan ze zien, zelfs wij niet." } }, "pl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Hasła są szyfrowane. Nikt poza Tobą ich nie widzi, nawet my." } }, "pt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "As palavras-passe estão encriptadas. Ninguém além de ti pode vê-las, nem mesmo nós." } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Пароли подвергаются шифрованию. Никто, кроме вас, их не увидит. Даже мы." } } @@ -47608,7 +47873,7 @@ } }, "pm.save-credentials.security.info" : { - "comment" : "Info message for the save credentials dialog\n Info message for the save credentials dialog when the autolock feature is off", + "comment" : "Info message for the save credentials dialog\nInfo message for the save credentials dialog when the autolock feature is off", "localizations" : { "de" : { "stringUnit" : { diff --git a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift index e04212eba4..f5829a552e 100644 --- a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift +++ b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift @@ -34,6 +34,7 @@ protocol AppearancePreferencesPersistor { var bookmarksBarAppearance: BookmarksBarAppearance { get set } var homeButtonPosition: HomeButtonPosition { get set } var homePageCustomBackground: String? { get set } + var centerAlignedBookmarksBar: Bool { get set } } struct AppearancePreferencesUserDefaultsPersistor: AppearancePreferencesPersistor { @@ -81,6 +82,9 @@ struct AppearancePreferencesUserDefaultsPersistor: AppearancePreferencesPersisto @UserDefaultsWrapper(key: .homePageCustomBackground, defaultValue: nil) var homePageCustomBackground: String? + + @UserDefaultsWrapper(key: .centerAlignedBookmarksBar, defaultValue: true) + var centerAlignedBookmarksBar: Bool } protocol HomePageNavigator { @@ -165,6 +169,11 @@ final class AppearancePreferences: ObservableObject { struct Notifications { static let showBookmarksBarSettingChanged = NSNotification.Name("ShowBookmarksBarSettingChanged") static let bookmarksBarSettingAppearanceChanged = NSNotification.Name("BookmarksBarSettingAppearanceChanged") + static let bookmarksBarAlignmentChanged = NSNotification.Name("BookmarksBarAlignmentChanged") + } + + struct Constants { + static let bookmarksBarAlignmentChangedIsCenterAlignedParameter = "isCenterAligned" } static let shared = AppearancePreferences() @@ -246,6 +255,15 @@ final class AppearancePreferences: ObservableObject { } } + @Published var centerAlignedBookmarksBarBool: Bool { + didSet { + persistor.centerAlignedBookmarksBar = centerAlignedBookmarksBarBool + NotificationCenter.default.post(name: Notifications.bookmarksBarAlignmentChanged, + object: nil, + userInfo: [Constants.bookmarksBarAlignmentChangedIsCenterAlignedParameter: centerAlignedBookmarksBarBool]) + } + } + var isContinueSetUpAvailable: Bool { let osVersion = ProcessInfo.processInfo.operatingSystemVersion @@ -277,6 +295,7 @@ final class AppearancePreferences: ObservableObject { bookmarksBarAppearance = persistor.bookmarksBarAppearance homeButtonPosition = persistor.homeButtonPosition homePageCustomBackground = persistor.homePageCustomBackground.flatMap(CustomBackground.init) + centerAlignedBookmarksBarBool = persistor.centerAlignedBookmarksBar } private var persistor: AppearancePreferencesPersistor diff --git a/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift b/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift index 5b6b15b9c1..9ea82f488c 100644 --- a/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift @@ -129,7 +129,6 @@ extension Preferences { // SECTION 4: Bookmarks Bar PreferencePaneSection(UserText.showBookmarksBar) { - HStack { ToggleMenuItem(UserText.showBookmarksBarPreference, isOn: $model.showBookmarksBar) .accessibilityIdentifier("Preferences.AppearanceView.showBookmarksBarPreferenceToggle") @@ -150,6 +149,22 @@ extension Preferences { } .disabled(!model.showBookmarksBar) } + + HStack { + Text(UserText.preferencesBookmarksCenterAlignBookmarksBarTitle) + NSPopUpButtonView(selection: $model.centerAlignedBookmarksBarBool) { + let button = NSPopUpButton() + button.setContentHuggingPriority(.defaultHigh, for: .horizontal) + + let leftAligned = button.menu?.addItem(withTitle: UserText.preferencesBookmarksLeftAlignBookmarksBare, action: nil, keyEquivalent: "") + leftAligned?.representedObject = false + + let centerAligned = button.menu?.addItem(withTitle: UserText.preferencesBookmarksCenterAlignBookmarksBar, action: nil, keyEquivalent: "") + centerAligned?.representedObject = true + + return button + } + } } } } diff --git a/UnitTests/Preferences/AppearancePreferencesTests.swift b/UnitTests/Preferences/AppearancePreferencesTests.swift index b6ea70274f..43c0bcaead 100644 --- a/UnitTests/Preferences/AppearancePreferencesTests.swift +++ b/UnitTests/Preferences/AppearancePreferencesTests.swift @@ -31,6 +31,7 @@ struct AppearancePreferencesPersistorMock: AppearancePreferencesPersistor { var bookmarksBarAppearance: BookmarksBarAppearance var homeButtonPosition: HomeButtonPosition var homePageCustomBackground: String? + var centerAlignedBookmarksBar: Bool init( showFullURL: Bool = false, @@ -42,7 +43,8 @@ struct AppearancePreferencesPersistorMock: AppearancePreferencesPersistor { showBookmarksBar: Bool = true, bookmarksBarAppearance: BookmarksBarAppearance = .alwaysOn, homeButtonPosition: HomeButtonPosition = .right, - homePageCustomBackground: String? = nil + homePageCustomBackground: String? = nil, + centerAlignedBookmarksBar: Bool = true ) { self.showFullURL = showFullURL self.currentThemeName = currentThemeName @@ -54,6 +56,7 @@ struct AppearancePreferencesPersistorMock: AppearancePreferencesPersistor { self.bookmarksBarAppearance = bookmarksBarAppearance self.homeButtonPosition = homeButtonPosition self.homePageCustomBackground = homePageCustomBackground + self.centerAlignedBookmarksBar = centerAlignedBookmarksBar } } @@ -69,7 +72,8 @@ final class AppearancePreferencesTests: XCTestCase { isFavoriteVisible: true, isRecentActivityVisible: true, homeButtonPosition: .left, - homePageCustomBackground: CustomBackground.gradient(.gradient01).description + homePageCustomBackground: CustomBackground.gradient(.gradient01).description, + centerAlignedBookmarksBar: true ) ) @@ -81,6 +85,7 @@ final class AppearancePreferencesTests: XCTestCase { XCTAssertEqual(model.isRecentActivityVisible, true) XCTAssertEqual(model.homeButtonPosition, .left) XCTAssertEqual(model.homePageCustomBackground, .gradient(.gradient01)) + XCTAssertTrue(model.centerAlignedBookmarksBarBool) model = AppearancePreferences( persistor: AppearancePreferencesPersistorMock( @@ -91,7 +96,8 @@ final class AppearancePreferencesTests: XCTestCase { isFavoriteVisible: false, isRecentActivityVisible: false, homeButtonPosition: .left, - homePageCustomBackground: CustomBackground.gradient(.gradient05).description + homePageCustomBackground: CustomBackground.gradient(.gradient05).description, + centerAlignedBookmarksBar: false ) ) XCTAssertEqual(model.showFullURL, true) @@ -102,6 +108,7 @@ final class AppearancePreferencesTests: XCTestCase { XCTAssertEqual(model.isRecentActivityVisible, false) XCTAssertEqual(model.homeButtonPosition, .left) XCTAssertEqual(model.homePageCustomBackground, .gradient(.gradient05)) + XCTAssertFalse(model.centerAlignedBookmarksBarBool) } func testWhenInitializedWithGarbageThenThemeIsSetToSystemDefault() throws { diff --git a/UnitTests/Sync/Mocks/MockAppearancePreferencesPersistor.swift b/UnitTests/Sync/Mocks/MockAppearancePreferencesPersistor.swift index 6171721460..3c9624e536 100644 --- a/UnitTests/Sync/Mocks/MockAppearancePreferencesPersistor.swift +++ b/UnitTests/Sync/Mocks/MockAppearancePreferencesPersistor.swift @@ -45,4 +45,6 @@ class MockAppearancePreferencesPersistor: AppearancePreferencesPersistor { var bookmarksBarAppearance: BookmarksBarAppearance = .alwaysOn + var centerAlignedBookmarksBar: Bool = false + }