Skip to content

Commit 6b72924

Browse files
Merge pull request #534 from PermanentOrg/bugfix/VSP-1657-iOS-Update-archive-access-role
VSP-1657[iOS][Mobile]In share workspace, the user role should be updated when more options are displayed
2 parents f2c39e9 + 6a3d3ff commit 6b72924

File tree

3 files changed

+174
-18
lines changed

3 files changed

+174
-18
lines changed

Permanent/Modules/FileOperations/Views/FileMoreMenuView.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct FileMoreMenuView: View {
1515
private let onDeleteConfirmed: (([FileModel]) -> Void)?
1616
private let onLeaveShareConfirmed: ((FileModel) -> Void)?
1717

18-
init(fileViewModel: FileModel, menuItems: [FileMenuViewModel.MenuItem], selectedItemCount: Int? = nil, selectedFiles: [FileModel]? = nil, showArchiveInfo: Bool = false, onDismiss: @escaping () -> Void, onShareManagementRequested: ((FileModel) -> Void)? = nil, onGetLinkRequested: ((FileModel) -> Void)? = nil, onDeleteConfirmed: (([FileModel]) -> Void)? = nil, onLeaveShareConfirmed: ((FileModel) -> Void)? = nil, downloadHandler: FileMenuViewModel.DownloadHandler? = nil) {
18+
init(fileViewModel: FileModel, menuItems: [FileMenuViewModel.MenuItem], selectedItemCount: Int? = nil, selectedFiles: [FileModel]? = nil, showArchiveInfo: Bool = false, onDismiss: @escaping () -> Void, onShareManagementRequested: ((FileModel) -> Void)? = nil, onGetLinkRequested: ((FileModel) -> Void)? = nil, onDeleteConfirmed: (([FileModel]) -> Void)? = nil, onLeaveShareConfirmed: ((FileModel) -> Void)? = nil, downloadHandler: FileMenuViewModel.DownloadHandler? = nil, menuItemsGenerator: FileMenuViewModel.MenuItemsGenerator? = nil, fileModelUpdateHandler: FileMenuViewModel.FileModelUpdateHandler? = nil) {
1919
let newViewModel = FileMenuViewModel(
2020
fileViewModel: fileViewModel,
2121
menuItems: menuItems,
@@ -29,6 +29,14 @@ struct FileMoreMenuView: View {
2929
newViewModel.setDownloadHandler(downloadHandler)
3030
}
3131

32+
if let menuItemsGenerator = menuItemsGenerator {
33+
newViewModel.setMenuItemsGenerator(menuItemsGenerator)
34+
}
35+
36+
if let fileModelUpdateHandler = fileModelUpdateHandler {
37+
newViewModel.setFileModelUpdateHandler(fileModelUpdateHandler)
38+
}
39+
3240
self.viewModel = newViewModel
3341
self.onShareManagementRequested = onShareManagementRequested
3442
self.onGetLinkRequested = onGetLinkRequested
@@ -173,11 +181,11 @@ struct FileMoreMenuView: View {
173181

174182
Spacer(minLength: 0)
175183
}
176-
.frame(height: viewModel.preCalculatedHeight)
184+
.frame(height: viewModel.dynamicHeight)
177185
.frame(maxWidth: .infinity)
178186
.background(Color.white)
179187
.cornerRadius(16, corners: [.topLeft, .topRight])
180-
.offset(y: viewModel.isAnimating ? viewModel.dragOffset : viewModel.preCalculatedHeight)
188+
.offset(y: viewModel.isAnimating ? viewModel.dragOffset : viewModel.dynamicHeight)
181189
.gesture(
182190
DragGesture()
183191
.onChanged { value in
@@ -202,6 +210,7 @@ struct FileMoreMenuView: View {
202210
}
203211

204212
viewModel.prepareThumbnailForLoading()
213+
viewModel.fetchUpdatedAccessRole()
205214
viewModel.startPresentationAnimation()
206215
}
207216
.onChange(of: viewModel.specialMenuItemRequested) { menuItem in

Permanent/Modules/Shares/ViewController/SharesViewController.swift

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ class SharesViewController: BaseViewController<SharedFilesViewModel> {
690690
view.presentPopup(sortActionSheet, overlayView: overlayView)
691691
}
692692

693-
func showFileActionSheet(file: FileModel, atIndexPath indexPath: IndexPath) {
693+
private func generateMenuItems(for file: FileModel, atIndexPath indexPath: IndexPath) -> [FileMenuViewModel.MenuItem] {
694694
var menuItems: [FileMenuViewModel.MenuItem] = []
695695

696696
if file.permissions.contains(.share) {
@@ -739,6 +739,32 @@ class SharesViewController: BaseViewController<SharedFilesViewModel> {
739739
}))
740740
}
741741

742+
return menuItems
743+
}
744+
745+
private func updateFileModelInDataSource(_ updatedFile: FileModel) {
746+
guard let viewModel = self.viewModel else { return }
747+
748+
if viewModel.shareListType == .sharedByMe {
749+
if let index = viewModel.sharedByMeViewModels.firstIndex(where: { $0.recordId == updatedFile.recordId && $0.folderLinkId == updatedFile.folderLinkId }) {
750+
viewModel.sharedByMeViewModels[index] = updatedFile
751+
if let activeIndex = viewModel.viewModels.firstIndex(where: { $0.recordId == updatedFile.recordId && $0.folderLinkId == updatedFile.folderLinkId }) {
752+
viewModel.viewModels[activeIndex] = updatedFile
753+
}
754+
}
755+
} else {
756+
if let index = viewModel.sharedWithMeViewModels.firstIndex(where: { $0.recordId == updatedFile.recordId && $0.folderLinkId == updatedFile.folderLinkId }) {
757+
viewModel.sharedWithMeViewModels[index] = updatedFile
758+
if let activeIndex = viewModel.viewModels.firstIndex(where: { $0.recordId == updatedFile.recordId && $0.folderLinkId == updatedFile.folderLinkId }) {
759+
viewModel.viewModels[activeIndex] = updatedFile
760+
}
761+
}
762+
}
763+
}
764+
765+
func showFileActionSheet(file: FileModel, atIndexPath indexPath: IndexPath) {
766+
let menuItems = generateMenuItems(for: file, atIndexPath: indexPath)
767+
742768
// Determine if we should show archive info (only in Shared With Me tab, and at root level)
743769
let isSharedWithMe = viewModel?.shareListType == .sharedWithMe
744770
let isAtRootLevel = viewModel?.currentFolderIsRoot ?? true
@@ -752,13 +778,11 @@ class SharesViewController: BaseViewController<SharedFilesViewModel> {
752778
self?.dismiss(animated: true)
753779
},
754780
onShareManagementRequested: { [weak self] file in
755-
// Dismiss the menu first, then present ShareManagement
756781
self?.dismiss(animated: true, completion: {
757782
self?.presentShareManagement(for: file)
758783
})
759784
},
760785
onGetLinkRequested: { [weak self] file in
761-
// Dismiss the menu first, then handle get link
762786
self?.dismiss(animated: true, completion: {
763787
self?.getShareLinkAction(file: file)
764788
})
@@ -814,6 +838,13 @@ class SharesViewController: BaseViewController<SharedFilesViewModel> {
814838
},
815839
progressHandler: nil
816840
)
841+
},
842+
menuItemsGenerator: { [weak self] updatedFile in
843+
guard let self = self else { return [] }
844+
return self.generateMenuItems(for: updatedFile, atIndexPath: indexPath)
845+
},
846+
fileModelUpdateHandler: { [weak self] (updatedFile: FileModel) in
847+
self?.updateFileModelInDataSource(updatedFile)
817848
}
818849
)
819850

@@ -891,13 +922,11 @@ class SharesViewController: BaseViewController<SharedFilesViewModel> {
891922
self?.dismiss(animated: true)
892923
},
893924
onShareManagementRequested: { [weak self] file in
894-
// Dismiss the menu first, then present ShareManagement
895925
self?.dismiss(animated: true, completion: {
896926
self?.presentShareManagement(for: file)
897927
})
898928
},
899929
onGetLinkRequested: { [weak self] file in
900-
// Dismiss the menu first, then handle get link
901930
self?.dismiss(animated: true, completion: {
902931
self?.getShareLinkAction(file: file)
903932
})
@@ -956,6 +985,10 @@ class SharesViewController: BaseViewController<SharedFilesViewModel> {
956985
},
957986
progressHandler: nil
958987
)
988+
},
989+
fileModelUpdateHandler: { [weak self] (updatedFile: FileModel) in
990+
// Update the file in the data source when its role/permissions change
991+
self?.updateFileModelInDataSource(updatedFile)
959992
}
960993
)
961994

@@ -1198,7 +1231,8 @@ class SharesViewController: BaseViewController<SharedFilesViewModel> {
11981231
self.refreshCollectionView()
11991232
} else {
12001233
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: [])
1201-
showFileActionSheet(file: file, atIndexPath: indexPath)
1234+
let currentFile = viewModel?.viewModels[indexPath.row] ?? file
1235+
showFileActionSheet(file: currentFile, atIndexPath: indexPath)
12021236
}
12031237

12041238
case .downloading:

Permanent/ViewModels/ViewModel/FileMenuViewModel.swift

Lines changed: 122 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ class FileMenuViewModel: ObservableObject {
5555
@Published var pendingLeaveShareAction: (() -> Void)?
5656
@Published var isExecutingAction: Bool = false
5757

58+
@Published var dynamicMenuItems: [MenuItem] = []
59+
@Published var dynamicHeight: CGFloat = 0
60+
5861
// MARK: - Input Properties
59-
let fileViewModel: FileModel
62+
var fileViewModel: FileModel
6063
let menuItems: [MenuItem]
6164
let selectedItemCount: Int?
6265
let selectedFiles: [FileModel]?
@@ -68,7 +71,6 @@ class FileMenuViewModel: ObservableObject {
6871
let cachedFormattedDate: String
6972
let preCalculatedHeight: CGFloat
7073
let archiveName: String?
71-
let accessRoleName: String?
7274

7375
// MARK: - Private Properties
7476
private var dragStartTime: Date = Date()
@@ -80,7 +82,11 @@ class FileMenuViewModel: ObservableObject {
8082

8183
// MARK: - Dependencies
8284
typealias DownloadHandler = (FileModel, @escaping (URL?, Error?) -> Void) -> Void
85+
typealias MenuItemsGenerator = (FileModel) -> [MenuItem]
86+
typealias FileModelUpdateHandler = (FileModel) -> Void
8387
private var downloadHandler: DownloadHandler?
88+
private var menuItemsGenerator: MenuItemsGenerator?
89+
private var fileModelUpdateHandler: FileModelUpdateHandler?
8490
var viewControllerProvider: (() -> UIViewController?)?
8591
private var capturedPresentingViewController: UIViewController?
8692

@@ -119,13 +125,15 @@ class FileMenuViewModel: ObservableObject {
119125

120126
if showArchiveInfo, let sharedByArchive = fileViewModel.sharedByArchive {
121127
self.archiveName = sharedByArchive.name
122-
self.accessRoleName = fileViewModel.accessRole.title.uppercased()
123128
} else {
124129
self.archiveName = nil
125-
self.accessRoleName = nil
126130
}
127131

128132
self.preCalculatedHeight = Self.calculateSheetHeight(for: menuItems, showArchiveInfo: showArchiveInfo && fileViewModel.sharedByArchive != nil)
133+
134+
// Initialize dynamic properties
135+
self.dynamicMenuItems = menuItems
136+
self.dynamicHeight = self.preCalculatedHeight
129137
}
130138

131139
deinit {
@@ -207,15 +215,15 @@ class FileMenuViewModel: ObservableObject {
207215
}
208216

209217
var backgroundOpacity: Double {
210-
isAnimating ? max(0.0, 0.3 * (1.0 - Double(dragOffset / preCalculatedHeight))) : 0.0
218+
isAnimating ? max(0.0, 0.3 * (1.0 - Double(dragOffset / dynamicHeight))) : 0.0
211219
}
212220

213221
var regularMenuItems: [MenuItem] {
214-
return menuItems.filter { $0.type != .delete && $0.type != .unshare }
222+
return dynamicMenuItems.filter { $0.type != .delete && $0.type != .unshare }
215223
}
216224

217225
var destructiveMenuItem: MenuItem? {
218-
return menuItems.first { $0.type == .delete || $0.type == .unshare }
226+
return dynamicMenuItems.first { $0.type == .delete || $0.type == .unshare }
219227
}
220228

221229
var displayTitle: String {
@@ -226,6 +234,103 @@ class FileMenuViewModel: ObservableObject {
226234
}
227235
}
228236

237+
var accessRoleName: String? {
238+
guard showArchiveInfo, fileViewModel.sharedByArchive != nil else {
239+
return nil
240+
}
241+
return fileViewModel.accessRole.title.uppercased()
242+
}
243+
244+
// MARK: - Access Role Update
245+
func fetchUpdatedAccessRole() {
246+
guard showArchiveInfo, fileViewModel.sharedByArchive != nil else {
247+
return
248+
}
249+
250+
let itemInfo = (folderLinkId: fileViewModel.folderLinkId, parentFolderLinkId: fileViewModel.parentFolderLinkId)
251+
252+
let endpoint: FilesEndpoint
253+
if fileViewModel.type.isFolder {
254+
endpoint = FilesEndpoint.getFolder(itemInfo: itemInfo)
255+
} else {
256+
endpoint = FilesEndpoint.getRecord(itemInfo: itemInfo)
257+
}
258+
259+
let apiOperation = APIOperation(endpoint)
260+
261+
apiOperation.execute(in: APIRequestDispatcher()) { [weak self] result in
262+
guard let self = self else { return }
263+
264+
DispatchQueue.main.async {
265+
switch result {
266+
case .json(let response, _):
267+
if self.fileViewModel.type.isFolder {
268+
guard let model: APIResults<FolderVO> = JSONHelper.decoding(
269+
from: response,
270+
with: APIResults<FolderVO>.decoder
271+
),
272+
model.isSuccessful,
273+
let folderData = model.results.first?.data?.first?.folderVO,
274+
let accessRoleString = folderData.accessRole else {
275+
return
276+
}
277+
278+
let newAccessRole = AccessRole.roleForValue(accessRoleString)
279+
if self.fileViewModel.accessRole != newAccessRole {
280+
self.fileViewModel.accessRole = newAccessRole
281+
self.updatePermissions(forAccessRole: accessRoleString)
282+
self.regenerateMenuItemsAndAnimateHeight()
283+
}
284+
} else {
285+
guard let model: APIResults<RecordVO> = JSONHelper.decoding(
286+
from: response,
287+
with: APIResults<RecordVO>.decoder
288+
),
289+
model.isSuccessful,
290+
let recordData = model.results.first?.data?.first?.recordVO,
291+
let accessRoleString = recordData.accessRole else {
292+
return
293+
}
294+
295+
let newAccessRole = AccessRole.roleForValue(accessRoleString)
296+
if self.fileViewModel.accessRole != newAccessRole {
297+
self.fileViewModel.accessRole = newAccessRole
298+
self.updatePermissions(forAccessRole: accessRoleString)
299+
self.regenerateMenuItemsAndAnimateHeight()
300+
}
301+
}
302+
303+
case .error:
304+
break
305+
306+
default:
307+
break
308+
}
309+
}
310+
}
311+
}
312+
313+
private func updatePermissions(forAccessRole accessRoleString: String) {
314+
let newPermissions = ArchiveVOData.permissions(forAccessRole: accessRoleString)
315+
self.fileViewModel.permissions = newPermissions
316+
}
317+
318+
private func regenerateMenuItemsAndAnimateHeight() {
319+
fileModelUpdateHandler?(fileViewModel)
320+
321+
guard let generator = menuItemsGenerator else {
322+
return
323+
}
324+
325+
let newMenuItems = generator(fileViewModel)
326+
let newHeight = Self.calculateSheetHeight(for: newMenuItems, showArchiveInfo: showArchiveInfo && fileViewModel.sharedByArchive != nil)
327+
328+
withAnimation(.easeInOut(duration: 0.3)) {
329+
self.dynamicMenuItems = newMenuItems
330+
self.dynamicHeight = newHeight
331+
}
332+
}
333+
229334
// MARK: - Animation Control
230335
func startPresentationAnimation() {
231336
withAnimation(.easeOut(duration: 0.3)) {
@@ -296,10 +401,18 @@ class FileMenuViewModel: ObservableObject {
296401
downloadHandler = handler
297402
}
298403

404+
func setMenuItemsGenerator(_ generator: @escaping MenuItemsGenerator) {
405+
menuItemsGenerator = generator
406+
}
407+
408+
func setFileModelUpdateHandler(_ handler: @escaping FileModelUpdateHandler) {
409+
fileModelUpdateHandler = handler
410+
}
411+
299412
func dismissWithAnimation() {
300413
withAnimation(.easeIn(duration: 0.25)) {
301414
isAnimating = false
302-
dragOffset = preCalculatedHeight
415+
dragOffset = dynamicHeight
303416
}
304417

305418
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
@@ -326,7 +439,7 @@ class FileMenuViewModel: ObservableObject {
326439

327440
func handleDragEnded(_ value: DragGesture.Value) {
328441
isDragging = false
329-
let threshold = preCalculatedHeight * 0.3
442+
let threshold = dynamicHeight * 0.3
330443

331444
if value.translation.height > threshold || value.predictedEndTranslation.height > threshold {
332445
dismissWithAnimation()

0 commit comments

Comments
 (0)