Skip to content

Commit 2c537b6

Browse files
implemented uploading custom attachments
1 parent cc51110 commit 2c537b6

File tree

1,101 files changed

+58539
-17
lines changed

Some content is hidden

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

1,101 files changed

+58539
-17
lines changed

DemoAppSwiftUI/AppDelegate.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,14 @@ class AppDelegate: NSObject, UIApplicationDelegate {
8282
*/
8383

8484
/*
85-
let messageTypeResolver = CustomMessageResolver()
86-
let utils = Utils(messageTypeResolver: messageTypeResolver)
85+
let messageTypeResolver = CustomMessageTypeResolver()
86+
let utils = Utils(messageTypeResolver: messageTypeResolver)
8787

88-
streamChat = StreamChat(chatClient: chatClient, utils: utils)
89-
*/
88+
streamChat = StreamChat(chatClient: chatClient, utils: utils)
89+
*/
9090

9191
streamChat = StreamChat(chatClient: chatClient)
92-
92+
9393
chatClient.connectUser(
9494
userInfo: .init(id: credentials.id, name: credentials.name, imageURL: credentials.avatarURL),
9595
token: token
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
//
2+
// Copyright © 2021 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
import StreamChat
7+
import SwiftUI
8+
import StreamChatSwiftUI
9+
10+
extension AttachmentType {
11+
static let contact = Self(rawValue: "contact")
12+
}
13+
14+
struct ContactAttachmentPayload: AttachmentPayload {
15+
static let type: AttachmentType = .contact
16+
17+
let name: String
18+
let phoneNumber: String
19+
}
20+
21+
typealias ContactAttachment = ChatMessageAttachment<ContactAttachmentPayload>
22+
23+
extension ContactAttachmentPayload: Identifiable {
24+
25+
var id: String {
26+
"\(name)-\(phoneNumber)"
27+
}
28+
29+
}
30+
31+
class CustomAttachmentsFactory: ViewFactory {
32+
33+
@Injected(\.chatClient) var chatClient: ChatClient
34+
35+
private let mockContacts = [
36+
CustomAttachment(
37+
id: "123",
38+
content: AnyAttachmentPayload(payload: ContactAttachmentPayload(name: "Test 1", phoneNumber: "071234234232"))
39+
),
40+
CustomAttachment(
41+
id: "124",
42+
content: AnyAttachmentPayload(payload: ContactAttachmentPayload(name: "Test 2", phoneNumber: "4323243423432"))
43+
),
44+
CustomAttachment(
45+
id: "125",
46+
content: AnyAttachmentPayload(payload: ContactAttachmentPayload(name: "Test 3", phoneNumber: "75756756756756"))
47+
),
48+
CustomAttachment(
49+
id: "126",
50+
content: AnyAttachmentPayload(payload: ContactAttachmentPayload(name: "Test 4", phoneNumber: "534543674576565"))
51+
),
52+
CustomAttachment(
53+
id: "127",
54+
content: AnyAttachmentPayload(payload: ContactAttachmentPayload(name: "Test 5", phoneNumber: "534534543543534"))
55+
)
56+
57+
]
58+
59+
func makeAttachmentSourcePickerView(
60+
selected: AttachmentPickerState,
61+
onPickerStateChange: @escaping (AttachmentPickerState) -> Void
62+
) -> some View {
63+
CustomAttachmentSourcePickerView(
64+
selected: selected,
65+
onTap: onPickerStateChange
66+
)
67+
}
68+
69+
func makeCustomAttachmentView(
70+
addedCustomAttachments: [CustomAttachment],
71+
onCustomAttachmentTap: @escaping (CustomAttachment) -> Void
72+
) -> some View {
73+
CustomContactAttachmentView(
74+
contacts: mockContacts,
75+
addedContacts: addedCustomAttachments,
76+
onCustomAttachmentTap: onCustomAttachmentTap
77+
)
78+
}
79+
80+
func makeCustomAttachmentViewType(
81+
for message: ChatMessage,
82+
isFirst: Bool,
83+
availableWidth: CGFloat
84+
) -> some View {
85+
let contactAttachments = message.attachments(payloadType: ContactAttachmentPayload.self)
86+
return VStack {
87+
ForEach(0..<contactAttachments.count) { i in
88+
let contact = contactAttachments[i]
89+
CustomContactAttachmentPreview(
90+
contact: CustomAttachment(
91+
id: "\(message.id)-\(i)",
92+
content: AnyAttachmentPayload(payload: contact.payload)
93+
),
94+
payload: contact.payload,
95+
onCustomAttachmentTap: { _ in },
96+
isAttachmentSelected: false,
97+
hasSpacing: false
98+
)
99+
.standardPadding()
100+
}
101+
.messageBubble(for: message, isFirst: true)
102+
}
103+
}
104+
105+
func makeCustomAttachmentPreviewView(
106+
addedCustomAttachments: [CustomAttachment],
107+
onCustomAttachmentTap: @escaping (CustomAttachment) -> Void
108+
) -> some View {
109+
CustomContactAttachmentComposerPreview(
110+
addedCustomAttachments: addedCustomAttachments,
111+
onCustomAttachmentTap: onCustomAttachmentTap
112+
)
113+
}
114+
115+
}
116+
117+
class CustomMessageTypeResolver: MessageTypeResolving {
118+
119+
func hasCustomAttachment(message: ChatMessage) -> Bool {
120+
let contactAttachments = message.attachments(payloadType: ContactAttachmentPayload.self)
121+
return contactAttachments.count > 0
122+
}
123+
124+
}
125+
126+
struct CustomAttachmentSourcePickerView: View {
127+
128+
@Injected(\.colors) var colors
129+
130+
var selected: AttachmentPickerState
131+
var onTap: (AttachmentPickerState) -> Void
132+
133+
var body: some View {
134+
HStack(alignment: .center, spacing: 24) {
135+
AttachmentPickerButton(
136+
iconName: "photo",
137+
pickerType: .photos,
138+
isSelected: selected == .photos,
139+
onTap: onTap
140+
)
141+
142+
AttachmentPickerButton(
143+
iconName: "folder",
144+
pickerType: .files,
145+
isSelected: selected == .files,
146+
onTap: onTap
147+
)
148+
149+
AttachmentPickerButton(
150+
iconName: "camera",
151+
pickerType: .camera,
152+
isSelected: selected == .camera,
153+
onTap: onTap
154+
)
155+
156+
AttachmentPickerButton(
157+
iconName: "person.crop.circle",
158+
pickerType: .custom,
159+
isSelected: selected == .custom,
160+
onTap: onTap
161+
)
162+
163+
Spacer()
164+
}
165+
.padding(.horizontal, 16)
166+
.frame(height: 56)
167+
.background(Color(colors.background1))
168+
}
169+
170+
}
171+
172+
struct CustomContactAttachmentView: View {
173+
174+
@Injected(\.fonts) var fonts
175+
@Injected(\.colors) var colors
176+
177+
let contacts: [CustomAttachment]
178+
let addedContacts: [CustomAttachment]
179+
var onCustomAttachmentTap: (CustomAttachment) -> Void
180+
181+
var body: some View {
182+
AttachmentTypeContainer {
183+
VStack(alignment: .leading) {
184+
Text("Contacts")
185+
.font(fonts.headlineBold)
186+
.standardPadding()
187+
188+
ScrollView {
189+
VStack {
190+
ForEach(contacts) { contact in
191+
if let payload = contact.content.payload as? ContactAttachmentPayload {
192+
CustomContactAttachmentPreview(
193+
contact: contact,
194+
payload: payload,
195+
onCustomAttachmentTap: onCustomAttachmentTap,
196+
isAttachmentSelected: addedContacts.contains(contact)
197+
)
198+
.padding(.all, 4)
199+
.padding(.horizontal, 8)
200+
}
201+
}
202+
}
203+
.frame(maxWidth: .infinity)
204+
}
205+
}
206+
}
207+
}
208+
209+
}
210+
211+
struct CustomContactAttachmentComposerPreview: View {
212+
213+
var addedCustomAttachments: [CustomAttachment]
214+
var onCustomAttachmentTap: (CustomAttachment) -> Void
215+
216+
var body: some View {
217+
VStack {
218+
ForEach(addedCustomAttachments) { contact in
219+
if let payload = contact.content.payload as? ContactAttachmentPayload {
220+
HStack {
221+
CustomContactAttachmentPreview(
222+
contact: contact,
223+
payload: payload,
224+
onCustomAttachmentTap: onCustomAttachmentTap,
225+
isAttachmentSelected: false
226+
)
227+
.padding(.leading, 8)
228+
229+
Spacer()
230+
231+
DiscardAttachmentButton(
232+
attachmentIdentifier: payload.id,
233+
onDiscard: { _ in
234+
onCustomAttachmentTap(contact)
235+
}
236+
)
237+
}
238+
.padding(.all, 4)
239+
.roundWithBorder()
240+
.padding(.all, 2)
241+
}
242+
}
243+
}
244+
}
245+
246+
}
247+
248+
struct CustomContactAttachmentPreview: View {
249+
250+
@Injected(\.fonts) var fonts
251+
@Injected(\.colors) var colors
252+
253+
let contact: CustomAttachment
254+
let payload: ContactAttachmentPayload
255+
var onCustomAttachmentTap: (CustomAttachment) -> Void
256+
var isAttachmentSelected: Bool
257+
var hasSpacing = true
258+
259+
var body: some View {
260+
Button {
261+
withAnimation {
262+
onCustomAttachmentTap(contact)
263+
}
264+
} label: {
265+
HStack {
266+
Image(systemName: "person.crop.circle")
267+
.renderingMode(.template)
268+
.foregroundColor(Color(colors.textLowEmphasis))
269+
270+
VStack(alignment: .leading) {
271+
Text(payload.name)
272+
.font(fonts.bodyBold)
273+
.foregroundColor(Color(colors.text))
274+
Text(payload.phoneNumber)
275+
.font(fonts.footnote)
276+
.foregroundColor(Color(colors.textLowEmphasis))
277+
}
278+
279+
if hasSpacing {
280+
Spacer()
281+
}
282+
283+
if isAttachmentSelected {
284+
Image(systemName: "checkmark")
285+
.renderingMode(.template)
286+
.foregroundColor(Color(colors.textLowEmphasis))
287+
}
288+
}
289+
290+
}
291+
}
292+
293+
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/AttachmentPickerView.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import Photos
66
import SwiftUI
7+
import StreamChat
78

89
/// View for the attachment picker.
910
public struct AttachmentPickerView<Factory: ViewFactory>: View {
@@ -18,7 +19,9 @@ public struct AttachmentPickerView<Factory: ViewFactory>: View {
1819
var onPickerStateChange: (AttachmentPickerState) -> Void
1920
var photoLibraryAssets: PHFetchResult<PHAsset>?
2021
var onAssetTap: (AddedAsset) -> Void
22+
var onCustomAttachmentTap: (CustomAttachment) -> Void
2123
var isAssetSelected: (String) -> Bool
24+
var addedCustomAttachments: [CustomAttachment]
2225
var cameraImageAdded: (AddedAsset) -> Void
2326
var askForAssetsAccessPermissions: () -> Void
2427

@@ -49,12 +52,17 @@ public struct AttachmentPickerView<Factory: ViewFactory>: View {
4952
filePickerShown: $filePickerShown,
5053
addedFileURLs: $addedFileURLs
5154
)
52-
} else {
55+
} else if selectedPickerState == .camera {
5356
viewFactory.makeCameraPickerView(
5457
selected: $selectedPickerState,
5558
cameraPickerShown: $cameraPickerShown,
5659
cameraImageAdded: cameraImageAdded
5760
)
61+
} else if selectedPickerState == .custom {
62+
viewFactory.makeCustomAttachmentView(
63+
addedCustomAttachments: addedCustomAttachments,
64+
onCustomAttachmentTap: onCustomAttachmentTap
65+
)
5866
}
5967
}
6068
.frame(height: height)
@@ -106,15 +114,28 @@ struct AttachmentSourcePickerView: View {
106114
}
107115

108116
/// Button used for picking of attachment types.
109-
struct AttachmentPickerButton: View {
117+
public struct AttachmentPickerButton: View {
118+
110119
@Injected(\.colors) var colors
111120

112121
var iconName: String
113122
var pickerType: AttachmentPickerState
114123
var isSelected: Bool
115124
var onTap: (AttachmentPickerState) -> Void
116125

117-
var body: some View {
126+
public init(
127+
iconName: String,
128+
pickerType: AttachmentPickerState,
129+
isSelected: Bool,
130+
onTap: @escaping (AttachmentPickerState) -> Void
131+
) {
132+
self.iconName = iconName
133+
self.pickerType = pickerType
134+
self.isSelected = isSelected
135+
self.onTap = onTap
136+
}
137+
138+
public var body: some View {
118139
Button {
119140
onTap(pickerType)
120141
} label: {

0 commit comments

Comments
 (0)