diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 9942036c4..74082046b 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -1570,6 +1570,9 @@ }, "Message" : { + }, + "Message copied into clipboard" : { + }, "Messages" : { diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index a5895da25..b771b28ed 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -625,7 +625,7 @@ struct CallView: View { ) DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard" + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" ToastViewModel.shared.displayToast = true } }, label: { diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift b/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift index f9c006e0a..a103ea00d 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift @@ -69,7 +69,7 @@ struct ContactListBottomSheet: View { dismiss() } - ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard" + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" ToastViewModel.shared.displayToast.toggle() } label: { diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 085d3eb3e..3e0245555 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -127,6 +127,7 @@ struct ContentView: View { self.index = 0 historyViewModel.displayedCall = nil conversationViewModel.displayedConversation = nil + meetingViewModel.displayedMeeting = nil }, label: { VStack { Image("address-book") @@ -171,6 +172,7 @@ struct ContentView: View { self.index = 1 contactViewModel.indexDisplayedFriend = nil conversationViewModel.displayedConversation = nil + meetingViewModel.displayedMeeting = nil if historyListViewModel.missedCallsCount > 0 { historyListViewModel.resetMissedCallsCount() } @@ -219,6 +221,7 @@ struct ContentView: View { self.index = 2 historyViewModel.displayedCall = nil contactViewModel.indexDisplayedFriend = nil + meetingViewModel.displayedMeeting = nil }, label: { VStack { Image("chat-teardrop-text") @@ -275,6 +278,11 @@ struct ContentView: View { } VStack(spacing: 0) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 1) + if searchIsActive == false { HStack { Image("profile-image-example") @@ -392,6 +400,7 @@ struct ContentView: View { .padding(.top, 2.5) .padding(.bottom, 2.5) .background(Color.orangeMain500) + .roundedCorner(10, corners: [.bottomRight, .bottomLeft]) } else { HStack { Button { @@ -523,6 +532,7 @@ struct ContentView: View { .padding(.horizontal) .padding(.bottom, 5) .background(Color.orangeMain500) + .roundedCorner(10, corners: [.bottomRight, .bottomLeft]) } if self.index == 0 { @@ -592,6 +602,7 @@ struct ContentView: View { self.index = 0 historyViewModel.displayedCall = nil conversationViewModel.displayedConversation = nil + meetingViewModel.displayedMeeting = nil }, label: { VStack { Image("address-book") @@ -638,6 +649,7 @@ struct ContentView: View { self.index = 1 contactViewModel.indexDisplayedFriend = nil conversationViewModel.displayedConversation = nil + meetingViewModel.displayedMeeting = nil if historyListViewModel.missedCallsCount > 0 { historyListViewModel.resetMissedCallsCount() } @@ -688,6 +700,7 @@ struct ContentView: View { self.index = 2 historyViewModel.displayedCall = nil contactViewModel.indexDisplayedFriend = nil + meetingViewModel.displayedMeeting = nil }, label: { VStack { Image("chat-teardrop-text") diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index 6f691cd00..bef6589ca 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -70,52 +70,89 @@ struct ChatBubbleView: View { } VStack(alignment: message.isOutgoing ? .trailing : .leading) { - if !message.attachments.isEmpty { - messageAttachments() - } - - if !message.text.isEmpty { - Text(message.text) - .foregroundStyle(Color.grayMain2c700) - .default_text_style(styleSize: 16) - } - - HStack(alignment: .center) { - Text(conversationViewModel.getMessageTime(startDate: message.dateReceived)) - .foregroundStyle(Color.grayMain2c500) - .default_text_style_300(styleSize: 14) - .padding(.top, 1) + VStack(alignment: message.isOutgoing ? .trailing : .leading) { + if !message.attachments.isEmpty { + messageAttachments() + } - if (conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup) || message.isOutgoing { - if message.status == .sending { - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500)) - .frame(width: 15, height: 15) - .padding(.top, 1) - } else if message.status != nil { - Image(conversationViewModel.getImageIMDN(status: message.status!)) - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.orangeMain500) - .frame(width: 15, height: 15) - .padding(.top, 1) + if !message.text.isEmpty { + Text(message.text) + .foregroundStyle(Color.grayMain2c700) + .default_text_style(styleSize: 16) + } + + HStack(alignment: .center) { + Text(conversationViewModel.getMessageTime(startDate: message.dateReceived)) + .foregroundStyle(Color.grayMain2c500) + .default_text_style_300(styleSize: 14) + .padding(.top, 1) + + if (conversationViewModel.displayedConversation != nil && conversationViewModel.displayedConversation!.isGroup) || message.isOutgoing { + if message.status == .sending { + ProgressView() + .progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500)) + .frame(width: 15, height: 15) + .padding(.top, 1) + } else if message.status != nil { + Image(conversationViewModel.getImageIMDN(status: message.status!)) + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 15, height: 15) + .padding(.top, 1) + } } } + .padding(.top, -4) + } + .padding(.all, 15) + .background(message.isOutgoing ? Color.orangeMain100 : Color.grayMain2c100) + .clipShape(RoundedRectangle(cornerRadius: 3)) + .roundedCorner( + 16, + corners: message.isOutgoing && message.isFirstMessage ? [.topLeft, .topRight, .bottomLeft] : + (!message.isOutgoing && message.isFirstMessage ? [.topRight, .bottomRight, .bottomLeft] : [.allCorners])) + + if !message.reactions.isEmpty { + HStack { + ForEach(0.. 50 ? 50 : iconSize) } - .padding(.horizontal, 5) + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.ownReaction == "👍" ? Color.gray200 : .white) + .cornerRadius(10) Button { + conversationViewModel.sendReaction(emoji: "❤️") } label: { Text("❤️") .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } - .padding(.horizontal, 5) + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.ownReaction == "❤️" ? Color.gray200 : .white) + .cornerRadius(10) Button { + conversationViewModel.sendReaction(emoji: "😂") } label: { Text("😂") .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } - .padding(.horizontal, 5) + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.ownReaction == "😂" ? Color.gray200 : .white) + .cornerRadius(10) Button { + conversationViewModel.sendReaction(emoji: "😮") } label: { Text("😮") .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } - .padding(.horizontal, 5) + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.ownReaction == "😮" ? Color.gray200 : .white) + .cornerRadius(10) Button { + conversationViewModel.sendReaction(emoji: "😢") } label: { Text("😢") .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } - .padding(.horizontal, 5) + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.ownReaction == "😢" ? Color.gray200 : .white) + .cornerRadius(10) Button { } label: { @@ -635,22 +651,33 @@ struct ConversationFragment: View { Divider() - Button { - } label: { - HStack { - Text("menu_copy_chat_message") - .default_text_style(styleSize: 15) - Spacer() - Image("copy") - .resizable() - .frame(width: 20, height: 20, alignment: .leading) + if !conversationViewModel.selectedMessage!.text.isEmpty { + Button { + UIPasteboard.general.setValue( + conversationViewModel.selectedMessage!.text, + 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) } - .padding(.vertical, 5) - .padding(.horizontal, 20) + + Divider() } - Divider() - Button { } label: { HStack { @@ -698,8 +725,6 @@ struct ConversationFragment: View { .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) .shadow(color: .black.opacity(0.1), radius: 10) } - - Spacer() } .frame(maxWidth: .infinity) .background(.gray.opacity(0.1)) diff --git a/Linphone/UI/Main/Conversations/Model/Message.swift b/Linphone/UI/Main/Conversations/Model/Message.swift index adbd9bc78..97ea3f44d 100644 --- a/Linphone/UI/Main/Conversations/Model/Message.swift +++ b/Linphone/UI/Main/Conversations/Model/Message.swift @@ -73,6 +73,8 @@ public struct Message: Identifiable, Hashable { public var attachments: [Attachment] public var recording: Recording? public var replyMessage: ReplyMessage? + public var ownReaction: String + public var reactions: [String] public init( id: String, @@ -85,7 +87,9 @@ public struct Message: Identifiable, Hashable { text: String = "", attachments: [Attachment] = [], recording: Recording? = nil, - replyMessage: ReplyMessage? = nil + replyMessage: ReplyMessage? = nil, + ownReaction: String = "", + reactions: [String] = [] ) { self.id = id self.status = status @@ -98,6 +102,8 @@ public struct Message: Identifiable, Hashable { self.attachments = attachments self.recording = recording self.replyMessage = replyMessage + self.ownReaction = ownReaction + self.reactions = reactions } public static func makeMessage( @@ -131,7 +137,9 @@ public struct Message: Identifiable, Hashable { text: draft.text, attachments: attachments, recording: draft.recording, - replyMessage: draft.replyMessage + replyMessage: draft.replyMessage, + ownReaction: draft.ownReaction, + reactions: draft.reactions ) } } @@ -144,7 +152,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.id == rhs.id && lhs.status == rhs.status && lhs.isFirstMessage == rhs.isFirstMessage && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions } } @@ -217,6 +225,8 @@ public struct DraftMessage { public let recording: Recording? public let replyMessage: ReplyMessage? public let createdAt: Date + public let ownReaction: String + public let reactions: [String] public init(id: String? = nil, isOutgoing: Bool, @@ -227,7 +237,10 @@ public struct DraftMessage { medias: [Media], recording: Recording?, replyMessage: ReplyMessage?, - createdAt: Date) { + createdAt: Date, + ownReaction: String, + reactions: [String] + ) { self.id = id self.isOutgoing = isOutgoing self.dateReceived = dateReceived @@ -238,6 +251,8 @@ public struct DraftMessage { self.recording = recording self.replyMessage = replyMessage self.createdAt = createdAt + self.ownReaction = ownReaction + self.reactions = reactions } } diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index 69a5baec6..01ce53fd3 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -35,6 +35,7 @@ class ConversationViewModel: ObservableObject { @Published var messageText: String = "" private var chatRoomSuscriptions = Set() + private var chatMessageSuscriptions = Set() @Published var conversationMessagesSection: [MessagesSection] = [] @Published var participantConversationModel: [ContactAvatarModel] = [] @@ -60,8 +61,73 @@ class ConversationViewModel: ObservableObject { } } + func addChatMessageDelegate(message: ChatMessage) { + coreContext.doOnCoreQueue { _ in + if self.displayedConversation != nil { + /* + self.chatMessageSuscriptions.insert(message.publisher?.onMsgStateChanged?.postOnCoreQueue {(cbValue: (message: ChatMessage, state: ChatMessage.State)) in + var statusTmp: Message.Status? = .sending + switch cbValue.message.state { + case .InProgress: + statusTmp = .sending + case .Delivered: + statusTmp = .sent + case .DeliveredToUser: + statusTmp = .received + case .Displayed: + statusTmp = .read + default: + statusTmp = nil + } + + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == message.messageId}) + + DispatchQueue.main.async { + if indexMessage != nil { + self.objectWillChange.send() + self.conversationMessagesSection[0].rows[indexMessage!].status = statusTmp + } + } + }) + */ + + self.chatMessageSuscriptions.insert(message.publisher?.onNewMessageReaction?.postOnCoreQueue {(cbValue: (message: ChatMessage, reaction: ChatMessageReaction)) in + + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == message.messageId}) + var reactionsTmp: [String] = [] + cbValue.message.reactions.forEach({ chatMessageReaction in + reactionsTmp.append(chatMessageReaction.body) + }) + + DispatchQueue.main.async { + if indexMessage != nil { + self.objectWillChange.send() + self.conversationMessagesSection[0].rows[indexMessage!].reactions = reactionsTmp + } + } + }) + + self.chatMessageSuscriptions.insert(message.publisher?.onReactionRemoved?.postOnCoreQueue {(cbValue: (message: ChatMessage, address: Address)) in + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.id == message.messageId}) + var reactionsTmp: [String] = [] + cbValue.message.reactions.forEach({ chatMessageReaction in + reactionsTmp.append(chatMessageReaction.body) + }) + + DispatchQueue.main.async { + if indexMessage != nil { + self.objectWillChange.send() + self.conversationMessagesSection[0].rows[indexMessage!].reactions = reactionsTmp + } + } + }) + } + } + } + func removeConversationDelegate() { self.chatRoomSuscriptions.removeAll() + self.chatMessageSuscriptions.removeAll() } func getHistorySize() { @@ -210,19 +276,28 @@ class ConversationViewModel: ObservableObject { statusTmp = nil } + var reactionsTmp: [String] = [] + eventLog.chatMessage?.reactions.forEach({ chatMessageReaction in + reactionsTmp.append(chatMessageReaction.body) + }) + if eventLog.chatMessage != nil { conversationMessage.append( Message( - id: UUID().uuidString, + id: eventLog.chatMessage?.messageId ?? UUID().uuidString, status: statusTmp, isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, dateReceived: eventLog.chatMessage?.time ?? 0, address: addressCleaned?.asStringUriOnly() ?? "", isFirstMessage: isFirstMessageTmp, text: contentText, - attachments: attachmentList + attachments: attachmentList, + ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", + reactions: reactionsTmp ) ) + + self.addChatMessageDelegate(message: eventLog.chatMessage!) } } @@ -324,19 +399,28 @@ class ConversationViewModel: ObservableObject { statusTmp = nil } + var reactionsTmp: [String] = [] + eventLog.chatMessage?.reactions.forEach({ chatMessageReaction in + reactionsTmp.append(chatMessageReaction.body) + }) + if eventLog.chatMessage != nil { conversationMessagesTmp.insert( Message( - id: UUID().uuidString, + id: eventLog.chatMessage?.messageId ?? UUID().uuidString, status: statusTmp, isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, dateReceived: eventLog.chatMessage?.time ?? 0, address: addressCleaned?.asStringUriOnly() ?? "", isFirstMessage: isFirstMessageTmp, text: contentText, - attachments: attachmentList + attachments: attachmentList, + ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", + reactions: reactionsTmp ), at: 0 ) + + self.addChatMessageDelegate(message: eventLog.chatMessage!) } } @@ -451,18 +535,27 @@ class ConversationViewModel: ObservableObject { statusTmp = nil } + var reactionsTmp: [String] = [] + eventLog.chatMessage?.reactions.forEach({ chatMessageReaction in + reactionsTmp.append(chatMessageReaction.body) + }) + if eventLog.chatMessage != nil { let message = Message( - id: UUID().uuidString, + id: eventLog.chatMessage?.messageId ?? UUID().uuidString, status: statusTmp, isOutgoing: eventLog.chatMessage?.isOutgoing ?? false, dateReceived: eventLog.chatMessage?.time ?? 0, address: addressCleaned?.asStringUriOnly() ?? "", isFirstMessage: isFirstMessageTmp, text: contentText, - attachments: attachmentList + attachments: attachmentList, + ownReaction: eventLog.chatMessage?.ownReaction?.body ?? "", + reactions: reactionsTmp ) + self.addChatMessageDelegate(message: eventLog.chatMessage!) + DispatchQueue.main.async { if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty @@ -729,6 +822,43 @@ class ConversationViewModel: ObservableObject { } } + func sendReaction(emoji: String) { + coreContext.doOnCoreQueue { _ in + if self.selectedMessage != nil { + Log.info("[ConversationViewModel] Sending reaction \(emoji) to message with ID \(self.selectedMessage!.id)") + let messageToSendReaction = self.displayedConversation!.chatRoom.findMessage(messageId: self.selectedMessage!.id) + if messageToSendReaction != nil { + do { + let reaction = try messageToSendReaction!.createReaction(utf8Reaction: messageToSendReaction?.ownReaction?.body == emoji ? "" : emoji) + reaction.send() + + let indexMessageSelected = self.conversationMessagesSection[0].rows.firstIndex(of: self.selectedMessage!) + + DispatchQueue.main.async { + if indexMessageSelected != nil { + self.conversationMessagesSection[0].rows[indexMessageSelected!].ownReaction = messageToSendReaction?.ownReaction?.body == emoji ? "" : emoji + } + self.selectedMessage = nil + } + } catch { + Log.info("[ConversationViewModel] Error: Can't send reaction \(emoji) to message with ID \(self.selectedMessage!.id)") + } + } + } + } + } + + func resend() { + coreContext.doOnCoreQueue { _ in + if self.selectedMessage != nil { + Log.info("[ConversationViewModel] Re-sending message with ID \(self.selectedMessage!.id)") + let messageToResend = self.displayedConversation!.chatRoom.findMessage(messageId: self.selectedMessage!.id) + if messageToResend != nil { + messageToResend!.send() + } + } + } + } } struct LinphoneCustomEventLog: Hashable { var id = UUID() diff --git a/Linphone/UI/Main/Fragments/ToastView.swift b/Linphone/UI/Main/Fragments/ToastView.swift index d918e4140..de0bbf2b9 100644 --- a/Linphone/UI/Main/Fragments/ToastView.swift +++ b/Linphone/UI/Main/Fragments/ToastView.swift @@ -80,13 +80,20 @@ struct ToastView: View { .default_text_style(styleSize: 15) .padding(8) - case "Success_copied_into_clipboard": + case "Success_address_copied_into_clipboard": Text("SIP address copied into clipboard") .multilineTextAlignment(.center) .foregroundStyle(Color.greenSuccess500) .default_text_style(styleSize: 15) .padding(8) + case "Success_message_copied_into_clipboard": + Text("Message copied into clipboard") + .multilineTextAlignment(.center) + .foregroundStyle(Color.greenSuccess500) + .default_text_style(styleSize: 15) + .padding(8) + case "Info_call_securised": Text("call_can_be_trusted_toast") .multilineTextAlignment(.center) diff --git a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift index 2bbbac2ae..4aa868f20 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift @@ -135,7 +135,7 @@ struct HistoryContactFragment: View { ) } - ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard" + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" ToastViewModel.shared.displayToast.toggle() } label: { diff --git a/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift index 04b1403cf..19705fcd4 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift @@ -155,7 +155,7 @@ struct HistoryListBottomSheet: View { dismiss() } - ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard" + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" ToastViewModel.shared.displayToast.toggle() } label: { diff --git a/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift b/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift index 4115036e5..cf4b7d793 100644 --- a/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift +++ b/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift @@ -185,7 +185,7 @@ struct MeetingFragment: View { ) DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard" + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" ToastViewModel.shared.displayToast = true } }, label: { diff --git a/Linphone/Utils/Avatar.swift b/Linphone/Utils/Avatar.swift index bb7a6d028..a87908184 100644 --- a/Linphone/Utils/Avatar.swift +++ b/Linphone/Utils/Avatar.swift @@ -58,8 +58,8 @@ struct Avatar: View { Image(contactAvatarModel.presenceStatus == .Online ? "presence-online" : "presence-busy") .resizable() .frame(width: avatarSize/4, height: avatarSize/4) - .padding(.trailing, avatarSize == 50 ? 1 : 3) - .padding(.bottom, avatarSize == 50 ? 1 : 3) + .padding(.trailing, avatarSize == 50 || avatarSize == 35 ? 1 : 3) + .padding(.bottom, avatarSize == 50 || avatarSize == 35 ? 1 : 3) } } }