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

Address bookmarks feedback #2411

Merged
merged 19 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8f6576f
Bookmarks dialog views infrastructure (#2198)
alessandroboron Feb 15, 2024
d7354a6
Bookmarks add/edit dialog views (#2209)
alessandroboron Feb 15, 2024
8cbf3a4
Bookmarks folder add/edit logic (#2221)
alessandroboron Feb 23, 2024
bf47c30
Bookmarks add/edit logic (#2231)
alessandroboron Feb 26, 2024
ff1cdfb
Bookmarks shortcut panel (#2245)
alessandroboron Feb 26, 2024
96d16f0
Manage Bookmarks Changes (#2270)
alessandroboron Mar 1, 2024
fd2434e
Bookmarks bar and Favorites changes (#2286)
alessandroboron Mar 1, 2024
7aa8463
Bookmarks Add delete icons on multiple items selection (#2271)
alessandroboron Mar 5, 2024
6662df7
Centralise Contextual Menu (#2297)
alessandroboron Mar 7, 2024
a90da95
Alessandro/bookmarks fix shortcut panel (#2313)
alessandroboron Mar 7, 2024
3c941e6
Fix merge conflicts when rebasing
alessandroboron Mar 7, 2024
c53b8fd
Remove stale bookmark string not used anymore
alessandroboron Mar 7, 2024
1a43a18
Bookmarks remove unused code (#2323)
alessandroboron Mar 7, 2024
7d97321
Fix Add Bookmark buttons size (#2353)
alessandroboron Mar 8, 2024
7fb6c53
Alessandro/address bookmarks list fix menu (#2352)
alessandroboron Mar 8, 2024
872b40d
Ensure that root bookmark folder comparison works when one folder has…
alessandroboron Mar 11, 2024
72f84ec
Alessandro/bookmarks ship review design (#2386)
alessandroboron Mar 13, 2024
8979fd7
Update Manage Bookmarks chevron icon (#2413)
alessandroboron Mar 14, 2024
1ce025a
Fix the chevron icon in Manage Bookmarks (#2439)
alessandroboron Mar 18, 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
234 changes: 202 additions & 32 deletions DuckDuckGo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "icon-16-bookmark-add.pdf",
"filename" : "AddBookmark.svg",
"idiom" : "universal"
}
],
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "icon-16-folder-add.pdf",
"filename" : "AddFolder.svg",
"idiom" : "universal"
}
],
Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "BookmarksFolder.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Chevron-Medium-Right-16.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
15 changes: 15 additions & 0 deletions DuckDuckGo/Assets.xcassets/Images/Trash.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Trash.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
9 changes: 9 additions & 0 deletions DuckDuckGo/Assets.xcassets/Images/Trash.imageset/Trash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Bookmarks+Tab.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 Foundation

extension Tab {

@MainActor
static func withContentOfBookmark(folder: BookmarkFolder, burnerMode: BurnerMode) -> [Tab] {
folder.children.compactMap { entity in
guard let url = (entity as? Bookmark)?.urlObject else { return nil }
return Tab(content: .url(url, source: .bookmark), shouldLoadInBackground: true, burnerMode: burnerMode)
}
}

}

extension TabCollection {

@MainActor
static func withContentOfBookmark(folder: BookmarkFolder, burnerMode: BurnerMode) -> TabCollection {
let tabs = Tab.withContentOfBookmark(folder: folder, burnerMode: burnerMode)
return TabCollection(tabs: tabs)
}

}
80 changes: 75 additions & 5 deletions 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: Identifiable {
internal class BaseBookmarkEntity: Identifiable, Equatable, Hashable {

static func singleEntity(with uuid: String) -> NSFetchRequest<BookmarkEntity> {
let request = BookmarkEntity.fetchRequest()
Expand Down Expand Up @@ -92,6 +92,23 @@ internal class BaseBookmarkEntity: Identifiable {
}
}

// Subclasses needs to override to check equality on their properties
func isEqual(to instance: BaseBookmarkEntity) -> Bool {
id == instance.id &&
title == instance.title &&
isFolder == instance.isFolder
}

static func == (lhs: BaseBookmarkEntity, rhs: BaseBookmarkEntity) -> Bool {
return type(of: lhs) == type(of: rhs) && lhs.isEqual(to: rhs)
}

func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(title)
hasher.combine(isFolder)
}

}

final class BookmarkFolder: BaseBookmarkEntity {
Expand Down Expand Up @@ -129,6 +146,38 @@ final class BookmarkFolder: BaseBookmarkEntity {

super.init(id: id, title: title, isFolder: true)
}

override func isEqual(to instance: BaseBookmarkEntity) -> Bool {
guard let folder = instance as? BookmarkFolder else {
return false
}
return id == folder.id &&
title == folder.title &&
isFolder == folder.isFolder &&
isParentFolderEqual(lhs: parentFolderUUID, rhs: folder.parentFolderUUID) &&
children == folder.children
}

override func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(title)
hasher.combine(isFolder)
hasher.combine(parentFolderUUID)
hasher.combine(children)
}

// In some cases a bookmark folder that is child of the root folder has its `parentFolderUUID` set to `bookmarks_root`. In some other cases is nil. Making sure that comparing a `nil` and a `bookmarks_root` does not return false. Probably would be good idea to remove the optionality of `parentFolderUUID` in the future and set it to `bookmarks_root` when needed.
private func isParentFolderEqual(lhs: String?, rhs: String?) -> Bool {
switch (lhs, rhs) {
case (.none, .none):
return true
case (.some(let lhsValue), .some(let rhsValue)):
return lhsValue == rhsValue
case (.some(let value), .none), (.none, .some(let value)):
return value == "bookmarks_root"
}
}

}

final class Bookmark: BaseBookmarkEntity {
Expand Down Expand Up @@ -183,12 +232,33 @@ final class Bookmark: BaseBookmarkEntity {
parentFolderUUID: bookmark.parentFolderUUID)
}

}
convenience init(from bookmark: Bookmark, withNewUrl url: String, title: String, isFavorite: Bool) {
self.init(id: bookmark.id,
url: url,
title: title,
isFavorite: isFavorite,
parentFolderUUID: bookmark.parentFolderUUID)
}

extension BaseBookmarkEntity: Equatable {
override func isEqual(to instance: BaseBookmarkEntity) -> Bool {
guard let bookmark = instance as? Bookmark else {
return false
}
return id == bookmark.id &&
title == bookmark.title &&
isFolder == bookmark.isFolder &&
url == bookmark.url &&
isFavorite == bookmark.isFavorite &&
parentFolderUUID == bookmark.parentFolderUUID
}

static func == (lhs: BaseBookmarkEntity, rhs: BaseBookmarkEntity) -> Bool {
return lhs.id == rhs.id
override func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(title)
hasher.combine(isFolder)
hasher.combine(url)
hasher.combine(isFavorite)
hasher.combine(parentFolderUUID)
}

}
32 changes: 32 additions & 0 deletions DuckDuckGo/Bookmarks/Model/BookmarkFolderInfo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// BookmarkFolderInfo.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 Foundation

protocol BookmarksEntityIdentifiable {
var entityId: String { get }
var parentId: String? { get }
}

struct BookmarkEntityInfo: Equatable, BookmarksEntityIdentifiable {
let entity: BaseBookmarkEntity
let parent: BookmarkFolder?

var entityId: String { entity.id }
var parentId: String? { parent?.id }
}
29 changes: 22 additions & 7 deletions DuckDuckGo/Bookmarks/Model/BookmarkList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,19 +124,39 @@ struct BookmarkList {
}
}

mutating func update(bookmark: Bookmark, newURL: String, newTitle: String, newIsFavorite: Bool) -> Bookmark? {
guard !bookmark.isFolder else { return nil }

let newBookmark = Bookmark(from: bookmark, withNewUrl: newURL, title: newTitle, isFavorite: newIsFavorite)
return updateBookmarkList(newBookmark: newBookmark, oldBookmark: bookmark)
}

mutating func updateUrl(of bookmark: Bookmark, to newURL: String) -> Bookmark? {
guard !bookmark.isFolder else { return nil }

guard itemsDict[newURL] == nil else {
os_log("BookmarkList: Update failed, new url already in bookmark list")
return nil
}

let newBookmark = Bookmark(from: bookmark, with: newURL)
return updateBookmarkList(newBookmark: newBookmark, oldBookmark: bookmark)
}

func bookmarks() -> [IdentifiableBookmark] {
return allBookmarkURLsOrdered
}

}

private extension BookmarkList {

mutating private func updateBookmarkList(newBookmark: Bookmark, oldBookmark bookmark: Bookmark) -> Bookmark? {
guard itemsDict[bookmark.url] != nil, let index = allBookmarkURLsOrdered.firstIndex(of: IdentifiableBookmark(from: bookmark)) else {
os_log("BookmarkList: Update failed, no such item in bookmark list")
return nil
}

let newBookmark = Bookmark(from: bookmark, with: newURL)
let newIdentifiableBookmark = IdentifiableBookmark(from: newBookmark)

allBookmarkURLsOrdered.remove(at: index)
Expand All @@ -146,13 +166,8 @@ struct BookmarkList {
let updatedBookmarks = existingBookmarks.filter { $0.id != bookmark.id }

itemsDict[bookmark.url] = updatedBookmarks
itemsDict[newURL] = (itemsDict[newURL] ?? []) + [bookmark]
itemsDict[newBookmark.url] = (itemsDict[newBookmark.url] ?? []) + [newBookmark]

return newBookmark
}

func bookmarks() -> [IdentifiableBookmark] {
return allBookmarkURLsOrdered
}

}
25 changes: 25 additions & 0 deletions DuckDuckGo/Bookmarks/Model/BookmarkManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ protocol BookmarkManager: AnyObject {
func remove(folder: BookmarkFolder)
func remove(objectsWithUUIDs uuids: [String])
func update(bookmark: Bookmark)
func update(bookmark: Bookmark, withURL url: URL, title: String, isFavorite: Bool)
func update(folder: BookmarkFolder)
func update(folder: BookmarkFolder, andMoveToParent parent: ParentFolderType)
@discardableResult func updateUrl(of bookmark: Bookmark, to newUrl: URL) -> Bookmark?
func add(bookmark: Bookmark, to parent: BookmarkFolder?, completion: @escaping (Error?) -> Void)
func add(objectsWithUUIDs uuids: [String], to parent: BookmarkFolder?, completion: @escaping (Error?) -> Void)
Expand Down Expand Up @@ -211,12 +213,35 @@ final class LocalBookmarkManager: BookmarkManager {

}

func update(bookmark: Bookmark, withURL url: URL, title: String, isFavorite: Bool) {
guard list != nil else { return }
guard getBookmark(forUrl: bookmark.url) != nil else {
os_log("LocalBookmarkManager: Failed to update bookmark url - not in the list.", type: .error)
return
}

guard let newBookmark = list?.update(bookmark: bookmark, newURL: url.absoluteString, newTitle: title, newIsFavorite: isFavorite) else {
os_log("LocalBookmarkManager: Failed to update URL of bookmark.", type: .error)
return
}

bookmarkStore.update(bookmark: newBookmark)
loadBookmarks()
requestSync()
}

func update(folder: BookmarkFolder) {
bookmarkStore.update(folder: folder)
loadBookmarks()
requestSync()
}

func update(folder: BookmarkFolder, andMoveToParent parent: ParentFolderType) {
bookmarkStore.update(folder: folder, andMoveToParent: parent)
loadBookmarks()
requestSync()
}

func updateUrl(of bookmark: Bookmark, to newUrl: URL) -> Bookmark? {
guard list != nil else { return nil }
guard getBookmark(forUrl: bookmark.url) != nil else {
Expand Down
Loading
Loading