Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private extension FilesView {
}
}

if !viewModel.isRecycleBin, !viewModel.isOffline {
if showMoreActionsButton {
ToolbarItem(placement: .navigationBarTrailing) {
moreActionsButton
}
Expand All @@ -120,6 +120,10 @@ private extension FilesView {
}
}

var showMoreActionsButton: Bool {
!viewModel.isRecycleBin && !viewModel.isOffline && viewModel.selfUserRole == .editor
}

var closeButton: some View {
Button(
action: { onDismissContainer() },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ package final class FilesViewModel: ObservableObject {
// TODO: [WPB-25941] Remove drive permissions flag when feature is complete
var isDrivePermissionsFlagEnabled: Bool = UserDefaults.standard.bool(forKey: "enableDrivePermissions")

var selfUserRole: WireDriveConversation.Participant.Role {
selfUser?.role ?? .viewer
}

var navigationTitle: String {
if let title {
title
Expand All @@ -94,7 +98,7 @@ package final class FilesViewModel: ObservableObject {
}

var navigationSubtitle: String? {
if let selfUser, selfUser.role == .viewer, !isBrowsing, isDrivePermissionsFlagEnabled {
if selfUserRole == .viewer, !isBrowsing, isDrivePermissionsFlagEnabled {
Strings.Files.ViewerAccess.navigationSubtitle
} else {
nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ struct FilesItemView: View {
Strings.Files.Item.Menu.shareLink,
systemImage: "square.and.arrow.up"
)
}
}.disabled(viewModel.disableShareLinkButton)
}

menuItem(.makeAvailableOffline) { item in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@
isDrivePermissionsFlagEnabled && item.isReadOnly && isBrowsing
}

var disableShareLinkButton: Bool {

Check warning on line 83 in WireMessaging/Sources/WireMessagingUI/WireDrive/Components/Files/Item/FilesItemViewModel.swift

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Update this function so that its implementation is not identical to showReadOnlyIcon on line 79.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-ios&issues=AZ7L67XjP9Gzi5qzFYan&open=AZ7L67XjP9Gzi5qzFYan&pullRequest=4869
isDrivePermissionsFlagEnabled && item.isReadOnly && isBrowsing
}

struct TagsInfo {
let firstTag: String?
let additionalTagsIndicator: String?
Expand Down Expand Up @@ -243,6 +247,120 @@
}
#endif

var isOffline: Bool {
networkMonitor.currentStatus == .disconnected
}

var tagsInfo: TagsInfo {
let additionalTags = item.tags.count - 1
let formattedNumber: String? = if additionalTags > 0 {
additionalTagNumberFormatter.string(for: additionalTags) ?? "+\(additionalTags)"
} else {
nil
}
return .init(
firstTag: item.tags.sortedAlphabetically.first,
additionalTagsIndicator: formattedNumber
)
}

var menuActions: Set<ItemAction> {
let isViewerMode = item.isReadOnly && isDrivePermissionsFlagEnabled

if isViewerMode {
return viewerMenuActions
} else {
return editorMenuActions
}

}

private var viewerMenuActions: Set<ItemAction> {
var actions: Set<ItemAction> = []

if !isInRecycleBin {
actions.insert(.primaryAction)

if !isOffline, isBrowsing {
actions.insert(.shareLink)
}
}

if !isEditable, !isInRecycleBin, item.kind == .file {
if isAvailableOffline {
actions.insert(.removeAvailableOffline)
} else {
if !isOffline {
actions.insert(.makeAvailableOffline)
}
}
}

return actions
}

private var editorMenuActions: Set<ItemAction> {
var actions: Set<ItemAction> = []

if !isInRecycleBin {
actions.insert(.primaryAction)

if !isOffline {
actions.insert(.shareLink)
}
}

if !isEditable, !isInRecycleBin, item.kind == .file {
if isAvailableOffline {
actions.insert(.removeAvailableOffline)
} else {
if !isOffline {
actions.insert(.makeAvailableOffline)
}
}
}

if !isBrowsing, !isOffline {
if isInRecycleBin {
actions.formUnion([.restore, .deletePermanently])
} else {
if item.kind == .file, isEditable {
actions.insert(.showVersionHistory)
}
actions.formUnion([
.moveToFolder,
.rename,
.editTags,
.deleteToRecycleBin
])

if isEditable {
actions.insert(.edit)
}
}
}

return actions
}

var isAvailableOffline: Bool {
let isAvailableOffline = (try? localAssetRepository.asset(nodeID: nodeID)?.isAvailableOffline) ?? false
let isDownloaded = switch fileTracker.state {
case .loaded:
true
default:
false
}

let isFolder = item.kind == .folder

return isAvailableOffline && isDownloaded && !isFolder
Comment thread
jullianm marked this conversation as resolved.
}
}

// MARK: - Formatting

private extension FilesItemViewModel {
private static func subtitle(
selectedSortingKey: FilesSortingViewModel.SortingKey?,
isBrowsing: Bool,
Expand Down Expand Up @@ -361,80 +479,6 @@
}
}
}

var tagsInfo: TagsInfo {
let additionalTags = item.tags.count - 1
let formattedNumber: String? = if additionalTags > 0 {
additionalTagNumberFormatter.string(for: additionalTags) ?? "+\(additionalTags)"
} else {
nil
}
return .init(
firstTag: item.tags.sortedAlphabetically.first,
additionalTagsIndicator: formattedNumber
)
}

var isOffline: Bool {
networkMonitor.currentStatus == .disconnected
}

var menuActions: Set<ItemAction> {
var actions: Set<ItemAction> = []

if !isInRecycleBin {
actions.insert(.primaryAction)

if !isOffline {
actions.insert(.shareLink)
}
}

if !isEditable, !isInRecycleBin, item.kind == .file {
if isAvailableOffline {
actions.insert(.removeAvailableOffline)
} else {
if !isOffline {
actions.insert(.makeAvailableOffline)
}
}
}

if !isBrowsing, !isOffline {
if isInRecycleBin {
actions.insert(.restore)
actions.insert(.deletePermanently)
} else {
if item.kind == .file, isEditable {
actions.insert(.showVersionHistory)
}
actions.insert(.moveToFolder)
actions.insert(.rename)
actions.insert(.editTags)
actions.insert(.deleteToRecycleBin)

if isEditable {
actions.insert(.edit)
}
}
}

return actions
}

var isAvailableOffline: Bool {
let isAvailableOffline = (try? localAssetRepository.asset(nodeID: nodeID)?.isAvailableOffline) ?? false
let isDownloaded = switch fileTracker.state {
case .loaded:
true
default:
false
}

let isFolder = item.kind == .folder

return isAvailableOffline && isDownloaded && !isFolder
}
}

private extension [String] {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,7 @@ final class FilesViewTests: XCTestCase {
editingURLRepository: editingURLRepository
)

nodesApi.getDriveConversations_MockValue = [
.mocked(),
.mocked()
]
nodesApi.getDriveConversations_MockValue = .mocked(selfUserRole: .editor)

driveConversationsUseCase = WireDriveGetConversationsUseCase(nodesAPI: nodesApi)

Expand Down Expand Up @@ -317,7 +314,7 @@ final class FilesViewTests: XCTestCase {

@MainActor
func testFilesView_LoadingState() async {
let view = makeFilesView(state: .loading)
let view = await makeFilesView(state: .loading)

snapshotHelper
.withUserInterfaceStyle(.light)
Expand All @@ -329,7 +326,7 @@ final class FilesViewTests: XCTestCase {

@MainActor
func testFilesView_NoDataState() async {
let view = makeFilesView(state: .received(items: []))
let view = await makeFilesView(state: .received(items: []))

snapshotHelper
.withUserInterfaceStyle(.light)
Expand All @@ -341,7 +338,7 @@ final class FilesViewTests: XCTestCase {

@MainActor
func testFilesView_PendingState() async {
let view = makeFilesView(state: .pending)
let view = await makeFilesView(state: .pending)

snapshotHelper
.withUserInterfaceStyle(.light)
Expand All @@ -353,7 +350,7 @@ final class FilesViewTests: XCTestCase {

@MainActor
func testFilesView_ErrorState() async {
let view = makeFilesView(state: .error(isConnectionError: false))
let view = await makeFilesView(state: .error(isConnectionError: false))

snapshotHelper
.withUserInterfaceStyle(.light)
Expand All @@ -364,9 +361,16 @@ final class FilesViewTests: XCTestCase {
}

@MainActor
func testFilesView_ViewerOnlyBanner() async {
let view = makeFilesView(state: .received(items: []), isReadOnly: true)
func testFilesView_ViewerOnly() async {
// Given
let nodesApi = MockNodesAPIProtocol()
nodesApi.getDriveConversations_MockValue = .mocked(selfUserRole: .viewer)
driveConversationsUseCase = WireDriveGetConversationsUseCase(nodesAPI: nodesApi)

// When
let view = await makeFilesView(state: .received(items: []), isReadOnly: true)

// Then
snapshotHelper
.withUserInterfaceStyle(.light)
.verify(matching: view, named: "light", record: record)
Expand Down Expand Up @@ -405,7 +409,7 @@ final class FilesViewTests: XCTestCase {
state: FilesListStateController.State,
isBrowsing: Bool = false,
isReadOnly: Bool = false
) -> some View {
) async -> some View {
let filesViewModel = FilesViewModel(
useCases: .init(
fetchNodes: fetchNodesUseCase,
Expand Down Expand Up @@ -448,6 +452,8 @@ final class FilesViewTests: XCTestCase {
networkMonitor: networkMonitor
)

await filesViewModel.setup()

filesViewModel.filesController.state = state
filesViewModel.filesController.hasMore = false
filesViewModel.showReadOnlyBanner = isReadOnly
Expand Down
Loading