forked from mirrors/linphone-iphone
Fix media list rendering in chat bubble
This commit is contained in:
parent
f51e92adee
commit
1763f21359
6 changed files with 627 additions and 430 deletions
|
|
@ -123,6 +123,9 @@
|
|||
},
|
||||
"%lld selected participants" : {
|
||||
|
||||
},
|
||||
"%lld%%" : {
|
||||
|
||||
},
|
||||
"+" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -538,68 +538,48 @@ struct ChatBubbleView: View {
|
|||
|
||||
if eventLogMessage.message.attachments.first!.type == .image || eventLogMessage.message.attachments.first!.type == .video {
|
||||
if #available(iOS 16.0, *) {
|
||||
AsyncImage(url: eventLogMessage.message.attachments.first!.thumbnail) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
case .success(let image):
|
||||
ZStack {
|
||||
image
|
||||
CachedAsyncImage(
|
||||
url: eventLogMessage.message.attachments.first!.thumbnail,
|
||||
placeholder: ProgressView(),
|
||||
onImageTapped: {
|
||||
selectedURLAttachment = eventLogMessage.message.attachments.first!.full
|
||||
})
|
||||
|
||||
.overlay(
|
||||
Group {
|
||||
if eventLogMessage.message.attachments.first!.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.interpolation(.medium)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
||||
if eventLogMessage.message.attachments.first!.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40, alignment: .leading)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
case .failure:
|
||||
Image("image-broken")
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
)
|
||||
.layoutPriority(-1)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
.onTapGesture {
|
||||
selectedURLAttachment = eventLogMessage.message.attachments.first!.full
|
||||
}
|
||||
} else {
|
||||
AsyncImage(url: eventLogMessage.message.attachments.first!.thumbnail) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
ProgressView()
|
||||
case .success(let image):
|
||||
ZStack {
|
||||
image
|
||||
CachedAsyncImage(
|
||||
url: eventLogMessage.message.attachments.first!.thumbnail,
|
||||
placeholder: ProgressView(),
|
||||
onImageTapped: {
|
||||
selectedURLAttachment = eventLogMessage.message.attachments.first!.full
|
||||
})
|
||||
|
||||
.overlay(
|
||||
Group {
|
||||
if eventLogMessage.message.attachments.first!.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.interpolation(.medium)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
||||
if eventLogMessage.message.attachments.first!.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40, alignment: .leading)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
case .failure:
|
||||
Image("image-broken")
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
)
|
||||
.layoutPriority(-1)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
.id(UUID())
|
||||
.onTapGesture {
|
||||
selectedURLAttachment = eventLogMessage.message.attachments.first!.full
|
||||
}
|
||||
}
|
||||
} else if eventLogMessage.message.attachments.first!.type == .gif {
|
||||
if #available(iOS 16.0, *) {
|
||||
|
|
@ -633,22 +613,29 @@ struct ChatBubbleView: View {
|
|||
} else {
|
||||
HStack {
|
||||
VStack {
|
||||
Image(getImageOfType(type: eventLogMessage.message.attachments.first!.type))
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.frame(width: 60, height: 60, alignment: .leading)
|
||||
if conversationViewModel.attachmentTransferInProgress != nil && conversationViewModel.attachmentTransferInProgress!.id == eventLogMessage.message.attachments.first!.id {
|
||||
CircularProgressView(progress: Double(conversationViewModel.attachmentTransferInProgress!.transferProgressIndication) / 100.0)
|
||||
.frame(width: 80, height: 80)
|
||||
} else {
|
||||
Image(getImageOfType(type: eventLogMessage.message.attachments.first!.type))
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.frame(width: 60, height: 60, alignment: .leading)
|
||||
}
|
||||
}
|
||||
.frame(width: 100, height: 100)
|
||||
.background(Color.grayMain2c200)
|
||||
.onTapGesture {
|
||||
if eventLogMessage.message.attachments.first!.type == .fileTransfer && !eventLogMessage.message.isFileTransferInProgress {
|
||||
if eventLogMessage.message.attachments.first!.type == .fileTransfer && eventLogMessage.message.attachments.first!.transferProgressIndication == -1 {
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
conversationViewModel.downloadContent(
|
||||
chatMessage: eventLogMessage.eventModel.eventLog.chatMessage!,
|
||||
content: eventLogMessage.eventModel.eventLog.chatMessage!.contents.first!
|
||||
)
|
||||
}
|
||||
} else {
|
||||
selectedURLAttachment = eventLogMessage.message.attachments.first!.full
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -676,121 +663,117 @@ struct ChatBubbleView: View {
|
|||
}
|
||||
} else if eventLogMessage.message.attachments.count > 1 {
|
||||
let sizeCard = ((geometryProxy.size.width - 150)/2)-2
|
||||
let columns = [
|
||||
GridItem(.adaptive(minimum: sizeCard), spacing: 1)]
|
||||
let columns = [GridItem(.adaptive(minimum: sizeCard), spacing: 1)]
|
||||
|
||||
LazyVStack {
|
||||
VStack {
|
||||
LazyVGrid(columns: columns) {
|
||||
ForEach(eventLogMessage.message.attachments, id: \.id) { attachment in
|
||||
if attachment.type == .image || attachment.type == .gif
|
||||
|| attachment.type == .video {
|
||||
ForEach(eventLogMessage.message.attachments.filter({ $0.type == .image || $0.type == .gif
|
||||
|| $0.type == .video }), id: \.id) { attachment in
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.fill(Color(.white))
|
||||
.frame(width: sizeCard, height: sizeCard)
|
||||
|
||||
if #available(iOS 16.0, *) {
|
||||
AsyncImage(url: attachment.thumbnail) { image in
|
||||
ZStack {
|
||||
image
|
||||
.resizable()
|
||||
.interpolation(.medium)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
||||
CachedAsyncImage(
|
||||
url: attachment.thumbnail,
|
||||
placeholder: ProgressView(),
|
||||
onImageTapped: {
|
||||
selectedURLAttachment = attachment.full
|
||||
})
|
||||
|
||||
.overlay(
|
||||
Group {
|
||||
if attachment.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40, alignment: .leading)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
}
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
)
|
||||
.layoutPriority(-1)
|
||||
.onTapGesture {
|
||||
selectedURLAttachment = attachment.full
|
||||
}
|
||||
} else {
|
||||
AsyncImage(url: attachment.thumbnail) { image in
|
||||
ZStack {
|
||||
image
|
||||
.resizable()
|
||||
.interpolation(.medium)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
||||
CachedAsyncImage(
|
||||
url: attachment.thumbnail,
|
||||
placeholder: ProgressView(),
|
||||
onImageTapped: {
|
||||
selectedURLAttachment = attachment.full
|
||||
})
|
||||
|
||||
.overlay(
|
||||
Group {
|
||||
if attachment.type == .video {
|
||||
Image("play-fill")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 40, height: 40, alignment: .leading)
|
||||
.frame(width: 40, height: 40)
|
||||
}
|
||||
}
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
)
|
||||
.id(UUID())
|
||||
.layoutPriority(-1)
|
||||
.onTapGesture {
|
||||
selectedURLAttachment = attachment.full
|
||||
}
|
||||
}
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ForEach(eventLogMessage.message.attachments, id: \.id) { attachment in
|
||||
if !(attachment.type == .image || attachment.type == .gif
|
||||
|| attachment.type == .video) {
|
||||
HStack {
|
||||
VStack {
|
||||
ForEach(eventLogMessage.message.attachments.filter({ $0.type != .image && $0.type != .gif
|
||||
&& $0.type != .video }), id: \.id) { attachment in
|
||||
HStack {
|
||||
VStack {
|
||||
if conversationViewModel.attachmentTransferInProgress != nil && conversationViewModel.attachmentTransferInProgress!.id == attachment.id {
|
||||
CircularProgressView(progress: Double(conversationViewModel.attachmentTransferInProgress!.transferProgressIndication) / 100.0)
|
||||
.frame(width: 80, height: 80)
|
||||
} else {
|
||||
Image(getImageOfType(type: attachment.type))
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.frame(width: 60, height: 60, alignment: .leading)
|
||||
}
|
||||
.frame(width: 100, height: 100)
|
||||
.background(Color.grayMain2c200)
|
||||
.onTapGesture {
|
||||
if attachment.type == .fileTransfer && !eventLogMessage.message.isFileTransferInProgress {
|
||||
if let content = eventLogMessage.eventModel.eventLog.chatMessage!.contents.first(where: {$0.filePath == attachment.full.absoluteString}) {
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
conversationViewModel.downloadContent(
|
||||
chatMessage: eventLogMessage.eventModel.eventLog.chatMessage!,
|
||||
content: content
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(width: 100, height: 100)
|
||||
.background(Color.grayMain2c200)
|
||||
.onTapGesture {
|
||||
if attachment.type == .fileTransfer && attachment.transferProgressIndication == -1 {
|
||||
CoreContext.shared.doOnCoreQueue { _ in
|
||||
if let content = eventLogMessage.eventModel.eventLog.chatMessage!.contents.first(where: {$0.name == attachment.name}) {
|
||||
conversationViewModel.downloadContent(
|
||||
chatMessage: eventLogMessage.eventModel.eventLog.chatMessage!,
|
||||
content: content
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectedURLAttachment = attachment.full
|
||||
}
|
||||
}
|
||||
|
||||
VStack {
|
||||
Text(attachment.name)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.default_text_style_600(styleSize: 14)
|
||||
.truncationMode(.middle)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
||||
VStack {
|
||||
Text(attachment.name)
|
||||
.foregroundStyle(Color.grayMain2c700)
|
||||
.default_text_style_600(styleSize: 14)
|
||||
.truncationMode(.middle)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(attachment.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.background(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.onTapGesture {
|
||||
selectedURLAttachment = attachment.full
|
||||
Text(attachment.size.formatBytes())
|
||||
.default_text_style_300(styleSize: 14)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.background(.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||
.onTapGesture {
|
||||
selectedURLAttachment = attachment.full
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1083,6 +1066,82 @@ struct CustomSlider: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct CircularProgressView: View {
|
||||
var progress: Double
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(Color(.systemGray4), lineWidth: 5)
|
||||
Circle()
|
||||
.trim(from: 0, to: progress)
|
||||
.stroke(
|
||||
Color.orangeMain500,
|
||||
style: StrokeStyle(lineWidth: 5, lineCap: .round))
|
||||
.rotationEffect(Angle(degrees: -90))
|
||||
.animation(.easeInOut(duration: 0.5), value: progress)
|
||||
.overlay(
|
||||
Text("\(Int(progress * 100))%")
|
||||
.font(.system(size: 15, weight: .bold, design: .rounded))
|
||||
.foregroundColor(Color.orangeMain500)
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
class ImageCache {
|
||||
static let shared = NSCache<NSURL, UIImage>()
|
||||
}
|
||||
|
||||
struct CachedAsyncImage<Placeholder: View>: View {
|
||||
let url: URL
|
||||
let placeholder: Placeholder
|
||||
let onImageTapped: (() -> Void)?
|
||||
|
||||
@State private var image: UIImage?
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if let image = image {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.interpolation(.medium)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.onTapGesture {
|
||||
onImageTapped?()
|
||||
}
|
||||
} else {
|
||||
placeholder
|
||||
.onAppear {
|
||||
loadImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func loadImage() {
|
||||
if let cachedImage = ImageCache.shared.object(forKey: url as NSURL) {
|
||||
self.image = cachedImage
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
if let downloadedImage = UIImage(data: data) {
|
||||
ImageCache.shared.setObject(downloadedImage, forKey: url as NSURL)
|
||||
await MainActor.run {
|
||||
self.image = downloadedImage
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Error loading image: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
#Preview {
|
||||
ChatBubbleView(conversationViewModel: ConversationViewModel(), index: 0)
|
||||
|
|
|
|||
|
|
@ -757,139 +757,115 @@ struct ConversationFragment: View {
|
|||
|
||||
if conversationViewModel.selectedMessage != nil && conversationViewModel.displayedConversation != nil {
|
||||
let iconSize = ((geometry.size.width - (conversationViewModel.displayedConversation!.isGroup ? 43 : 10) - 10) / 6) - 30
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
|
||||
ScrollView {
|
||||
VStack {
|
||||
HStack {
|
||||
if conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
if conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "👍")
|
||||
} label: {
|
||||
Text("👍")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "❤️")
|
||||
} label: {
|
||||
Text("❤️")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "❤️" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "😂")
|
||||
} label: {
|
||||
Text("😂")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😂" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "😮")
|
||||
} label: {
|
||||
Text("😮")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😮" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "😢")
|
||||
} label: {
|
||||
Text("😢")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😢" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
/*
|
||||
Button {
|
||||
} label: {
|
||||
Image("plus-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: iconSize > 50 ? 50 : iconSize, height: iconSize > 50 ? 50 : iconSize, alignment: .leading)
|
||||
}
|
||||
.padding(.trailing, 5)
|
||||
*/
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 10)
|
||||
.background(.white)
|
||||
.cornerRadius(20)
|
||||
|
||||
if !conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0)
|
||||
.shadow(color: .black.opacity(0.1), radius: 10)
|
||||
|
||||
ChatBubbleView(conversationViewModel: conversationViewModel, eventLogMessage: conversationViewModel.selectedMessage!, geometryProxy: geometry)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 1)
|
||||
.shadow(color: .black.opacity(0.1), radius: 10)
|
||||
|
||||
HStack {
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "👍")
|
||||
} label: {
|
||||
Text("👍")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "❤️")
|
||||
} label: {
|
||||
Text("❤️")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "❤️" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "😂")
|
||||
} label: {
|
||||
Text("😂")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😂" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "😮")
|
||||
} label: {
|
||||
Text("😮")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😮" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
Button {
|
||||
conversationViewModel.sendReaction(emoji: "😢")
|
||||
} label: {
|
||||
Text("😢")
|
||||
.default_text_style(styleSize: iconSize > 50 ? 50 : iconSize)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(conversationViewModel.selectedMessage?.message.ownReaction == "😢" ? Color.gray200 : .white)
|
||||
.cornerRadius(10)
|
||||
|
||||
/*
|
||||
Button {
|
||||
} label: {
|
||||
Image("plus-circle")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.grayMain2c500)
|
||||
.frame(width: iconSize > 50 ? 50 : iconSize, height: iconSize > 50 ? 50 : iconSize, alignment: .leading)
|
||||
}
|
||||
.padding(.trailing, 5)
|
||||
*/
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 10)
|
||||
.background(.white)
|
||||
.cornerRadius(20)
|
||||
|
||||
if !conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0)
|
||||
.shadow(color: .black.opacity(0.1), radius: 10)
|
||||
|
||||
ChatBubbleView(conversationViewModel: conversationViewModel, eventLogMessage: conversationViewModel.selectedMessage!, geometryProxy: geometry)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 1)
|
||||
.shadow(color: .black.opacity(0.1), radius: 10)
|
||||
|
||||
HStack {
|
||||
if conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
VStack {
|
||||
Button {
|
||||
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
|
||||
conversationViewModel.selectedMessage = nil
|
||||
conversationViewModel.replyToMessage(index: indexMessage ?? 0)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_reply_to_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("reply")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
if conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
if !conversationViewModel.selectedMessage!.message.text.isEmpty {
|
||||
VStack {
|
||||
Button {
|
||||
UIPasteboard.general.setValue(
|
||||
conversationViewModel.selectedMessage?.message.text ?? "Error_message_not_available",
|
||||
forPasteboardType: UTType.plainText.identifier
|
||||
)
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_message_copied_into_clipboard"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
|
||||
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
|
||||
conversationViewModel.selectedMessage = nil
|
||||
conversationViewModel.replyToMessage(index: indexMessage ?? 0)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_copy_chat_message")
|
||||
Text("menu_reply_to_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("copy")
|
||||
Image("reply")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
|
|
@ -898,65 +874,93 @@ struct ConversationFragment: View {
|
|||
}
|
||||
|
||||
Divider()
|
||||
|
||||
if !conversationViewModel.selectedMessage!.message.text.isEmpty {
|
||||
Button {
|
||||
UIPasteboard.general.setValue(
|
||||
conversationViewModel.selectedMessage?.message.text ?? "Error_message_not_available",
|
||||
forPasteboardType: UTType.plainText.identifier
|
||||
)
|
||||
|
||||
ToastViewModel.shared.toastMessage = "Success_message_copied_into_clipboard"
|
||||
ToastViewModel.shared.displayToast = true
|
||||
|
||||
conversationViewModel.selectedMessage = nil
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_copy_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("copy")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
|
||||
Button {
|
||||
conversationForwardMessageViewModel.initConversationsLists(convsList: conversationsListViewModel.conversationsListTmp)
|
||||
conversationForwardMessageViewModel.selectedMessage = conversationViewModel.selectedMessage
|
||||
conversationViewModel.selectedMessage = nil
|
||||
withAnimation {
|
||||
isShowConversationForwardMessageFragment = true
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_forward_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("forward")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
conversationViewModel.deleteMessage()
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_delete_selected_item")
|
||||
.foregroundStyle(.red)
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("trash-simple-red")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.red)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: geometry.size.width / 1.5)
|
||||
.padding(.vertical, 8)
|
||||
.background(.white)
|
||||
.cornerRadius(20)
|
||||
|
||||
Button {
|
||||
conversationForwardMessageViewModel.initConversationsLists(convsList: conversationsListViewModel.conversationsListTmp)
|
||||
conversationForwardMessageViewModel.selectedMessage = conversationViewModel.selectedMessage
|
||||
conversationViewModel.selectedMessage = nil
|
||||
withAnimation {
|
||||
isShowConversationForwardMessageFragment = true
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_forward_chat_message")
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("forward")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
conversationViewModel.deleteMessage()
|
||||
} label: {
|
||||
HStack {
|
||||
Text("menu_delete_selected_item")
|
||||
.foregroundStyle(.red)
|
||||
.default_text_style(styleSize: 15)
|
||||
Spacer()
|
||||
Image("trash-simple-red")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(.red)
|
||||
.frame(width: 20, height: 20, alignment: .leading)
|
||||
}
|
||||
.padding(.vertical, 5)
|
||||
.padding(.horizontal, 20)
|
||||
if !conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: geometry.size.width / 1.5)
|
||||
.padding(.vertical, 8)
|
||||
.background(.white)
|
||||
.cornerRadius(20)
|
||||
|
||||
if !conversationViewModel.selectedMessage!.message.isOutgoing {
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.bottom, 20)
|
||||
.padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0)
|
||||
.shadow(color: .black.opacity(0.1), radius: 10)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.bottom, 20)
|
||||
.padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0)
|
||||
.shadow(color: .black.opacity(0.1), radius: 10)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(minHeight: geometry.size.height)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(.gray.opacity(0.1))
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
|
|
|
|||
|
|
@ -85,8 +85,9 @@ public struct Attachment: Codable, Identifiable, Hashable {
|
|||
public let type: AttachmentType
|
||||
public let duration: Int
|
||||
public let size: Int
|
||||
public var transferProgressIndication: Int
|
||||
|
||||
public init(id: String, name: String, thumbnail: URL, full: URL, type: AttachmentType, duration: Int = 0, size: Int = 0) {
|
||||
public init(id: String, name: String, thumbnail: URL, full: URL, type: AttachmentType, duration: Int = 0, size: Int = 0, transferProgressIndication: Int = 0) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.thumbnail = thumbnail
|
||||
|
|
@ -94,9 +95,16 @@ public struct Attachment: Codable, Identifiable, Hashable {
|
|||
self.type = type
|
||||
self.duration = duration
|
||||
self.size = size
|
||||
self.transferProgressIndication = transferProgressIndication
|
||||
}
|
||||
|
||||
public init(id: String, name: String, url: URL, type: AttachmentType, duration: Int = 0, size: Int = 0) {
|
||||
self.init(id: id, name: name, thumbnail: url, full: url, type: type, duration: duration, size: size)
|
||||
public init(id: String, name: String, url: URL, type: AttachmentType, duration: Int = 0, size: Int = 0, transferProgressIndication: Int = 0) {
|
||||
self.init(id: id, name: name, thumbnail: url, full: url, type: type, duration: duration, size: size, transferProgressIndication: transferProgressIndication)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attachment: Equatable {
|
||||
public static func == (lhs: Attachment, rhs: Attachment) -> Bool {
|
||||
lhs.id == rhs.id && lhs.transferProgressIndication == rhs.transferProgressIndication
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,8 +88,6 @@ public struct Message: Identifiable, Hashable {
|
|||
public var isIcalendar: Bool
|
||||
public var messageConferenceInfo: MessageConferenceInfo?
|
||||
|
||||
public var isFileTransferInProgress: Bool
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
appData: String = "",
|
||||
|
|
@ -111,8 +109,7 @@ public struct Message: Identifiable, Hashable {
|
|||
ephemeralExpireTime: Int = 0,
|
||||
ephemeralLifetime: Int = 0,
|
||||
isIcalendar: Bool = false,
|
||||
messageConferenceInfo: MessageConferenceInfo? = nil,
|
||||
isFileTransferInProgress: Bool = false
|
||||
messageConferenceInfo: MessageConferenceInfo? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.appData = appData
|
||||
|
|
@ -135,7 +132,6 @@ public struct Message: Identifiable, Hashable {
|
|||
self.ephemeralLifetime = ephemeralLifetime
|
||||
self.isIcalendar = isIcalendar
|
||||
self.messageConferenceInfo = messageConferenceInfo
|
||||
self.isFileTransferInProgress = isFileTransferInProgress
|
||||
}
|
||||
|
||||
public static func makeMessage(
|
||||
|
|
@ -188,7 +184,7 @@ extension Message {
|
|||
|
||||
extension Message: Equatable {
|
||||
public static func == (lhs: Message, rhs: Message) -> Bool {
|
||||
lhs.id == rhs.id && lhs.status == rhs.status && lhs.isFirstMessage == rhs.isFirstMessage && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime
|
||||
lhs.id == rhs.id && lhs.status == rhs.status && lhs.isFirstMessage == rhs.isFirstMessage && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime && lhs.attachments == rhs.attachments
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ class ConversationViewModel: ObservableObject {
|
|||
@Published var progress: Double = 0.0
|
||||
|
||||
@Published var attachments: [Attachment] = []
|
||||
@Published var attachmentTransferInProgress: Attachment?
|
||||
|
||||
struct SheetCategory: Identifiable {
|
||||
let id = UUID()
|
||||
|
|
@ -165,115 +166,231 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func addChatMessageDelegate(message: ChatMessage) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
if self.displayedConversation != nil {
|
||||
var statusTmp: Message.Status? = .sending
|
||||
switch message.state {
|
||||
case .InProgress:
|
||||
statusTmp = .sending
|
||||
case .Delivered:
|
||||
statusTmp = .sent
|
||||
case .DeliveredToUser:
|
||||
statusTmp = .received
|
||||
case .Displayed:
|
||||
statusTmp = .read
|
||||
case .NotDelivered:
|
||||
statusTmp = .error
|
||||
default:
|
||||
statusTmp = .sending
|
||||
if self.displayedConversation != nil {
|
||||
var statusTmp: Message.Status? = .sending
|
||||
switch message.state {
|
||||
case .InProgress:
|
||||
statusTmp = .sending
|
||||
case .Delivered:
|
||||
statusTmp = .sent
|
||||
case .DeliveredToUser:
|
||||
statusTmp = .received
|
||||
case .Displayed:
|
||||
statusTmp = .read
|
||||
case .NotDelivered:
|
||||
statusTmp = .error
|
||||
default:
|
||||
statusTmp = .sending
|
||||
}
|
||||
|
||||
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
||||
|
||||
if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty {
|
||||
if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) {
|
||||
self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId
|
||||
}
|
||||
|
||||
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
||||
|
||||
if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty {
|
||||
if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) {
|
||||
self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId
|
||||
}
|
||||
|
||||
if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) {
|
||||
if indexMessage < self.conversationMessagesSection[0].rows.count {
|
||||
if self.conversationMessagesSection[0].rows[indexMessage].message.status != statusTmp {
|
||||
DispatchQueue.main.async {
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.status = statusTmp ?? .error
|
||||
if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) {
|
||||
if indexMessage < self.conversationMessagesSection[0].rows.count {
|
||||
if self.conversationMessagesSection[0].rows[indexMessage].message.status != statusTmp {
|
||||
DispatchQueue.main.async {
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.status = statusTmp ?? .error
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage < self.conversationMessagesSection[0].rows.count {
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage < self.conversationMessagesSection[0].rows.count {
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
let chatMessageDelegate = ChatMessageDelegateStub(onMsgStateChanged: { (message: ChatMessage, msgState: ChatMessage.State) in
|
||||
var statusTmp: Message.Status?
|
||||
switch message.state {
|
||||
case .InProgress:
|
||||
statusTmp = .sending
|
||||
case .Delivered:
|
||||
statusTmp = .sent
|
||||
case .DeliveredToUser:
|
||||
statusTmp = .received
|
||||
case .Displayed:
|
||||
statusTmp = .read
|
||||
case .NotDelivered:
|
||||
statusTmp = .error
|
||||
default:
|
||||
statusTmp = .sending
|
||||
}
|
||||
|
||||
if msgState == .FileTransferDone {
|
||||
message.contents.forEach { content in
|
||||
if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) {
|
||||
if let contentIndex = self.conversationMessagesSection[0].rows[indexMessage].message.attachments.firstIndex(where: {$0.name == content.name && $0.full.pathExtension.isEmpty && $0.full.absoluteString != content.filePath}) {
|
||||
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
|
||||
let contentTmp = self.conversationMessagesSection[0].rows[indexMessage].message.attachments[contentIndex]
|
||||
if let pathThumbnail = content.type == "video" ? URL(string: self.generateThumbnail(name: content.name ?? "")) : contentTmp.thumbnail {
|
||||
if let path = URL(string: self.getNewFilePath(name: filePathSep[1])) {
|
||||
if path != contentTmp.full {
|
||||
|
||||
var typeTmp: AttachmentType = .other
|
||||
|
||||
switch content.type {
|
||||
case "image":
|
||||
typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
|
||||
case "audio":
|
||||
typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
|
||||
case "application":
|
||||
typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
|
||||
case "text":
|
||||
typeTmp = .text
|
||||
case "video":
|
||||
typeTmp = .video
|
||||
default:
|
||||
typeTmp = .other
|
||||
}
|
||||
|
||||
let newAttachment = Attachment(
|
||||
id: UUID().uuidString,
|
||||
name: content.name ?? contentTmp.name,
|
||||
thumbnail: content.type == "video" ? pathThumbnail : path,
|
||||
full: path,
|
||||
type: typeTmp,
|
||||
duration: contentTmp.duration,
|
||||
size: contentTmp.size,
|
||||
transferProgressIndication: 100
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.attachments[contentIndex] = newAttachment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.coreContext.doOnCoreQueue { _ in
|
||||
let chatMessageDelegate = ChatMessageDelegateStub(onMsgStateChanged: { (message: ChatMessage, msgState: ChatMessage.State) in
|
||||
var statusTmp: Message.Status?
|
||||
switch message.state {
|
||||
case .InProgress:
|
||||
statusTmp = .sending
|
||||
case .Delivered:
|
||||
statusTmp = .sent
|
||||
case .DeliveredToUser:
|
||||
statusTmp = .received
|
||||
case .Displayed:
|
||||
statusTmp = .read
|
||||
case .NotDelivered:
|
||||
statusTmp = .error
|
||||
default:
|
||||
statusTmp = .sending
|
||||
}
|
||||
|
||||
if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) {
|
||||
self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId
|
||||
}
|
||||
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.status = statusTmp ?? .error
|
||||
}
|
||||
}
|
||||
}, onNewMessageReaction: { (message: ChatMessage, _: ChatMessageReaction) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
var reactionsTmp: [String] = []
|
||||
message.reactions.forEach({ chatMessageReaction in
|
||||
reactionsTmp.append(chatMessageReaction.body)
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp
|
||||
}
|
||||
}
|
||||
}, onReactionRemoved: { (message: ChatMessage, _: Address) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
var reactionsTmp: [String] = []
|
||||
message.reactions.forEach({ chatMessageReaction in
|
||||
reactionsTmp.append(chatMessageReaction.body)
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp
|
||||
}
|
||||
}
|
||||
}, onEphemeralMessageTimerStarted: { (message: ChatMessage) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||
}
|
||||
|
||||
if let indexMessageEventLogId = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId.isEmpty && $0.eventModel.eventLog.chatMessage != nil ? $0.eventModel.eventLog.chatMessage!.messageId == message.messageId : false}) {
|
||||
self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId
|
||||
}
|
||||
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.status = statusTmp ?? .error
|
||||
}
|
||||
}
|
||||
}, onNewMessageReaction: { (message: ChatMessage, _: ChatMessageReaction) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
var reactionsTmp: [String] = []
|
||||
message.reactions.forEach({ chatMessageReaction in
|
||||
reactionsTmp.append(chatMessageReaction.body)
|
||||
})
|
||||
|
||||
self.chatMessageDelegateHolders.append(ChatMessageDelegateHolder(message: message, delegate: chatMessageDelegate))
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp
|
||||
}
|
||||
}
|
||||
}, onReactionRemoved: { (message: ChatMessage, _: Address) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
var reactionsTmp: [String] = []
|
||||
message.reactions.forEach({ chatMessageReaction in
|
||||
reactionsTmp.append(chatMessageReaction.body)
|
||||
})
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.reactions = reactionsTmp
|
||||
}
|
||||
}
|
||||
}, onFileTransferProgressIndication: { (message: ChatMessage, content: Content, offset: Int, total: Int) in
|
||||
if let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) {
|
||||
|
||||
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
|
||||
let path = URL(string: self.getNewFilePath(name: filePathSep[1]))
|
||||
|
||||
if let contentTmp = self.conversationMessagesSection[0].rows[indexMessage].message.attachments.first(where: {$0.full == path}) {
|
||||
DispatchQueue.main.async {
|
||||
self.attachmentTransferInProgress = contentTmp
|
||||
self.attachmentTransferInProgress!.transferProgressIndication = ((offset * 100) / total)
|
||||
}
|
||||
|
||||
if ((offset * 100) / total) >= 100 {
|
||||
DispatchQueue.main.async {
|
||||
self.attachmentTransferInProgress = nil
|
||||
}
|
||||
}
|
||||
/*
|
||||
if ((offset * 100) / total) >= 100 {
|
||||
|
||||
var typeTmp: AttachmentType = .other
|
||||
|
||||
switch content.type {
|
||||
case "image":
|
||||
typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
|
||||
case "audio":
|
||||
typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
|
||||
case "application":
|
||||
typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
|
||||
case "text":
|
||||
typeTmp = .text
|
||||
case "video":
|
||||
typeTmp = .video
|
||||
default:
|
||||
typeTmp = .other
|
||||
}
|
||||
|
||||
if let pathThumbnail = content.type == "video" ? URL(string: self.generateThumbnail(name: content.name ?? "")) : contentTmp.thumbnail {
|
||||
if content.filePath != nil {
|
||||
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
|
||||
if let path = URL(string: self.getNewFilePath(name: filePathSep[1])) {
|
||||
|
||||
let newAttachment = Attachment(
|
||||
id: UUID().uuidString,
|
||||
name: content.name ?? contentTmp.name,
|
||||
thumbnail: content.type == "video" ? pathThumbnail : path,
|
||||
full: path,
|
||||
type: typeTmp,
|
||||
duration: contentTmp.duration,
|
||||
size: contentTmp.size,
|
||||
transferProgressIndication: 100
|
||||
)
|
||||
|
||||
if let contentIndex = self.conversationMessagesSection[0].rows[indexMessage].message.attachments.firstIndex(where: {$0.full == path}) {
|
||||
DispatchQueue.main.async {
|
||||
print("attachmentattachment ----------------")
|
||||
print("attachmentattachment ---------------- \(content.name ?? "")")
|
||||
print("attachmentattachment ---------------- \(filePathSep[1])")
|
||||
print("attachmentattachment ----------------")
|
||||
self.conversationMessagesSection[0].rows[indexMessage].message.attachments[contentIndex] = newAttachment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}, onEphemeralMessageTimerStarted: { (message: ChatMessage) in
|
||||
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
|
||||
let ephemeralExpireTimeTmp = message.ephemeralExpireTime
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if indexMessage != nil {
|
||||
self.conversationMessagesSection[0].rows[indexMessage!].message.ephemeralExpireTime = ephemeralExpireTimeTmp
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
self.chatMessageDelegateHolders.append(ChatMessageDelegateHolder(message: message, delegate: chatMessageDelegate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -425,7 +542,8 @@ class ConversationViewModel: ObservableObject {
|
|||
name: content.name!,
|
||||
url: path!,
|
||||
type: .fileTransfer,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -458,7 +576,8 @@ class ConversationViewModel: ObservableObject {
|
|||
url: path!,
|
||||
type: typeTmp,
|
||||
duration: typeTmp == .voiceRecording ? content.fileDuration : 0,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -481,7 +600,8 @@ class ConversationViewModel: ObservableObject {
|
|||
thumbnail: pathThumbnail!,
|
||||
full: path!,
|
||||
type: .video,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -595,8 +715,7 @@ class ConversationViewModel: ObservableObject {
|
|||
ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0,
|
||||
ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0,
|
||||
isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false,
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil,
|
||||
isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -648,7 +767,7 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty {
|
||||
eventLog.chatMessage!.contents.forEach { content in
|
||||
if content.isText {
|
||||
if content.isText && content.name == nil {
|
||||
contentText = content.utf8Text ?? ""
|
||||
} else if content.name != nil && !content.name!.isEmpty {
|
||||
if content.filePath == nil || content.filePath!.isEmpty {
|
||||
|
|
@ -662,7 +781,8 @@ class ConversationViewModel: ObservableObject {
|
|||
name: content.name!,
|
||||
url: path!,
|
||||
type: .fileTransfer,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -694,7 +814,8 @@ class ConversationViewModel: ObservableObject {
|
|||
url: path!,
|
||||
type: typeTmp,
|
||||
duration: typeTmp == . voiceRecording ? content.fileDuration : 0,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -717,7 +838,8 @@ class ConversationViewModel: ObservableObject {
|
|||
thumbnail: pathThumbnail!,
|
||||
full: path!,
|
||||
type: .video,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -831,8 +953,7 @@ class ConversationViewModel: ObservableObject {
|
|||
ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0,
|
||||
ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0,
|
||||
isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false,
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil,
|
||||
isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil
|
||||
)
|
||||
), at: 0
|
||||
)
|
||||
|
|
@ -882,7 +1003,7 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty {
|
||||
eventLog.chatMessage!.contents.forEach { content in
|
||||
if content.isText {
|
||||
if content.isText && content.name == nil {
|
||||
contentText = content.utf8Text ?? ""
|
||||
} else {
|
||||
if content.filePath == nil || content.filePath!.isEmpty {
|
||||
|
|
@ -896,7 +1017,8 @@ class ConversationViewModel: ObservableObject {
|
|||
name: content.name ?? "???",
|
||||
url: path!,
|
||||
type: .fileTransfer,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name ?? "???")"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -905,6 +1027,7 @@ class ConversationViewModel: ObservableObject {
|
|||
if content.type != "video" {
|
||||
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
|
||||
let path = URL(string: self.getNewFilePath(name: filePathSep[1]))
|
||||
|
||||
var typeTmp: AttachmentType = .other
|
||||
|
||||
switch content.type {
|
||||
|
|
@ -928,7 +1051,8 @@ class ConversationViewModel: ObservableObject {
|
|||
url: path!,
|
||||
type: typeTmp,
|
||||
duration: typeTmp == . voiceRecording ? content.fileDuration : 0,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -951,7 +1075,8 @@ class ConversationViewModel: ObservableObject {
|
|||
thumbnail: pathThumbnail!,
|
||||
full: path!,
|
||||
type: .video,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -1059,6 +1184,7 @@ class ConversationViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
if eventLog.chatMessage != nil {
|
||||
|
||||
let message = EventLogMessage(
|
||||
eventModel: EventModel(eventLog: eventLog),
|
||||
message: Message(
|
||||
|
|
@ -1080,8 +1206,7 @@ class ConversationViewModel: ObservableObject {
|
|||
ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0,
|
||||
ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0,
|
||||
isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false,
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil,
|
||||
isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -1194,7 +1319,7 @@ class ConversationViewModel: ObservableObject {
|
|||
|
||||
if eventLog.chatMessage != nil && !eventLog.chatMessage!.contents.isEmpty {
|
||||
eventLog.chatMessage!.contents.forEach { content in
|
||||
if content.isText {
|
||||
if content.isText && content.name == nil {
|
||||
contentText = content.utf8Text ?? ""
|
||||
} else if content.name != nil && !content.name!.isEmpty {
|
||||
if content.filePath == nil || content.filePath!.isEmpty {
|
||||
|
|
@ -1208,7 +1333,8 @@ class ConversationViewModel: ObservableObject {
|
|||
name: content.name!,
|
||||
url: path!,
|
||||
type: .fileTransfer,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -1240,7 +1366,8 @@ class ConversationViewModel: ObservableObject {
|
|||
url: path!,
|
||||
type: typeTmp,
|
||||
duration: typeTmp == . voiceRecording ? content.fileDuration : 0,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -1263,7 +1390,8 @@ class ConversationViewModel: ObservableObject {
|
|||
thumbnail: pathThumbnail!,
|
||||
full: path!,
|
||||
type: .video,
|
||||
size: content.fileSize
|
||||
size: content.fileSize,
|
||||
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
|
||||
)
|
||||
attachmentNameList += ", \(content.name!)"
|
||||
attachmentList.append(attachment)
|
||||
|
|
@ -1377,8 +1505,7 @@ class ConversationViewModel: ObservableObject {
|
|||
ephemeralExpireTime: eventLog.chatMessage?.ephemeralExpireTime ?? 0,
|
||||
ephemeralLifetime: eventLog.chatMessage?.ephemeralLifetime ?? 0,
|
||||
isIcalendar: eventLog.chatMessage?.contents.first?.isIcalendar ?? false,
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil,
|
||||
isFileTransferInProgress: eventLog.chatMessage!.isFileTransferInProgress
|
||||
messageConferenceInfo: eventLog.chatMessage != nil && eventLog.chatMessage!.contents.first != nil && eventLog.chatMessage!.contents.first!.isIcalendar == true ? self.parseConferenceInvite(content: eventLog.chatMessage!.contents.first!) : nil
|
||||
)
|
||||
), at: 0
|
||||
)
|
||||
|
|
@ -1624,7 +1751,7 @@ class ConversationViewModel: ObservableObject {
|
|||
func downloadContent(chatMessage: ChatMessage, content: Content) {
|
||||
// Log.debug("[ConversationViewModel] Starting downloading content for file \(model.fileName)")
|
||||
if self.displayedConversation != nil {
|
||||
if !chatMessage.isFileTransferInProgress && (content.filePath == nil || content.filePath!.isEmpty) {
|
||||
if content.filePath == nil || content.filePath!.isEmpty {
|
||||
if let contentName = content.name {
|
||||
// let isImage = FileUtil.isExtensionImage(path: contentName)
|
||||
var file = FileUtil.sharedContainerUrl().appendingPathComponent("Library/Images").absoluteString + (contentName.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue