Skip to content

GT-1719 favorites drag and drop #2509

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

Merged
merged 34 commits into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
91e2f58
allow favorited tools to be move tools around and create useCases
rachaelblue Mar 21, 2025
879b6d4
sort favorites by position
rachaelblue Mar 21, 2025
05d79be
add test for reordering
rachaelblue Mar 21, 2025
04deec3
add second test
rachaelblue Mar 21, 2025
8aba919
fix off by one error when moving an item down
rachaelblue Mar 25, 2025
3017023
fix all buttons triggering on list tap
rachaelblue Mar 25, 2025
aeea2a2
adjust positions when favorite is removed
rachaelblue Mar 25, 2025
ee84694
fix failing tests
rachaelblue Mar 25, 2025
37edaa2
store cancellables in static var
rachaelblue Mar 25, 2025
bf6fa3f
validate favorites positions
rachaelblue Mar 27, 2025
cfcd1c7
sort by createdAt when validating
rachaelblue Mar 27, 2025
f04c558
Merge branch 'develop' of https://github.com/CruGlobal/godtools-swift…
rachaelblue Mar 28, 2025
98cb20a
add reorderFavorite domain model
rachaelblue Mar 28, 2025
a4019a2
add test for removingFavorite and checking position
rachaelblue Mar 28, 2025
e7a5edd
fix failing test
rachaelblue Apr 1, 2025
293f677
add test for adding a favorite tool
rachaelblue Apr 1, 2025
0b7e8db
commit changes
rachaelblue Apr 1, 2025
30fdfc2
remove row separator from top of list
rachaelblue Apr 1, 2025
3fb9dba
Reverting changes to dev since nothing changed
levieggertcru Apr 2, 2025
2a839df
Merge pull request #2528 from CruGlobal/GT-1719-revert-tool-card-view…
levieggertcru Apr 2, 2025
8d63445
Create failing test by adding all favorited tools at position 0.
levieggertcru Apr 2, 2025
611bd82
validate favorites after storing
rachaelblue Apr 3, 2025
4ce2e10
validate resources sorted by position not in publisher
rachaelblue Apr 3, 2025
5fa4611
Revert "validate favorites after storing"
rachaelblue Apr 5, 2025
306dc33
fix adding multiple favorites all having position 0 and write test
rachaelblue Apr 5, 2025
e70f1ec
Merge branch 'develop' of https://github.com/CruGlobal/godtools-swift…
rachaelblue Apr 5, 2025
94dc147
Merge branch 'GT-1719-Favorites-Drag-and-Drop' into GT-1719-create-fa…
levieggertcru Apr 8, 2025
d7a21dc
Merge pull request #2529 from CruGlobal/GT-1719-create-failing-test-i…
levieggertcru Apr 8, 2025
98795c3
Remove invalid test
levieggertcru Apr 9, 2025
cca0719
Merge pull request #2538 from CruGlobal/GT-1719-remove-invalid-test
levieggertcru Apr 9, 2025
b478f91
return void from store favorites method
rachaelblue Apr 10, 2025
cb4d2fe
rename validation method to migration and delete methods that fetch/s…
rachaelblue Apr 10, 2025
17f27ef
remove ascending order param
rachaelblue Apr 11, 2025
16f1f81
Merge branch 'develop' into GT-1719-Favorites-Drag-and-Drop
levieggertcru Apr 14, 2025
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
48 changes: 48 additions & 0 deletions godtools.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class FavoritedToolsLatestToolDownloader: FavoritedToolsLatestToolDownloaderInte
)
.flatMap({ (resourcesChanged: Void, favoritedResourcesChanged: Void) -> AnyPublisher<[FavoritedResourceDataModel], Never> in

let favoritedTools: [FavoritedResourceDataModel] = self.favoritedResourcesRepository.getFavoritedResourcesSortedByCreatedAt(ascendingOrder: false)
let favoritedTools: [FavoritedResourceDataModel] = self.favoritedResourcesRepository.getFavoritedResourcesSortedByPosition(ascendingOrder: false)

return Just(favoritedTools)
.eraseToAnyPublisher()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,5 @@ class StoreInitialFavoritedTools: StoreInitialFavoritedToolsInterface {

return favoritedResourcesRepository
.storeFavoritedResourcesPublisher(ids: favoritedResourceIdsToStore)
.catch { _ in
return Just([])
.eraseToAnyPublisher()
}
.map { _ in
Void()
}
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,15 @@ class GetYourFavoritedToolsRepository: GetYourFavoritedToolsRepositoryInterface
func getToolsPublisher(translateInLanguage: AppLanguageDomainModel, maxCount: Int?) -> AnyPublisher<[YourFavoritedToolDomainModel], Never> {

return Publishers.CombineLatest3(
favoritedResourcesRepository.getFavoritedResourcesChangedPublisher(),
resourcesRepository.getResourcesChangedPublisher(),
getToolListItemInterfaceStringsRepository.getStringsPublisher(translateInLanguage: translateInLanguage)
getToolListItemInterfaceStringsRepository.getStringsPublisher(translateInLanguage: translateInLanguage),
favoritedResourcesRepository.getFavoritedResourcesSortedByPositionPublisher()
)
.flatMap({ (favoritedResourcesChanged: Void, resourcesChanged: Void, interfaceStrings: ToolListItemInterfaceStringsDomainModel) -> AnyPublisher<[YourFavoritedToolDomainModel], Never> in
.flatMap({ (resourcesChanged: Void, interfaceStrings: ToolListItemInterfaceStringsDomainModel, favoritedResourceModels: [FavoritedResourceDataModel]) -> AnyPublisher<[YourFavoritedToolDomainModel], Never> in

let numberOfFavoritedTools: Int = self.favoritedResourcesRepository.getNumberOfFavoritedResources()

let favoritedResources: [ResourceModel] = self.favoritedResourcesRepository
.getFavoritedResourcesSortedByCreatedAt(ascendingOrder: false)
let favoritedResources: [ResourceModel] = favoritedResourceModels
.prefix(maxCount ?? numberOfFavoritedTools)
.compactMap({
self.resourcesRepository.getResource(id: $0.id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// ReorderFavoritedToolRepository.swift
// godtools
//
// Created by Rachael Skeath on 3/19/25.
// Copyright © 2025 Cru. All rights reserved.
//

import Foundation
import Combine

class ReorderFavoritedToolRepository: ReorderFavoritedToolRepositoryInterface {

private let favoritedResourcesRepository: FavoritedResourcesRepository

init(favoritedResourcesRepository: FavoritedResourcesRepository) {
self.favoritedResourcesRepository = favoritedResourcesRepository
}

func reorderFavoritedToolPubilsher(toolId: String, originalPosition: Int, newPosition: Int) -> AnyPublisher<[ReorderFavoritedToolDomainModel], Error> {

return favoritedResourcesRepository.reorderFavoritedResourcePublisher(id: toolId, originalPosition: originalPosition, newPosition: newPosition)
.map { favoritesReordered in
return favoritesReordered.map {
ReorderFavoritedToolDomainModel(dataModelId: $0.id, position: $0.position)
}
}
.eraseToAnyPublisher()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ class ToggleToolFavoritedRepository: ToggleToolFavoritedRepositoryInterface {
else {

return favoritedResourcesRepository.storeFavoritedResourcesPublisher(ids: [toolId])
.catch { _ in
return Just([])
.eraseToAnyPublisher()
}
.map { _ in
ToolIsFavoritedDomainModel(dataModelId: toolId, isFavorited: true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ class FavoritesDataLayerDependencies {
)
}

func getReorderFavoritedToolRepository() -> ReorderFavoritedToolRepositoryInterface {
return ReorderFavoritedToolRepository(
favoritedResourcesRepository: coreDataLayer.getFavoritedResourcesRepository()
)
}

func getToggleToolFavoritedRepository() -> ToggleToolFavoritedRepositoryInterface {
return ToggleToolFavoritedRepository(
favoritedResourcesRepository: coreDataLayer.getFavoritedResourcesRepository()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ class FavoritesDomainLayerDependencies {
)
}

func getReorderFavoritedToolUseCase() -> ReorderFavoritedToolUseCase {
return ReorderFavoritedToolUseCase(
reorderFavoritedToolRepository: dataLayer.getReorderFavoritedToolRepository()
)
}

func getToggleFavoritedToolUseCase() -> ToggleToolFavoritedUseCase {
return ToggleToolFavoritedUseCase(
toggleToolFavoritedRepository: dataLayer.getToggleToolFavoritedRepository()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// ReorderFavoritedToolDomainModel.swift
// godtools
//
// Created by Rachael Skeath on 3/28/25.
// Copyright © 2025 Cru. All rights reserved.
//

import Foundation

struct ReorderFavoritedToolDomainModel {

let dataModelId: String
let position: Int
}

extension ReorderFavoritedToolDomainModel: Identifiable {

var id: String {
return dataModelId
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// ReorderFavoritedToolRepositoryInterface.swift
// godtools
//
// Created by Rachael Skeath on 3/19/25.
// Copyright © 2025 Cru. All rights reserved.
//

import Foundation
import Combine

protocol ReorderFavoritedToolRepositoryInterface {

func reorderFavoritedToolPubilsher(toolId: String, originalPosition: Int, newPosition: Int) -> AnyPublisher<[ReorderFavoritedToolDomainModel], Error>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// ReorderFavoritedToolUseCase.swift
// godtools
//
// Created by Rachael Skeath on 3/19/25.
// Copyright © 2025 Cru. All rights reserved.
//

import Foundation
import Combine

class ReorderFavoritedToolUseCase {

private let reorderFavoritedToolRepository: ReorderFavoritedToolRepositoryInterface

init(reorderFavoritedToolRepository: ReorderFavoritedToolRepositoryInterface) {
self.reorderFavoritedToolRepository = reorderFavoritedToolRepository
}

func reorderFavoritedToolPublisher(toolId: String, originalPosition: Int, newPosition: Int) -> AnyPublisher<[ReorderFavoritedToolDomainModel], Error> {

return reorderFavoritedToolRepository.reorderFavoritedToolPubilsher(toolId: toolId, originalPosition: originalPosition, newPosition: newPosition)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,53 +25,47 @@ struct AllYourFavoriteToolsView: View {
var body: some View {

GeometryReader { geometry in

PullToRefreshScrollView(showsIndicators: true) {

VStack(alignment: .leading, spacing: 0) {

VStack(alignment: .leading, spacing: 0) {
List {
Text(viewModel.sectionTitle)
.font(FontLibrary.sfProTextRegular.font(size: 22))
.foregroundColor(ColorPalette.gtGrey.color)
.padding(.top, 30)
.padding(.leading, contentHorizontalInsets)
LazyVStack(alignment: .center, spacing: toolCardSpacing) {

ForEach(viewModel.favoritedTools) { (tool: YourFavoritedToolDomainModel) in

ToolCardView(
viewModel: viewModel.getToolViewModel(tool: tool),
geometry: geometry,
layout: .landscape,
showsCategory: true,
navButtonTitleHorizontalPadding: YourFavoriteToolsView.toolCardNavButtonTitleHorizontalPadding,
accessibility: .favoriteTool,
favoriteTappedClosure: {
viewModel.unfavoriteToolTapped(tool: tool)
},
toolDetailsTappedClosure: {
viewModel.toolDetailsTapped(tool: tool)
},
openToolTappedClosure: {
viewModel.openToolTapped(tool: tool)
},
toolTappedClosure: {
viewModel.toolTapped(tool: tool)
}
)
}
.listRowSeparator(.hidden)

ForEach(viewModel.favoritedTools) { (tool: YourFavoritedToolDomainModel) in
ToolCardView(
viewModel: viewModel.getToolViewModel(tool: tool),
geometry: geometry,
layout: .landscape,
showsCategory: true,
navButtonTitleHorizontalPadding: YourFavoriteToolsView.toolCardNavButtonTitleHorizontalPadding,
accessibility: .favoriteTool,
favoriteTappedClosure: {

viewModel.unfavoriteToolTapped(tool: tool)
},
toolDetailsTappedClosure: {

viewModel.toolDetailsTapped(tool: tool)
},
openToolTappedClosure: {

viewModel.openToolTapped(tool: tool)
},
toolTappedClosure: {

viewModel.toolTapped(tool: tool)
}
)
.buttonStyle(.plain)
.listRowSeparator(.hidden)
}
.onMove { from, to in
viewModel.toolMoved(fromOffsets: from, toOffset: to)
}
.padding([.top], toolCardSpacing)
.padding([.bottom], DashboardView.scrollViewBottomSpacingToTabBar)
}

} refreshHandler: {

.listStyle(.plain)
}
}
.navigationBarBackButtonHidden(true)
Expand All @@ -95,6 +89,7 @@ struct AllYourFavoriteToolsView_Preview: PreviewProvider {
viewAllYourFavoritedToolsUseCase: appDiContainer.feature.favorites.domainLayer.getViewAllYourFavoritedToolsUseCase(),
getCurrentAppLanguageUseCase: appDiContainer.feature.appLanguage.domainLayer.getCurrentAppLanguageUseCase(),
getToolIsFavoritedUseCase: appDiContainer.feature.favorites.domainLayer.getToolIsFavoritedUseCase(),
reorderFavoritedToolUseCase: appDiContainer.feature.favorites.domainLayer.getReorderFavoritedToolUseCase(),
attachmentsRepository: appDiContainer.dataLayer.getAttachmentsRepository(),
trackScreenViewAnalyticsUseCase: appDiContainer.domainLayer.getTrackScreenViewAnalyticsUseCase(),
trackActionAnalyticsUseCase: appDiContainer.domainLayer.getTrackActionAnalyticsUseCase()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ class AllYourFavoriteToolsViewModel: ObservableObject {
private let viewAllYourFavoritedToolsUseCase: ViewAllYourFavoritedToolsUseCase
private let getCurrentAppLanguageUseCase: GetCurrentAppLanguageUseCase
private let getToolIsFavoritedUseCase: GetToolIsFavoritedUseCase
private let reorderFavoritedToolUseCase: ReorderFavoritedToolUseCase
private let attachmentsRepository: AttachmentsRepository
private let trackScreenViewAnalyticsUseCase: TrackScreenViewAnalyticsUseCase
private let trackActionAnalyticsUseCase: TrackActionAnalyticsUseCase
private let didConfirmToolRemovalSubject: PassthroughSubject<Void, Never> = PassthroughSubject()

private static var backgroundCancellables: Set<AnyCancellable> = Set()
private var cancellables: Set<AnyCancellable> = Set()

private weak var flowDelegate: FlowDelegate?
Expand All @@ -29,12 +31,13 @@ class AllYourFavoriteToolsViewModel: ObservableObject {
@Published var sectionTitle: String = ""
@Published var favoritedTools: [YourFavoritedToolDomainModel] = Array()

init(flowDelegate: FlowDelegate?, viewAllYourFavoritedToolsUseCase: ViewAllYourFavoritedToolsUseCase, getCurrentAppLanguageUseCase: GetCurrentAppLanguageUseCase, getToolIsFavoritedUseCase: GetToolIsFavoritedUseCase, attachmentsRepository: AttachmentsRepository, trackScreenViewAnalyticsUseCase: TrackScreenViewAnalyticsUseCase, trackActionAnalyticsUseCase: TrackActionAnalyticsUseCase) {
init(flowDelegate: FlowDelegate?, viewAllYourFavoritedToolsUseCase: ViewAllYourFavoritedToolsUseCase, getCurrentAppLanguageUseCase: GetCurrentAppLanguageUseCase, getToolIsFavoritedUseCase: GetToolIsFavoritedUseCase, reorderFavoritedToolUseCase: ReorderFavoritedToolUseCase, attachmentsRepository: AttachmentsRepository, trackScreenViewAnalyticsUseCase: TrackScreenViewAnalyticsUseCase, trackActionAnalyticsUseCase: TrackActionAnalyticsUseCase) {

self.flowDelegate = flowDelegate
self.viewAllYourFavoritedToolsUseCase = viewAllYourFavoritedToolsUseCase
self.getCurrentAppLanguageUseCase = getCurrentAppLanguageUseCase
self.getToolIsFavoritedUseCase = getToolIsFavoritedUseCase
self.reorderFavoritedToolUseCase = reorderFavoritedToolUseCase
self.attachmentsRepository = attachmentsRepository
self.trackScreenViewAnalyticsUseCase = trackScreenViewAnalyticsUseCase
self.trackActionAnalyticsUseCase = trackActionAnalyticsUseCase
Expand Down Expand Up @@ -190,4 +193,28 @@ extension AllYourFavoriteToolsViewModel {

flowDelegate?.navigate(step: .toolTappedFromAllYourFavoritedTools(tool: tool))
}

func toolMoved(fromOffsets source: IndexSet, toOffset destination: Int) {
for index in source {
guard index < favoritedTools.count else { continue }
let toolToMove = favoritedTools[index]

var newIndex: Int
if index < destination {
newIndex = destination - 1
} else {
newIndex = destination
}

reorderFavoritedToolUseCase
.reorderFavoritedToolPublisher(toolId: toolToMove.id, originalPosition: index, newPosition: newIndex)
.sink { _ in

} receiveValue: { _ in

}
.store(in: &AllYourFavoriteToolsViewModel.backgroundCancellables)

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class GetToolShortcutLinksRepository: GetToolShortcutLinksRepositoryInterface {

func getLinksPublisher(appLanguage: AppLanguageDomainModel) -> AnyPublisher<[ToolShortcutLinkDomainModel], Never> {

return favoritedResourcesRepository.getFavoritedResourcesSortedByCreatedAtPublisher(ascendingOrder: false)
return favoritedResourcesRepository.getFavoritedResourcesSortedByPositionPublisher(ascendingOrder: false)
Copy link
Collaborator

Choose a reason for hiding this comment

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

For tool shortcuts make sure it matches the favorited list order. Ascending defaults to true so you will want to remove this flag.

.flatMap({ (favoritedResources: [FavoritedResourceDataModel]) -> AnyPublisher<[ToolShortcutLinkDomainModel], Never> in

let toolShortcutLinks: [ToolShortcutLinkDomainModel] = favoritedResources
Expand Down
1 change: 1 addition & 0 deletions godtools/App/Flows/App/AppFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,7 @@ extension AppFlow {
viewAllYourFavoritedToolsUseCase: appDiContainer.feature.favorites.domainLayer.getViewAllYourFavoritedToolsUseCase(),
getCurrentAppLanguageUseCase: appDiContainer.feature.appLanguage.domainLayer.getCurrentAppLanguageUseCase(),
getToolIsFavoritedUseCase: appDiContainer.feature.favorites.domainLayer.getToolIsFavoritedUseCase(),
reorderFavoritedToolUseCase: appDiContainer.feature.favorites.domainLayer.getReorderFavoritedToolUseCase(),
attachmentsRepository: appDiContainer.dataLayer.getAttachmentsRepository(),
trackScreenViewAnalyticsUseCase: appDiContainer.domainLayer.getTrackScreenViewAnalyticsUseCase(),
trackActionAnalyticsUseCase: appDiContainer.domainLayer.getTrackActionAnalyticsUseCase()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@ class RealmFavoritedResource: Object {

@objc dynamic var createdAt: Date = Date()
@objc dynamic var resourceId: String = ""
@objc dynamic var position: Int = 0

override static func primaryKey() -> String? {
return "resourceId"
}

func mapFrom(dataModel: FavoritedResourceDataModel) {

createdAt = dataModel.createdAt
resourceId = dataModel.id
}
}
Loading
Loading