Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bookmark popover in SwiftUI #2036

Merged
merged 21 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f96615c
refactor AddBookmarkModalViewController in SwiftUI
mallexxx Jan 3, 2024
189b97c
disableAutocorrection
mallexxx Jan 3, 2024
0f6aa9a
refactor AddFolderModalViewController in SwiftUI
mallexxx Jan 3, 2024
5742d2d
fix title, add button title for edit folder name dialog
mallexxx Jan 3, 2024
bff36ed
fix initial folder name for rename folder dialog
mallexxx Jan 3, 2024
4adbc73
generalize Modal View presentation into a separate protocol extension
mallexxx Jan 3, 2024
92ba6c9
AddBookmarkPopover, AddBookmarkFolderPopover refactored in SwiftUI
mallexxx Jan 9, 2024
5825b9b
Merge remote-tracking branch 'origin/main' into alex/add-bookmark-pop…
mallexxx Jan 9, 2024
d7120a6
final touches
mallexxx Jan 9, 2024
354be6c
fix bookmark folder selection
mallexxx Jan 9, 2024
001d953
close Add Bookmark popover on second click
mallexxx Jan 9, 2024
ce15748
Merge branch 'main' into alex/add-bookmark-popover-swiftui
mallexxx Jan 15, 2024
90e5ba6
Merge remote-tracking branch 'origin/main' into alex/add-bookmark-pop…
mallexxx Jan 15, 2024
c8b8d23
fix appstore, release builds
mallexxx Jan 15, 2024
577014c
Merge remote-tracking branch 'origin/main' into alex/add-bookmark-pop…
mallexxx Jan 15, 2024
ed224ef
rm debug print
mallexxx Jan 16, 2024
9707e57
upd Localizable.xcstrings
mallexxx Jan 16, 2024
8808478
cleanup
mallexxx Jan 16, 2024
90960ed
update Localizable.xcstrings
mallexxx Jan 16, 2024
999aef3
Merge remote-tracking branch 'origin/main' into alex/add-bookmark-pop…
mallexxx Jan 16, 2024
034c1ac
fix popover closing on macOS 11
mallexxx Jan 16, 2024
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
96 changes: 64 additions & 32 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion DuckDuckGo/Bookmarks/Model/Bookmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import Cocoa
import Bookmarks

internal class BaseBookmarkEntity {
internal class BaseBookmarkEntity: Identifiable {

static func singleEntity(with uuid: String) -> NSFetchRequest<BookmarkEntity> {
let request = BookmarkEntity.fetchRequest()
Expand Down
2 changes: 1 addition & 1 deletion DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct AddBookmarkFolderModalView: ModalView {
.fontWeight(.semibold)

HStack(spacing: 16) {
Text("Name:", comment: "New bookmark folder dialog folder name field heading")
Text(UserText.newBookmarkDialogBookmarkNameTitle)
.frame(height: 22)

TextField("", text: $model.folderName)
Expand Down
98 changes: 98 additions & 0 deletions DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// AddBookmarkFolderPopoverView.swift
//
// 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 SwiftUI

struct AddBookmarkFolderPopoverView: ModalView {

@ObservedObject var model: AddBookmarkFolderPopoverViewModel

var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text(UserText.newFolder)
.bold()

VStack(alignment: .leading, spacing: 7) {
Text("Location:", comment: "Add Folder popover: parent folder picker title")

BookmarkFolderPicker(folders: model.folders, selectedFolder: $model.parent)
.accessibilityIdentifier("bookmark.folder.folder.dropdown")
.disabled(model.isDisabled)
}

VStack(alignment: .leading, spacing: 7) {
Text(UserText.newFolderDialogFolderNameTitle)

TextField("", text: $model.folderName)
.focusedOnAppear()
.accessibilityIdentifier("bookmark.folder.name.textfield")
.textFieldStyle(RoundedBorderTextFieldStyle())
.disabled(model.isDisabled)
}
.padding(.bottom, 16)

HStack {
Spacer()

Button(action: {
model.cancel()
}) {
Text(UserText.cancel)
}
.accessibilityIdentifier("bookmark.add.cancel.button")
.disabled(model.isDisabled)

Button(action: {
model.addFolder()
}) {
Text("Add Folder", comment: "Add Folder popover: Create folder button")
}
.keyboardShortcut(.defaultAction)
.accessibilityIdentifier("bookmark.add.add.folder.button")
.disabled(model.isAddFolderButtonDisabled || model.isDisabled)
}
}
.font(.system(size: 13))
.padding()
.frame(width: 300, height: 229)
.background(Color(.popoverBackground))
}
}

#if DEBUG
#Preview {
let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [
BookmarkFolder(id: "1", title: "Folder 1", children: [
BookmarkFolder(id: "2", title: "Nested Folder", children: [
])
]),
BookmarkFolder(id: "3", title: "Another Folder", children: [
BookmarkFolder(id: "4", title: "Nested Folder", children: [
BookmarkFolder(id: "5", title: "Another Nested Folder", children: [
])
])
])
]))
bkman.loadBookmarks()
customAssertionFailure = { _, _, _ in }

return AddBookmarkFolderPopoverView(model: AddBookmarkFolderPopoverViewModel(bookmarkManager: bkman) {
print("CompletionHandler:", $0?.title ?? "<nil>")
})
}
#endif
76 changes: 76 additions & 0 deletions DuckDuckGo/Bookmarks/View/AddBookmarkPopover.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// AddBookmarkPopover.swift
//
// Copyright © 2021 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 AppKit
import SwiftUI

final class AddBookmarkPopover: NSPopover {

var isNew: Bool = false
var bookmark: Bookmark? {
didSet {
setupBookmarkAddController()
}
}

private weak var addressBar: NSView?

/// prefferred bounding box for the popover positioning
override var boundingFrame: NSRect {
guard let addressBar,
let window = addressBar.window else { return .infinite }
var frame = window.convertToScreen(addressBar.convert(addressBar.bounds, to: nil))

frame = frame.insetBy(dx: -42, dy: -window.frame.size.height)

return frame
}

override init() {
super.init()

animates = false
behavior = .transient
}

required init?(coder: NSCoder) {
fatalError("BookmarksPopover: Bad initializer")
}

private func setupBookmarkAddController() {
guard let bookmark else { return }
contentViewController = NSHostingController(rootView: AddBookmarkPopoverView(model: AddBookmarkPopoverViewModel(bookmark: bookmark))
.legacyOnDismiss { [weak self] in
self?.performClose(nil)
})
}

override func show(relativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge) {
self.addressBar = positioningView.superview
super.show(relativeTo: positioningRect, of: positioningView, preferredEdge: preferredEdge)
}

override func performClose(_ sender: Any?) {
self.close()
}

func popoverWillClose() {
bookmark = nil
}

}
137 changes: 137 additions & 0 deletions DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
//
// AddBookmarkPopoverView.swift
//
// 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 SwiftUI
import SwiftUIExtensions

struct AddBookmarkPopoverView: View {

@ObservedObject private var model: AddBookmarkPopoverViewModel
@Environment(\.dismiss) private var dismiss

init(model: AddBookmarkPopoverViewModel) {
self.model = model
}

var body: some View {
if let addFolderViewModel = model.addFolderViewModel {
AddBookmarkFolderPopoverView(model: addFolderViewModel)
} else {
addBookmarkView
}
}

@MainActor
private var addBookmarkView: some View {
VStack(alignment: .leading, spacing: 19) {
Text("Bookmark Added", comment: "Bookmark Added popover title")
.fontWeight(.bold)
.padding(.bottom, 4)

VStack(alignment: .leading, spacing: 10) {
TextField("", text: $model.bookmarkTitle)
.focusedOnAppear()
.accessibilityIdentifier("bookmark.add.name.textfield")
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.system(size: 14))

HStack {
BookmarkFolderPicker(folders: model.folders,
selectedFolder: $model.selectedFolder)
.accessibilityIdentifier("bookmark.add.folder.dropdown")

Button {
model.addFolderButtonAction()
} label: {
Image(.addFolder)
}
.accessibilityIdentifier("bookmark.add.new.folder.button")
.buttonStyle(StandardButtonStyle())
}
}

Divider()

Button {
model.favoritesButtonAction()
} label: {
HStack(spacing: 8) {
if model.bookmark.isFavorite {
Image(.favoriteFilled)
Text(UserText.removeFromFavorites)
} else {
Image(.favorite)
Text(UserText.addToFavorites)
}
}
}
.accessibilityIdentifier("bookmark.add.add.to.favorites.button")
.buttonStyle(.borderless)
.foregroundColor(Color.button)

HStack {
Spacer()

Button {
model.removeButtonAction(dismiss: dismiss.callAsFunction)
} label: {
Text("Remove", comment: "Remove bookmark button title")
}
.accessibilityIdentifier("bookmark.add.remove.button")

Button {
model.doneButtonAction(dismiss: dismiss.callAsFunction)
} label: {
Text(UserText.done)
}
.keyboardShortcut(.defaultAction)
.accessibilityIdentifier("bookmark.add.done.button")
}

}
.font(.system(size: 13))
.padding(EdgeInsets(top: 19, leading: 19, bottom: 19, trailing: 19))
.frame(width: 300, height: 229)
.background(Color(.popoverBackground))
}

}

#if DEBUG
#Preview { {
let bkm = Bookmark(id: "n", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "1")
let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [
BookmarkFolder(id: "1", title: "Folder 1", children: [
bkm,
BookmarkFolder(id: "2", title: "Nested Folder", children: [
])
]),
Bookmark(id: "b2", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: true, parentFolderUUID: "1"),
BookmarkFolder(id: "3", title: "Another Folder", children: [
BookmarkFolder(id: "4", title: "Nested Folder", children: [
BookmarkFolder(id: "5", title: "Another Nested Folder", children: [
])
])
])
]))
bkman.loadBookmarks()
customAssertionFailure = { _, _, _ in }

return AddBookmarkPopoverView(model: AddBookmarkPopoverViewModel(bookmark: bkm, bookmarkManager: bkman))
}() }
#endif
Loading
Loading