Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 30 additions & 37 deletions DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class BookmarksBarViewController: NSViewController {
private let viewModel: BookmarksBarViewModel
private let tabCollectionViewModel: TabCollectionViewModel

private var viewModelCancellable: AnyCancellable?
private var cancellables = Set<AnyCancellable>()

fileprivate var clipThreshold: CGFloat {
let viewWidthWithoutClipIndicator = view.frame.width - clippedItemsIndicator.frame.minX
Expand Down Expand Up @@ -85,38 +85,25 @@ final class BookmarksBarViewController: NSViewController {

override func viewWillAppear() {
super.viewWillAppear()

subscribeToEvents()
refreshFavicons()
}

override func viewDidAppear() {
super.viewDidAppear()
frameChangeNotification()

frameDidChangeNotification()
}

func showBookmarksBarPrompt() {
BookmarksBarPromptPopover().show(relativeTo: promptAnchor.bounds, of: promptAnchor, preferredEdge: .minY)
self.bookmarksBarPromptShown = true
}

private func subscribeToViewModel() {
guard viewModelCancellable.isNil else {
assertionFailure("Tried to subscribe to view model while it is already subscribed")
return
}

viewModelCancellable = viewModel.$clippedItems.receive(on: RunLoop.main).sink { [weak self] _ in
self?.refreshClippedIndicator()
}
}

@objc
private func frameChangeNotification() {
// Wait until the frame change has taken effect for subviews before calculating changes to the list of items.
DispatchQueue.main.async {
self.viewModel.clipOrRestoreBookmarksBarItems()
self.refreshClippedIndicator()
}
private func frameDidChangeNotification() {
self.viewModel.clipOrRestoreBookmarksBarItems()
self.refreshClippedIndicator()
}

override func removeFromParent() {
Expand All @@ -125,25 +112,32 @@ final class BookmarksBarViewController: NSViewController {
}

private func subscribeToEvents() {
NotificationCenter.default.addObserver(self,
selector: #selector(frameChangeNotification),
name: NSView.frameDidChangeNotification,
object: view)

NotificationCenter.default.addObserver(self,
selector: #selector(refreshFavicons),
name: .faviconCacheUpdated,
object: nil)

subscribeToViewModel()
guard cancellables.isEmpty else { return }

NotificationCenter.default.publisher(for: NSView.frameDidChangeNotification, object: view)
// Wait until the frame change has taken effect for subviews before calculating changes to the list of items.
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.frameDidChangeNotification()
}
.store(in: &cancellables)

NotificationCenter.default.publisher(for: .faviconCacheUpdated)
.sink { [weak self] _ in
self?.refreshFavicons()
}
.store(in: &cancellables)

viewModel.$clippedItems
.receive(on: RunLoop.main)
.sink { [weak self] _ in
self?.refreshClippedIndicator()
}
.store(in: &cancellables)
}

private func unsubscribeFromEvents() {
NotificationCenter.default.removeObserver(self, name: NSView.frameDidChangeNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: .faviconCacheUpdated, object: nil)

viewModelCancellable?.cancel()
viewModelCancellable = nil
cancellables.removeAll()
}

// MARK: - Layout
Expand All @@ -163,7 +157,6 @@ final class BookmarksBarViewController: NSViewController {
self.clippedItemsIndicator.isHidden = viewModel.clippedItems.isEmpty
}

@objc
private func refreshFavicons() {
bookmarksBarCollectionView.reloadData()
}
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Common/Extensions/NSEventExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// limitations under the License.
//

import Foundation
import AppKit

extension NSEvent {
static func isContextClick(_ event: NSEvent) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Common/Extensions/NSViewExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ extension NSView {
NSLayoutConstraintToAttribute(attribute: .width, multiplier: multiplier, constant: const)
}
static func height(multiplier: CGFloat = 1.0, const: CGFloat = 0.0) -> NSLayoutConstraintToAttribute {
NSLayoutConstraintToAttribute(attribute: .width, multiplier: multiplier, constant: const)
NSLayoutConstraintToAttribute(attribute: .height, multiplier: multiplier, constant: const)
}

static func const(multiplier: CGFloat = 1.0, _ const: CGFloat) -> NSLayoutConstraintToAttribute {
Expand Down
59 changes: 49 additions & 10 deletions DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ extension UserDefaults {
public struct UserDefaultsWrapper<T> {

public enum Key: String, CaseIterable {
/// system setting defining window title double-click action
case appleActionOnDoubleClick = "AppleActionOnDoubleClick"

case configLastUpdated = "config.last.updated"
case configStorageTrackerRadarEtag = "config.storage.trackerradar.etag"
Expand Down Expand Up @@ -193,24 +195,49 @@ public struct UserDefaultsWrapper<T> {

public var wrappedValue: T {
get {
if let storedValue = defaults.object(forKey: key.rawValue),
let typedValue = storedValue as? T {
guard let storedValue = defaults.object(forKey: key.rawValue) else {
if setIfEmpty {
setValue(defaultValue)
}

return defaultValue
}

if let typedValue = storedValue as? T {
return typedValue
}

if setIfEmpty {
defaults.set(defaultValue, forKey: key.rawValue)
guard let rawRepresentableType = T.self as? any RawRepresentable.Type,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow storing RawRepresentable values using UserDefaultsWrapper

let value = rawRepresentableType.init(anyRawValue: storedValue) as? T else {
return defaultValue
}

return defaultValue
return value
}
set {
if (newValue as? AnyOptional)?.isNil == true {
defaults.removeObject(forKey: key.rawValue)
} else {
defaults.set(newValue, forKey: key.rawValue)
}
setValue(newValue)
}
}

private func setValue(_ value: T) {
guard (value as? AnyOptional)?.isNil != true else {
defaults.removeObject(forKey: key.rawValue)
return
}

if PropertyListSerialization.propertyList(value, isValidFor: .binary) {
defaults.set(value, forKey: key.rawValue)
return
}

guard let rawRepresentable = value as? any RawRepresentable,
PropertyListSerialization.propertyList(rawRepresentable.rawValue, isValidFor: .binary) else {
assertionFailure("\(value) cannot be stored in UserDefaults")
return
}

defaults.set(rawRepresentable.rawValue, forKey: key.rawValue)

}

static func clearAll() {
Expand Down Expand Up @@ -244,3 +271,15 @@ extension UserDefaultsWrapper where T: OptionalProtocol {
}

}

private extension RawRepresentable {

init?(anyRawValue: Any) {
guard let rawValue = anyRawValue as? RawValue else {
assertionFailure("\(anyRawValue) is not \(RawValue.self)")
return nil
}
self.init(rawValue: rawValue)
}

}
41 changes: 29 additions & 12 deletions DuckDuckGo/Common/View/AppKit/WindowDraggingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,24 @@
// limitations under the License.
//

import Cocoa
import AppKit
import Combine

final class WindowDraggingView: NSView {
let mouseDownPublisher: AnyPublisher<NSEvent, Never>

private let mouseDownSubject = PassthroughSubject<NSEvent, Never>()

var mouseDownPublisher: AnyPublisher<NSEvent, Never> {
mouseDownSubject.eraseToAnyPublisher()
}

private enum DoubleClickAction: String {
case none = "None"
case miniaturize = "Minimize"
case zoom = "Maximize"
}
@UserDefaultsWrapper(key: .appleActionOnDoubleClick, defaultValue: .zoom)
private var actionOnDoubleClick: DoubleClickAction

override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
return true
Expand All @@ -39,29 +52,33 @@ final class WindowDraggingView: NSView {
mouseDownSubject.send(event)

if event.clickCount == 2 {
zoom()
performDoubleClickAction()
} else {
drag(with: event)
}
}

override init(frame frameRect: NSRect) {
mouseDownPublisher = mouseDownSubject.eraseToAnyPublisher()
super.init(frame: frameRect)
}

required init?(coder: NSCoder) {
mouseDownPublisher = mouseDownSubject.eraseToAnyPublisher()
super.init(coder: coder)
private func performDoubleClickAction() {
switch actionOnDoubleClick {
case .zoom:
zoom()
case .miniaturize:
miniaturize()
case .none:
break
}
}

private func zoom() {
window?.zoom(self)
}

private func miniaturize() {
window?.miniaturize(self)
}

private func drag(with event: NSEvent) {
window?.performDrag(with: event)
}

private let mouseDownSubject = PassthroughSubject<NSEvent, Never>()
}