diff --git a/DuckDuckGo/Favorite.swift b/DuckDuckGo/Favorite.swift index 70262d0086..5b13b1f484 100644 --- a/DuckDuckGo/Favorite.swift +++ b/DuckDuckGo/Favorite.swift @@ -20,7 +20,7 @@ import Bookmarks import SwiftUI -struct Favorite: Identifiable, Equatable { +struct Favorite: Identifiable, Equatable, Hashable { let id: String let title: String let domain: String diff --git a/DuckDuckGo/FavoritesDefaultModel.swift b/DuckDuckGo/FavoritesDefaultModel.swift index b91c44c579..eae3da8301 100644 --- a/DuckDuckGo/FavoritesDefaultModel.swift +++ b/DuckDuckGo/FavoritesDefaultModel.swift @@ -114,6 +114,19 @@ final class FavoritesDefaultModel: FavoritesModel { onFavoriteEdit?(entity) } + func moveFavorites(from indexSet: IndexSet, to index: Int) { + guard indexSet.count == 1, + let fromIndex = indexSet.first else { return } + + let favorite = allFavorites[fromIndex] + guard let entity = lookupEntity(for: favorite) else { return } + + // adjust for different target index handling + let toIndex = index > fromIndex ? index - 1 : index + interactionModel.moveFavorite(entity, fromIndex: fromIndex, toIndex: toIndex) + allFavorites.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: index) + } + private func lookupEntity(for favorite: Favorite) -> BookmarkEntity? { interactionModel.favorites.first { $0.uuid == favorite.id diff --git a/DuckDuckGo/FavoritesModel.swift b/DuckDuckGo/FavoritesModel.swift index 60313089b1..427df29d1a 100644 --- a/DuckDuckGo/FavoritesModel.swift +++ b/DuckDuckGo/FavoritesModel.swift @@ -37,6 +37,7 @@ protocol FavoritesModel: AnyObject, ObservableObject { func favoriteSelected(_ favorite: Favorite) func editFavorite(_ favorite: Favorite) func deleteFavorite(_ favorite: Favorite) + func moveFavorites(from indexSet: IndexSet, to index: Int) } struct FavoritesSlice { diff --git a/DuckDuckGo/FavoritesPreviewModel.swift b/DuckDuckGo/FavoritesPreviewModel.swift index a3e145013f..4e59572887 100644 --- a/DuckDuckGo/FavoritesPreviewModel.swift +++ b/DuckDuckGo/FavoritesPreviewModel.swift @@ -71,6 +71,10 @@ final class FavoritesPreviewModel: FavoritesModel { } + func moveFavorites(from indexSet: IndexSet, to index: Int) { + allFavorites.move(fromOffsets: indexSet, toOffset: index) + } + func loadFavicon(for favorite: Favorite, size: CGFloat) async { } diff --git a/DuckDuckGo/FavoritesView.swift b/DuckDuckGo/FavoritesView.swift index 24a01a2bc0..bd2f690fe5 100644 --- a/DuckDuckGo/FavoritesView.swift +++ b/DuckDuckGo/FavoritesView.swift @@ -19,6 +19,7 @@ import Bookmarks import SwiftUI +import UniformTypeIdentifiers struct FavoritesView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @@ -35,7 +36,7 @@ struct FavoritesView: View { let result = model.prefixedFavorites(for: columns) NewTabPageGridView { _ in - ForEach(result.items) { item in + ReorderableForEach(result.items) { item in Button(action: { model.favoriteSelected(item) selectionFeedback.selectionChanged() @@ -52,6 +53,15 @@ struct FavoritesView: View { .background(.clear) .frame(width: NewTabPageGrid.Item.edgeSize) }) + .previewShape() + } preview: { favorite in + FavoriteIconView(favorite: favorite, faviconLoading: model.faviconLoader) + .frame(width: NewTabPageGrid.Item.edgeSize) + .previewShape() + } onMove: { from, to in + withAnimation { + model.moveFavorites(from: from, to: to) + } } } @@ -70,6 +80,22 @@ struct FavoritesView: View { } } +private extension View { + func previewShape() -> some View { + contentShape(.dragPreview, RoundedRectangle(cornerRadius: 8)) + } +} + +extension Favorite: Reorderable { + var dropItemProvider: NSItemProvider { + NSItemProvider(object: (urlObject?.absoluteString ?? "") as NSString) + } + + var dropType: UTType { + .plainText + } +} + #Preview { FavoritesView(model: FavoritesPreviewModel()) }