Add swipe action in message list

This commit is contained in:
Benoit Martins 2024-08-14 14:37:46 +02:00
parent 8045c4af2d
commit 3ba5fd5f38
10 changed files with 325 additions and 145 deletions

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "reply-reversed.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,5 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="R&#195;&#169;pondre" transform="translate(0, 28) scale(1, -1)">
<path id="Vector" d="M2.57775 14.19L10.9109 22.5231C11.0274 22.6398 11.176 22.7193 11.3377 22.7515C11.4994 22.7837 11.6671 22.7672 11.8194 22.7041C11.9718 22.6409 12.102 22.534 12.1935 22.3969C12.2851 22.2597 12.3339 22.0985 12.3338 21.9336V17.791C18.2815 18.1285 22.3595 21.9856 23.5043 23.2075C23.6841 23.3995 23.9198 23.5298 24.1779 23.5801C24.4361 23.6303 24.7035 23.5979 24.9421 23.4873C25.1808 23.3768 25.3784 23.1938 25.507 22.9644C25.6356 22.735 25.6886 22.4708 25.6584 22.2096C25.272 18.8493 23.4314 15.6171 20.4752 13.1088C18.0201 11.0255 15.0358 9.70366 12.3338 9.47242V5.26732C12.3339 5.10241 12.2851 4.94117 12.1935 4.80401C12.102 4.66685 11.9718 4.55994 11.8194 4.49682C11.6671 4.43369 11.4994 4.41719 11.3377 4.4494C11.176 4.48161 11.0274 4.56108 10.9109 4.67775L2.57775 13.0109C2.50027 13.0883 2.43881 13.1802 2.39688 13.2813C2.35494 13.3825 2.33335 13.4909 2.33335 13.6004C2.33335 13.71 2.35494 13.8184 2.39688 13.9195C2.43881 14.0207 2.50027 14.1126 2.57775 14.19Z" fill="#6C7A87"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -27,6 +27,9 @@
},
"*" : {
},
"**%@**" : {
},
"**Camera** : Pour capturer votre vidéo lors des appels vidéo et conférence." : {
@ -969,6 +972,23 @@
}
}
},
"conversation_reply_to_message_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Replying to: "
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "En réponse à : "
}
}
}
},
"Conversations" : {
},

View file

@ -33,6 +33,7 @@ struct ChatBubbleView: View {
@State private var timePassed: TimeInterval?
var body: some View {
HStack {
VStack {
if !message.text.isEmpty || !message.attachments.isEmpty {
HStack(alignment: .top, content: {
@ -188,6 +189,14 @@ struct ChatBubbleView: View {
}
}
.contentShape(Rectangle())
.onTapGesture {
if conversationViewModel.selectedMessage != nil {
conversationViewModel.selectedMessage = nil
}
UIApplication.shared.endEditing()
}
}
@ViewBuilder
func messageAttachments() -> some View {

View file

@ -222,9 +222,6 @@ struct ConversationFragment: View {
.padding()
}
}
.onTapGesture {
UIApplication.shared.endEditing()
}
.onAppear {
conversationViewModel.getMessages()
}
@ -315,9 +312,6 @@ struct ConversationFragment: View {
.padding()
}
}
.onTapGesture {
UIApplication.shared.endEditing()
}
.onAppear {
conversationViewModel.getMessages()
}
@ -327,6 +321,53 @@ struct ConversationFragment: View {
}
}
if conversationViewModel.messageToReply != nil {
ZStack(alignment: .top) {
HStack {
VStack {
(
Text("conversation_reply_to_message_title")
+ Text("**\(conversationViewModel.participantConversationModel.first(where: {$0.address == conversationViewModel.messageToReply!.address})?.name ?? "")**"))
.default_text_style_300(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.bottom, 1)
.lineLimit(1)
if conversationViewModel.messageToReply!.text.isEmpty {
Text(conversationViewModel.messageToReply!.attachmentsNames)
.default_text_style_300(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
} else {
Text("\(conversationViewModel.messageToReply!.text)")
.default_text_style_300(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
}
.frame(maxWidth: .infinity)
.padding(.all, 20)
.background(Color.gray100)
HStack {
Spacer()
Button(action: {
withAnimation {
conversationViewModel.messageToReply = nil
}
}, label: {
Image("x")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
.padding(.all, 10)
})
}
}
.transition(.move(edge: .bottom))
}
if !conversationViewModel.mediasToSend.isEmpty || mediasIsLoading {
ZStack(alignment: .top) {
HStack {
@ -636,6 +677,9 @@ struct ConversationFragment: View {
VStack {
Button {
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == conversationViewModel.selectedMessage!.id})
conversationViewModel.selectedMessage = nil
conversationViewModel.replyToMessage(index: indexMessage ?? 0)
} label: {
HStack {
Text("menu_reply_to_chat_message")
@ -819,7 +863,7 @@ struct ImagePicker: UIViewControllerRepresentable {
if data != nil {
do {
let decodedData: () = try data!.write(to: path)
let attachment = Attachment(id: UUID().uuidString, url: path, type: .image)
let attachment = Attachment(id: UUID().uuidString, name: (dateString.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? "") + ".jpeg", url: path, type: .image)
parent.selectedMedia.append(attachment)
} catch {
}
@ -836,6 +880,7 @@ struct ImagePicker: UIViewControllerRepresentable {
let attachment =
Attachment(
id: UUID().uuidString,
name: name,
thumbnail: pathThumbnail!,
full: videoUrl!,
type: .video

View file

@ -338,6 +338,20 @@ struct UIList: UIViewRepresentable {
}
isScrolledToTop = scrollView.contentOffset.y >= scrollView.contentSize.height - scrollView.frame.height - 200
}
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let archiveAction = UIContextualAction(style: .normal, title: "") { action, view, completionHandler in
self.conversationViewModel.replyToMessage(index: indexPath.row)
completionHandler(true)
}
archiveAction.image = UIImage(named: "reply-reversed")!
let configuration = UISwipeActionsConfiguration(actions: [archiveAction])
return configuration
}
}
}

View file

@ -45,18 +45,20 @@ public enum AttachmentType: String, Codable {
public struct Attachment: Codable, Identifiable, Hashable {
public let id: String
public let name: String
public let thumbnail: URL
public let full: URL
public let type: AttachmentType
public init(id: String, thumbnail: URL, full: URL, type: AttachmentType) {
public init(id: String, name: String, thumbnail: URL, full: URL, type: AttachmentType) {
self.id = id
self.name = name
self.thumbnail = thumbnail
self.full = full
self.type = type
}
public init(id: String, url: URL, type: AttachmentType) {
self.init(id: id, thumbnail: url, full: url, type: type)
public init(id: String, name: String, url: URL, type: AttachmentType) {
self.init(id: id, name: name, thumbnail: url, full: url, type: type)
}
}

View file

@ -70,6 +70,7 @@ public struct Message: Identifiable, Hashable {
public var address: String
public var isFirstMessage: Bool
public var text: String
public var attachmentsNames: String
public var attachments: [Attachment]
public var recording: Recording?
public var replyMessage: ReplyMessage?
@ -85,6 +86,7 @@ public struct Message: Identifiable, Hashable {
address: String,
isFirstMessage: Bool = false,
text: String = "",
attachmentsNames: String = "",
attachments: [Attachment] = [],
recording: Recording? = nil,
replyMessage: ReplyMessage? = nil,
@ -99,6 +101,7 @@ public struct Message: Identifiable, Hashable {
self.isFirstMessage = isFirstMessage
self.address = address
self.text = text
self.attachmentsNames = attachmentsNames
self.attachments = attachments
self.recording = recording
self.replyMessage = replyMessage
@ -117,12 +120,12 @@ public struct Message: Identifiable, Hashable {
switch media.type {
case .image:
return Attachment(id: UUID().uuidString, url: thumbnailURL, type: .image)
return Attachment(id: UUID().uuidString, name: "", url: thumbnailURL, type: .image)
case .video:
guard let fullURL = await media.getURL() else {
return nil
}
return Attachment(id: UUID().uuidString, thumbnail: thumbnailURL, full: fullURL, type: .video)
return Attachment(id: UUID().uuidString, name: "", thumbnail: thumbnailURL, full: fullURL, type: .video)
}
}

View file

@ -46,6 +46,7 @@ class ConversationViewModel: ObservableObject {
var oldMessageReceived = false
@Published var selectedMessage: Message?
@Published var messageToReply: Message?
init() {}
@ -180,6 +181,15 @@ class ConversationViewModel: ObservableObject {
}
}
}
if self.displayedConversation!.chatRoom.me != nil {
ContactAvatarModel.getAvatarModelFromAddress(address: self.displayedConversation!.chatRoom.me!.address!) { avatarResult in
let avatarModelTmp = avatarResult
DispatchQueue.main.async {
self.participantConversationModel.append(avatarModelTmp)
}
}
}
}
}
}
@ -188,6 +198,10 @@ class ConversationViewModel: ObservableObject {
self.getHistorySize()
self.getUnreadMessagesCount()
self.getParticipantConversationModel()
self.mediasToSend.removeAll()
self.messageToReply = nil
coreContext.doOnCoreQueue { _ in
if self.displayedConversation != nil {
let historyEvents = self.displayedConversation!.chatRoom.getHistoryRangeEvents(begin: 0, end: 30)
@ -195,6 +209,7 @@ class ConversationViewModel: ObservableObject {
var conversationMessage: [Message] = []
historyEvents.enumerated().forEach { index, eventLog in
var attachmentNameList: String = ""
var attachmentList: [Attachment] = []
var contentText = ""
@ -211,9 +226,11 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: .image
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
} else {
@ -224,9 +241,11 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
} else if content.type == "video" {
@ -237,10 +256,12 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
thumbnail: pathThumbnail!,
full: path!,
type: .video
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
}
@ -282,6 +303,10 @@ class ConversationViewModel: ObservableObject {
reactionsTmp.append(chatMessageReaction.body)
})
if !attachmentNameList.isEmpty {
attachmentNameList = String(attachmentNameList.dropFirst(2))
}
if eventLog.chatMessage != nil {
conversationMessage.append(
Message(
@ -292,6 +317,7 @@ class ConversationViewModel: ObservableObject {
address: addressCleaned?.asStringUriOnly() ?? "",
isFirstMessage: isFirstMessageTmp,
text: contentText,
attachmentsNames: attachmentNameList,
attachments: attachmentList,
ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "",
reactions: reactionsTmp
@ -334,6 +360,7 @@ class ConversationViewModel: ObservableObject {
var conversationMessagesTmp: [Message] = []
historyEvents.enumerated().reversed().forEach { index, eventLog in
var attachmentNameList: String = ""
var attachmentList: [Attachment] = []
var contentText = ""
@ -350,9 +377,11 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: .image
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
} else {
@ -363,9 +392,11 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
} else if content.type == "video" {
@ -376,10 +407,12 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
thumbnail: pathThumbnail!,
full: path!,
type: .video
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
}
@ -421,6 +454,10 @@ class ConversationViewModel: ObservableObject {
reactionsTmp.append(chatMessageReaction.body)
})
if !attachmentNameList.isEmpty {
attachmentNameList = String(attachmentNameList.dropFirst(2))
}
if eventLog.chatMessage != nil {
conversationMessagesTmp.insert(
Message(
@ -431,6 +468,7 @@ class ConversationViewModel: ObservableObject {
address: addressCleaned?.asStringUriOnly() ?? "",
isFirstMessage: isFirstMessageTmp,
text: contentText,
attachmentsNames: attachmentNameList,
attachments: attachmentList,
ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "",
reactions: reactionsTmp
@ -471,6 +509,7 @@ class ConversationViewModel: ObservableObject {
func getNewMessages(eventLogs: [EventLog]) {
eventLogs.enumerated().forEach { index, eventLog in
var attachmentNameList: String = ""
var attachmentList: [Attachment] = []
var contentText = ""
@ -487,9 +526,11 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: .image
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
} else if content.name != nil && !content.name!.isEmpty {
@ -500,9 +541,11 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
} else if content.type == "video" {
@ -513,10 +556,12 @@ class ConversationViewModel: ObservableObject {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
thumbnail: pathThumbnail!,
full: path!,
type: .video
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
}
@ -573,6 +618,10 @@ class ConversationViewModel: ObservableObject {
reactionsTmp.append(chatMessageReaction.body)
})
if !attachmentNameList.isEmpty {
attachmentNameList = String(attachmentNameList.dropFirst(2))
}
if eventLog.chatMessage != nil {
let message = Message(
id: eventLog.chatMessage?.messageId ?? UUID().uuidString,
@ -582,6 +631,7 @@ class ConversationViewModel: ObservableObject {
address: addressCleaned?.asStringUriOnly() ?? "",
isFirstMessage: isFirstMessageTmp,
text: contentText,
attachmentsNames: attachmentNameList,
attachments: attachmentList,
ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "",
reactions: reactionsTmp
@ -636,6 +686,17 @@ class ConversationViewModel: ObservableObject {
conversationMessagesSection = []
}
func replyToMessage(index: Int) {
coreContext.doOnCoreQueue { _ in
let messageToReplyTmp = self.conversationMessagesSection[0].rows[index]
DispatchQueue.main.async {
withAnimation(.linear(duration: 0.15)) {
self.messageToReply = messageToReplyTmp
}
}
}
}
func sendMessage() {
coreContext.doOnCoreQueue { _ in
//val messageToReplyTo = chatMessageToReplyTo

View file

@ -79,7 +79,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
let dataResult = try Data(contentsOf: urlFile!)
let urlImage = self.saveMedia(name: urlFile!.lastPathComponent, data: dataResult, type: .image)
if urlImage != nil {
let attachment = Attachment(id: UUID().uuidString, url: urlImage!, type: .image)
let attachment = Attachment(id: UUID().uuidString, name: urlFile!.lastPathComponent, url: urlImage!, type: .image)
medias.append(attachment)
}
} catch {
@ -98,7 +98,7 @@ struct PhotoPicker: UIViewControllerRepresentable {
let urlThumbnail = getURLThumbnail(name: urlFile!.lastPathComponent)
if urlImage != nil {
let attachment = Attachment(id: UUID().uuidString, thumbnail: urlThumbnail, full: urlImage!, type: .video)
let attachment = Attachment(id: UUID().uuidString, name: urlFile!.lastPathComponent, thumbnail: urlThumbnail, full: urlImage!, type: .video)
medias.append(attachment)
}
} catch {