Skip to content

Commit 7c7f32b

Browse files
jullianmcaldrian
andauthored
feat: implement all files view + search - WPB-21030 (#3740)
Co-authored-by: Christoph Aldrian <[email protected]>
1 parent 70652a4 commit 7c7f32b

File tree

59 files changed

+863
-326
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+863
-326
lines changed

WireDomain/Sources/WireDomain/Repositories/FeatureConfig/Models/FeatureState.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ import WireDataModel
2121
/// The state of the feature
2222

2323
public struct FeatureState {
24-
let name: Feature.Name
25-
let isEnabled: Bool
24+
public let name: Feature.Name
25+
public let isEnabled: Bool
2626
}

WireMessaging/Sources/WireMessagingAssembly/WireMessagingFactory.swift

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,40 @@ public struct WireMessagingFactory {
129129
public extension WireMessagingFactory {
130130

131131
@MainActor
132-
func makeFilesView(cellName: String, isCellsStatePending: Bool, nodeIDs: [UUID]) -> UIViewController {
133-
let configuration: WireCellsFetchNodesUseCase.Configuration =
134-
nodeIDs.isEmpty ? .conversationFileView(root: .path(cellName)) : .nodesFileView(nodeIDs: nodeIDs)
132+
func makeFilesView(
133+
cellName: String,
134+
isCellsStatePending: Bool,
135+
nodeIDs: [UUID]
136+
) -> UIViewController {
137+
let configuration: WireCellsFetchNodesUseCase.Configuration = nodeIDs
138+
.isEmpty ? .conversationFileView(root: .path(cellName)) :
139+
.nodesFileView(nodeIDs: nodeIDs)
140+
141+
let filesView: UIHostingController<FilesView>
142+
143+
filesView = makeFilesHostingController(
144+
configuration: configuration,
145+
isCellsStatePending: isCellsStatePending
146+
)
147+
148+
return filesView
149+
}
135150

151+
@MainActor
152+
func makeFilesBrowserView() -> UIViewController {
153+
let configuration: WireCellsFetchNodesUseCase.Configuration = .filesBrowserView()
154+
let filesBrowserView: UIHostingController<FilesBrowserView>
155+
filesBrowserView = makeFilesHostingController(
156+
configuration: configuration
157+
)
158+
return filesBrowserView
159+
}
160+
161+
@MainActor
162+
private func makeFilesHostingController<T: FilesViewProtocol>(
163+
configuration: WireCellsFetchNodesUseCase.Configuration,
164+
isCellsStatePending: Bool = false
165+
) -> UIHostingController<T> {
136166
let viewModel = FilesViewModel(
137167
fetchNodesUseCase: WireCellsFetchNodesUseCase(
138168
configuration: configuration,
@@ -148,9 +178,7 @@ public extension WireMessagingFactory {
148178
fileCache: fileCache
149179
)
150180

151-
return FilesHostingController(
152-
viewModel: viewModel
153-
)
181+
return UIHostingController(rootView: T(viewModel: viewModel))
154182
}
155183

156184
@MainActor

WireMessaging/Sources/WireMessagingDomain/WireCells/UseCases/WireCellsFetchNodesUseCase.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ package struct WireCellsFetchNodesUseCase: Sendable {
4343
let pageSize: Int = 30
4444

4545
/// A `Configuration` suitable for the conversation file view.
46-
package static func conversationFileView(root: WireCellsNodeLocator) -> Configuration {
46+
package static func conversationFileView(root: WireCellsNodeLocator?) -> Configuration {
4747
Configuration(
4848
root: root,
4949
nodeIDs: nil,
@@ -53,6 +53,17 @@ package struct WireCellsFetchNodesUseCase: Sendable {
5353
)
5454
}
5555

56+
/// A `Configuration` suitable for the files browser view.
57+
package static func filesBrowserView() -> Configuration {
58+
Configuration(
59+
root: nil,
60+
nodeIDs: nil,
61+
isRecursive: true,
62+
nodeType: .leaf,
63+
deletionStatus: .notDeleted
64+
)
65+
}
66+
5667
/// A `Configuration` for showing only specific nodes in the file view.
5768
package static func nodesFileView(nodeIDs: [UUID]) -> Configuration {
5869
Configuration(

WireMessaging/Sources/WireMessagingUI/ConversationChannelCreationForm/ConversationChannelCreationFormView.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ public struct ConversationChannelCreationForm: View {
199199
Section(content: {
200200
Toggle(Strings.CreationForm.WireCells.toggle + " (Cells beta)", isOn: $viewModel.fileManagementEnabled)
201201
}, footer: {
202-
Text(Strings.CreationForm.WireCells.description) +
203-
Text(" [\(Strings.CreationForm.WireCells.learnMore)](https://wire.com)") // TODO: [WPB-16736] URL to be defined
202+
Text(Strings.CreationForm.WireCells.description)
203+
// Text(" [\(Strings.CreationForm.WireCells.learnMore)](https://wire.com)") // TODO: [WPB-20191] URL to be
204+
// defined, uncomment when we have the URL
204205
})
205206
}
206207
}

WireMessaging/Sources/WireMessagingUI/Resources/Localization/en.lproj/Accessibility.strings

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@
2626
"conversation.wireCells.files.noData.message" = "You'll find all files and folders shared in this conversation here";
2727
"conversation.wireCells.files.pendingCells.title" = "Files are loading";
2828
"conversation.wireCells.files.pendingCells.message" = "Your documents and media files will be available here once we've uploaded all files and folders.";
29+
"conversation.wireCells.allFiles.noData.message" = "You'll find all files and folders shared across your conversations here.";
30+
"conversation.wireCells.files.error.title" = "Can't load files";
31+
"conversation.wireCells.files.error.message" = "Your files and folders could not be loaded. Please reload.";
32+
"conversation.wireCells.files.error.reload" = "Reload";
2933

WireMessaging/Sources/WireMessagingUI/Resources/Localization/en.lproj/Localizable.strings

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"conversation.creationForm.readReceipts.toggle" = "Read receipts";
7878
"conversation.creationForm.readReceipts.description" = "When this is on, people can see when their messages in this conversation are read.";
7979

80-
"conversation.creationForm.wireCells.toggle" = "File management";
80+
"conversation.creationForm.wireCells.toggle" = "File collaboration";
8181
"conversation.creationForm.wireCells.description" = "Enable participants to manage their documents and media files. Use folders, tags and filters to work more efficiently. This can't be undone.";
8282
"conversation.creationForm.wireCells.learnMore" = "Learn more";
8383

@@ -117,9 +117,14 @@
117117
"conversation.wireCells.files.item.deleteConfirmation.title" = "This will permanently delete the file %@ for all participants. Everyone will lose access and there is no way to recover the file.";
118118
"conversation.wireCells.files.item.deleteConfirmation.deletePermanently" = "Delete Permanently";
119119
"conversation.wireCells.files.noData.title" = "There are no files or folders yet";
120-
"conversation.wireCells.files.noData.message" = "You'll find all files and folders shared in this conversation here";
120+
"conversation.wireCells.files.noData.message" = "You'll find all files and folders shared in this conversation here.";
121+
"conversation.wireCells.allFiles.noData.message" = "You'll find all files and folders shared across your conversations here.";
121122
"conversation.wireCells.files.pendingCells.title" = "Files are loading";
122123
"conversation.wireCells.files.pendingCells.message" = "Your documents and media files will be available here once we've uploaded all files and folders.";
124+
"conversation.wireCells.files.error.title" = "Can't load files";
125+
"conversation.wireCells.files.error.message" = "Your files and folders could not be loaded. Please reload.";
126+
"conversation.wireCells.files.error.reload" = "Reload";
127+
"conversation.wireCells.files.search.title" = "Search files";
123128

124129
// MARK: - Conversation
125130
"conversation.message.attachment.notAvailable" = "File not available";
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//
2+
// Wire
3+
// Copyright (C) 2025 Wire Swiss GmbH
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with this program. If not, see http://www.gnu.org/licenses/.
17+
//
18+
19+
import Combine
20+
import QuickLook
21+
package import SwiftUI
22+
import WireDesign
23+
import WireFoundation
24+
import WireMessagingDomain
25+
import WireReusableUIComponents
26+
27+
private typealias Strings = L10n.Localizable.Conversation.WireCells
28+
29+
/// Allows browsing files shared across all conversations
30+
package struct FilesBrowserView: FilesViewProtocol {
31+
@ObservedObject package var viewModel: FilesViewModel
32+
33+
package init(viewModel: FilesViewModel) {
34+
self.viewModel = viewModel
35+
}
36+
37+
package var body: some View {
38+
ZStack {
39+
ColorTheme.Backgrounds.surface.color
40+
.ignoresSafeArea(.all)
41+
Group {
42+
switch viewModel.state {
43+
case .initial:
44+
Button(action: reloadTask) {
45+
Image(systemName: "arrow.trianglehead.clockwise")
46+
.wireTextStyle(.body3)
47+
.foregroundStyle(SemanticColors.Label.textDefault.color)
48+
49+
}
50+
case .loading:
51+
ProgressView()
52+
.progressViewStyle(.circular)
53+
case let .received(items):
54+
if items.isEmpty {
55+
FilesInfoView(info: .noFilesFound(scope: .allConversations))
56+
} else {
57+
filesList
58+
.listStyle(.plain)
59+
.refreshable { reloadTask() }
60+
}
61+
case .pending:
62+
FilesInfoView(info: .preparingFiles)
63+
case .error:
64+
FilesInfoView(info: .error, onReload: {
65+
reloadTask()
66+
})
67+
}
68+
}
69+
.quickLookPreview($viewModel.viewingURL) // TODO: [WPB-19395] Temporary implementation
70+
.navigationTitle(Strings.AllFiles.navigationTitle)
71+
.navigationBarTitleDisplayMode(.inline)
72+
.toolbarBackground(.visible, for: .navigationBar) // shows navigation bar divider
73+
.toolbarBackground(ColorTheme.Backgrounds.surface.color, for: .navigationBar)
74+
.if(!viewModel.state.items.isEmpty || !viewModel.searchText.isEmpty) { view in
75+
view.searchable(
76+
text: $viewModel.searchText,
77+
placement: .navigationBarDrawer,
78+
prompt: Strings.Files.Search.title
79+
)
80+
}
81+
.onAppear { reloadTask() }
82+
.alert(
83+
item: $viewModel.alert,
84+
title: { Text($0.title) },
85+
message: { Text($0.message) },
86+
actions: { _ in confirmButton }
87+
)
88+
}
89+
}
90+
}
91+
92+
// MARK: - Helper
93+
94+
private extension View {
95+
@ViewBuilder
96+
func `if`(
97+
_ condition: Bool,
98+
transform: (Self) -> some View
99+
) -> some View {
100+
if condition {
101+
transform(self)
102+
} else {
103+
self
104+
}
105+
}
106+
}
107+
108+
#Preview {
109+
NavigationStack {
110+
FilesBrowserView(viewModel: .preview())
111+
.environment(\.wireTextStyleMapping, WireTextStyleMapping())
112+
}
113+
}

WireMessaging/Sources/WireMessagingUI/WireCells/Components/Files/FilesHostingController.swift

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)