diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 2adeced6a..63dbe5d6f 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -790,6 +790,9 @@ }, "Cancelled" : { + }, + "Categories" : { + }, "Ce mode vous permet d’être interopérable avec d’autres services SIP.\nVos communications seront chiffrées de point à point. " : { @@ -1653,6 +1656,74 @@ }, "Message copied into clipboard" : { + }, + "message_delivery_info_error_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Error" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "En erreur" + } + } + } + }, + "message_delivery_info_read_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Read" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Lu" + } + } + } + }, + "message_delivery_info_received_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Received" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reçu" + } + } + } + }, + "message_delivery_info_sent_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sent" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Envoyé" + } + } + } }, "message_forwarded_label" : { "extractionState" : "manual", diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index c7b36fd29..ccff001b5 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -187,6 +187,10 @@ struct ChatBubbleView: View { } } } + .onTapGesture { + conversationViewModel.selectedMessageToDisplayDetails = eventLogMessage + conversationViewModel.prepareBottomSheetForDeliveryStatus() + } .padding(.top, -4) } .padding(.all, 15) diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 7027ad705..16619df4e 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -25,6 +25,7 @@ import UniformTypeIdentifiers struct ConversationFragment: View { @State private var orientation = UIDevice.current.orientation + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @ObservedObject var contactsManager = ContactsManager.shared @@ -54,374 +55,537 @@ struct ConversationFragment: View { @Binding var isShowConversationFragment: Bool + @State private var selectedCategoryIndex = 0 + var body: some View { NavigationView { GeometryReader { geometry in - ZStack { - VStack(spacing: 1) { - if conversationViewModel.displayedConversation != nil { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 0) - - HStack { - if (!(orientation == .landscapeLeft || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)) || isShowConversationFragment { - Image("caret-left") - .renderingMode(.template) + if #available(iOS 16.0, *), idiom != .pad { + innerView(geometry: geometry) + .background(.white) + .navigationBarHidden(true) + .onRotate { newOrientation in + orientation = newOrientation + } + .onAppear { + conversationViewModel.addConversationDelegate() + } + .onDisappear { + conversationViewModel.removeConversationDelegate() + } + .sheet(isPresented: $conversationViewModel.isShowSelectedMessageToDisplayDetailsBottomSheet, onDismiss: { + conversationViewModel.isShowSelectedMessageToDisplayDetailsBottomSheet = false + }, content: { + imdnSheet() + .presentationDetents([.medium]) + .presentationDragIndicator(.visible) + }) + .sheet(isPresented: $isShowPhotoLibrary, onDismiss: { + isShowPhotoLibrary = false + }, content: { + PhotoPicker(filter: nil, limit: conversationViewModel.maxMediaCount - conversationViewModel.mediasToSend.count) { results in + PhotoPicker.convertToAttachmentArray(fromResults: results) { mediasOrNil, errorOrNil in + if let error = errorOrNil { + print(error) + } + + if let medias = mediasOrNil { + conversationViewModel.mediasToSend.append(contentsOf: medias) + } + + self.mediasIsLoading = false + } + } + .edgesIgnoringSafeArea(.all) + }) + .fullScreenCover(isPresented: $isShowCamera) { + ImagePicker(conversationViewModel: conversationViewModel, selectedMedia: self.$conversationViewModel.mediasToSend) + .edgesIgnoringSafeArea(.all) + } + } else { + innerView(geometry: geometry) + .background(.white) + .navigationBarHidden(true) + .onRotate { newOrientation in + orientation = newOrientation + } + .onAppear { + conversationViewModel.addConversationDelegate() + } + .onDisappear { + conversationViewModel.removeConversationDelegate() + } + .halfSheet(showSheet: $conversationViewModel.isShowSelectedMessageToDisplayDetailsBottomSheet) { + imdnSheet() + } onDismiss: { + conversationViewModel.isShowSelectedMessageToDisplayDetailsBottomSheet = false + } + .sheet(isPresented: $isShowPhotoLibrary, onDismiss: { + isShowPhotoLibrary = false + }, content: { + PhotoPicker(filter: nil, limit: conversationViewModel.maxMediaCount - conversationViewModel.mediasToSend.count) { results in + PhotoPicker.convertToAttachmentArray(fromResults: results) { mediasOrNil, errorOrNil in + if let error = errorOrNil { + print(error) + } + + if let medias = mediasOrNil { + conversationViewModel.mediasToSend.append(contentsOf: medias) + } + + self.mediasIsLoading = false + } + } + .edgesIgnoringSafeArea(.all) + }) + .fullScreenCover(isPresented: $isShowCamera) { + ImagePicker(conversationViewModel: conversationViewModel, selectedMedia: self.$conversationViewModel.mediasToSend) + } + } + } + } + .navigationViewStyle(.stack) + } + + //swiftlint:disable cyclomatic_complexity + //swiftlint:disable function_body_length + @ViewBuilder + func innerView(geometry: GeometryProxy) -> some View { + ZStack { + VStack(spacing: 1) { + if conversationViewModel.displayedConversation != nil { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 0) + + HStack { + if (!(orientation == .landscapeLeft || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)) || isShowConversationFragment { + Image("caret-left") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, 4) + .padding(.leading, -10) + .onTapGesture { + withAnimation { + if isShowConversationFragment { + isShowConversationFragment = false + } + conversationViewModel.displayedConversation = nil + } + } + } + + Avatar(contactAvatarModel: conversationViewModel.displayedConversation!.avatarModel, avatarSize: 50) + .padding(.top, 4) + + Text(conversationViewModel.displayedConversation!.subject) + .default_text_style(styleSize: 16) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 4) + .lineLimit(1) + + Spacer() + + Button { + } label: { + Image("phone") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, 4) + } + + Menu { + Button { + isMenuOpen = false + } label: { + HStack { + Text("See contact") + Spacer() + Image("user-circle") .resizable() - .foregroundStyle(Color.orangeMain500) .frame(width: 25, height: 25, alignment: .leading) .padding(.all, 10) - .padding(.top, 4) - .padding(.leading, -10) - .onTapGesture { - withAnimation { - if isShowConversationFragment { - isShowConversationFragment = false + } + } + + Button { + isMenuOpen = false + } label: { + HStack { + Text("Copy SIP address") + Spacer() + Image("copy") + .resizable() + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + } + + Button(role: .destructive) { + isMenuOpen = false + } label: { + HStack { + Text("Delete history") + Spacer() + Image("trash-simple-red") + .resizable() + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + } + } label: { + Image("dots-three-vertical") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, 4) + } + .onTapGesture { + isMenuOpen = true + } + } + .frame(maxWidth: .infinity) + .frame(height: 50) + .padding(.horizontal) + .padding(.bottom, 4) + .background(.white) + + if #available(iOS 16.0, *) { + ZStack(alignment: .bottomTrailing) { + UIList( + viewModel: viewModel, + paginationState: paginationState, + conversationViewModel: conversationViewModel, + conversationsListViewModel: conversationsListViewModel, + geometryProxy: geometry, + sections: conversationViewModel.conversationMessagesSection + ) + } + .onAppear { + conversationViewModel.getMessages() + } + .onDisappear { + conversationViewModel.resetMessage() + } + } else { + ScrollViewReader { proxy in + ZStack(alignment: .bottomTrailing) { + List { + if conversationViewModel.conversationMessagesSection.first != nil { + let counter = conversationViewModel.conversationMessagesSection.first!.rows.count + ForEach(0.. conversationViewModel.conversationMessagesSection.first!.rows.count { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + conversationViewModel.getOldMessages() + } + } + + if index == 0 { + displayFloatingButton = false + conversationViewModel.markAsRead() + conversationsListViewModel.computeChatRoomsList(filter: "") + } } - conversationViewModel.displayedConversation = nil + .onDisappear { + if index == 0 { + displayFloatingButton = true + } + } + } + } + } + .scaleEffect(x: 1, y: -1, anchor: .center) + .listStyle(.plain) + .onAppear { + conversationViewModel.markAsRead() + conversationsListViewModel.computeChatRoomsList(filter: "") + } + + if displayFloatingButton { + Button { + if conversationViewModel.conversationMessagesSection.first != nil && conversationViewModel.conversationMessagesSection.first!.rows.first != nil { + withAnimation { + proxy.scrollTo(conversationViewModel.conversationMessagesSection.first!.rows.first!.message.id) } } + } label: { + ZStack { + + Image("caret-down") + .renderingMode(.template) + .foregroundStyle(.white) + .padding() + .background(Color.orangeMain500) + .clipShape(Circle()) + .shadow(color: .black.opacity(0.2), radius: 4) + + if conversationViewModel.displayedConversationUnreadMessagesCount > 0 { + VStack { + HStack { + Spacer() + + HStack { + Text( + conversationViewModel.displayedConversationUnreadMessagesCount < 99 + ? String(conversationViewModel.displayedConversationUnreadMessagesCount) + : "99+" + ) + .foregroundStyle(.white) + .default_text_style(styleSize: 10) + .lineLimit(1) + + } + .frame(width: 18, height: 18) + .background(Color.redDanger500) + .cornerRadius(50) + } + + Spacer() + } + } + } + + } + .frame(width: 50, height: 50) + .padding() } - - Avatar(contactAvatarModel: conversationViewModel.displayedConversation!.avatarModel, avatarSize: 50) - .padding(.top, 4) - - Text(conversationViewModel.displayedConversation!.subject) - .default_text_style(styleSize: 16) + } + .onAppear { + conversationViewModel.getMessages() + } + .onDisappear { + conversationViewModel.resetMessage() + } + } + } + + if conversationViewModel.messageToReply != nil { + ZStack(alignment: .top) { + HStack { + VStack { + ( + Text("conversation_reply_to_message_title") + + Text("**\(conversationViewModel.participantConversationModel.first(where: {$0.address == conversationViewModel.messageToReply!.message.address})?.name ?? "")**")) + .default_text_style_300(styleSize: 15) .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 4) + .padding(.bottom, 1) .lineLimit(1) - - Spacer() - - Button { - } label: { - Image("phone") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - .padding(.top, 4) - } - - Menu { - Button { - isMenuOpen = false - } label: { - HStack { - Text("See contact") - Spacer() - Image("user-circle") - .resizable() - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - } - } - Button { - isMenuOpen = false - } label: { - HStack { - Text("Copy SIP address") - Spacer() - Image("copy") - .resizable() - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - } + if conversationViewModel.messageToReply!.message.text.isEmpty { + Text(conversationViewModel.messageToReply!.message.attachmentsNames) + .default_text_style_300(styleSize: 15) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } else { + Text("\(conversationViewModel.messageToReply!.message.text)") + .default_text_style_300(styleSize: 15) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) } - - Button(role: .destructive) { - isMenuOpen = false - } label: { - HStack { - Text("Delete history") - Spacer() - Image("trash-simple-red") - .resizable() - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - } - } - } label: { - Image("dots-three-vertical") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - .padding(.top, 4) - } - .onTapGesture { - isMenuOpen = true } } .frame(maxWidth: .infinity) - .frame(height: 50) - .padding(.horizontal) - .padding(.bottom, 4) - .background(.white) + .padding(.all, 20) + .background(Color.gray100) - if #available(iOS 16.0, *) { - ZStack(alignment: .bottomTrailing) { - UIList( - viewModel: viewModel, - paginationState: paginationState, - conversationViewModel: conversationViewModel, - conversationsListViewModel: conversationsListViewModel, - geometryProxy: geometry, - sections: conversationViewModel.conversationMessagesSection - ) - } - .onAppear { - conversationViewModel.getMessages() - } - .onDisappear { - conversationViewModel.resetMessage() - } - } else { - ScrollViewReader { proxy in - ZStack(alignment: .bottomTrailing) { - List { - if conversationViewModel.conversationMessagesSection.first != nil { - let counter = conversationViewModel.conversationMessagesSection.first!.rows.count - ForEach(0.. conversationViewModel.conversationMessagesSection.first!.rows.count { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - conversationViewModel.getOldMessages() - } - } - - if index == 0 { - displayFloatingButton = false - conversationViewModel.markAsRead() - conversationsListViewModel.computeChatRoomsList(filter: "") - } - } - .onDisappear { - if index == 0 { - displayFloatingButton = true - } - } - } - } - } - .scaleEffect(x: 1, y: -1, anchor: .center) - .listStyle(.plain) - .onAppear { - conversationViewModel.markAsRead() - conversationsListViewModel.computeChatRoomsList(filter: "") - } - - if displayFloatingButton { - Button { - if conversationViewModel.conversationMessagesSection.first != nil && conversationViewModel.conversationMessagesSection.first!.rows.first != nil { - withAnimation { - proxy.scrollTo(conversationViewModel.conversationMessagesSection.first!.rows.first!.message.id) - } - } - } label: { - ZStack { - - Image("caret-down") - .renderingMode(.template) - .foregroundStyle(.white) - .padding() - .background(Color.orangeMain500) - .clipShape(Circle()) - .shadow(color: .black.opacity(0.2), radius: 4) - - if conversationViewModel.displayedConversationUnreadMessagesCount > 0 { - VStack { - HStack { - Spacer() - - HStack { - Text( - conversationViewModel.displayedConversationUnreadMessagesCount < 99 - ? String(conversationViewModel.displayedConversationUnreadMessagesCount) - : "99+" - ) - .foregroundStyle(.white) - .default_text_style(styleSize: 10) - .lineLimit(1) - - } - .frame(width: 18, height: 18) - .background(Color.redDanger500) - .cornerRadius(50) - } - - Spacer() - } - } - } - - } - .frame(width: 50, height: 50) - .padding() - } + HStack { + Spacer() + + Button(action: { + withAnimation { + conversationViewModel.messageToReply = nil } - .onAppear { - conversationViewModel.getMessages() - } - .onDisappear { - conversationViewModel.resetMessage() - } - } + }, label: { + Image("x") + .resizable() + .frame(width: 30, height: 30, alignment: .leading) + .padding(.all, 10) + }) } - - if conversationViewModel.messageToReply != nil { - ZStack(alignment: .top) { - HStack { - VStack { - ( - Text("conversation_reply_to_message_title") - + Text("**\(conversationViewModel.participantConversationModel.first(where: {$0.address == conversationViewModel.messageToReply!.message.address})?.name ?? "")**")) - .default_text_style_300(styleSize: 15) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.bottom, 1) - .lineLimit(1) - - if conversationViewModel.messageToReply!.message.text.isEmpty { - Text(conversationViewModel.messageToReply!.message.attachmentsNames) - .default_text_style_300(styleSize: 15) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - } else { - Text("\(conversationViewModel.messageToReply!.message.text)") - .default_text_style_300(styleSize: 15) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - } - } - } - .frame(maxWidth: .infinity) - .padding(.all, 20) - .background(Color.gray100) - + } + .transition(.move(edge: .bottom)) + } + + if !conversationViewModel.mediasToSend.isEmpty || mediasIsLoading { + ZStack(alignment: .top) { + HStack { + if mediasIsLoading { 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 { - if mediasIsLoading { - HStack { - Spacer() - - ProgressView() - - Spacer() - } - .frame(height: 120) - } + ProgressView() - if !mediasIsLoading { - LazyVGrid(columns: [ - GridItem(.adaptive(minimum: 100), spacing: 1) - ], spacing: 3) { - ForEach(conversationViewModel.mediasToSend, id: \.id) { attachment in + Spacer() + } + .frame(height: 120) + } + + if !mediasIsLoading { + LazyVGrid(columns: [ + GridItem(.adaptive(minimum: 100), spacing: 1) + ], spacing: 3) { + ForEach(conversationViewModel.mediasToSend, id: \.id) { attachment in + ZStack { + Rectangle() + .fill(Color(.white)) + .frame(width: 100, height: 100) + + AsyncImage(url: attachment.thumbnail) { image in ZStack { - Rectangle() - .fill(Color(.white)) - .frame(width: 100, height: 100) + image + .resizable() + .interpolation(.medium) + .aspectRatio(contentMode: .fill) - AsyncImage(url: attachment.thumbnail) { image in - ZStack { - image - .resizable() - .interpolation(.medium) - .aspectRatio(contentMode: .fill) - - if attachment.type == .video { - Image("play-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 40, height: 40, alignment: .leading) - } - } - } placeholder: { - ProgressView() - } - .layoutPriority(-1) - .onTapGesture { - if conversationViewModel.mediasToSend.count == 1 { - withAnimation { - conversationViewModel.mediasToSend.removeAll() - } - } else { - guard let index = self.conversationViewModel.mediasToSend.firstIndex(of: attachment) else { return } - self.conversationViewModel.mediasToSend.remove(at: index) - } + if attachment.type == .video { + Image("play-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 40, height: 40, alignment: .leading) } } - .clipShape(RoundedRectangle(cornerRadius: 4)) - .contentShape(Rectangle()) + } placeholder: { + ProgressView() + } + .layoutPriority(-1) + .onTapGesture { + if conversationViewModel.mediasToSend.count == 1 { + withAnimation { + conversationViewModel.mediasToSend.removeAll() + } + } else { + guard let index = self.conversationViewModel.mediasToSend.firstIndex(of: attachment) else { return } + self.conversationViewModel.mediasToSend.remove(at: index) + } } } - .frame( - width: geometry.size.width > 0 && CGFloat(102 * conversationViewModel.mediasToSend.count) > geometry.size.width - 20 - ? 102 * floor(CGFloat(geometry.size.width - 20) / 102) - : CGFloat(102 * conversationViewModel.mediasToSend.count) - ) + .clipShape(RoundedRectangle(cornerRadius: 4)) + .contentShape(Rectangle()) } } - .frame(maxWidth: .infinity) - .padding(.all, conversationViewModel.mediasToSend.isEmpty ? 0 : 10) - .background(Color.gray100) + .frame( + width: geometry.size.width > 0 && CGFloat(102 * conversationViewModel.mediasToSend.count) > geometry.size.width - 20 + ? 102 * floor(CGFloat(geometry.size.width - 20) / 102) + : CGFloat(102 * conversationViewModel.mediasToSend.count) + ) + } + } + .frame(maxWidth: .infinity) + .padding(.all, conversationViewModel.mediasToSend.isEmpty ? 0 : 10) + .background(Color.gray100) + + if !mediasIsLoading { + HStack { + Spacer() - if !mediasIsLoading { - HStack { - Spacer() - - Button(action: { - withAnimation { - conversationViewModel.mediasToSend.removeAll() - } - }, label: { - Image("x") - .resizable() - .frame(width: 30, height: 30, alignment: .leading) - .padding(.all, 10) - }) + Button(action: { + withAnimation { + conversationViewModel.mediasToSend.removeAll() } + }, label: { + Image("x") + .resizable() + .frame(width: 30, height: 30, alignment: .leading) + .padding(.all, 10) + }) + } + } + } + .transition(.move(edge: .bottom)) + } + + HStack(spacing: 0) { + Button { + } label: { + Image("smiley") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 28, height: 28, alignment: .leading) + .padding(.all, 6) + .padding(.top, 4) + } + .padding(.horizontal, isMessageTextFocused ? 0 : 2) + + Button { + self.isShowPhotoLibrary = true + self.mediasIsLoading = true + } label: { + Image("paperclip") + .renderingMode(.template) + .resizable() + .foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500) + .frame(width: isMessageTextFocused ? 0 : 28, height: isMessageTextFocused ? 0 : 28, alignment: .leading) + .padding(.all, isMessageTextFocused ? 0 : 6) + .padding(.top, 4) + .disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading) + } + .padding(.horizontal, isMessageTextFocused ? 0 : 2) + + Button { + self.isShowCamera = true + } label: { + Image("camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500) + .frame(width: isMessageTextFocused ? 0 : 28, height: isMessageTextFocused ? 0 : 28, alignment: .leading) + .padding(.all, isMessageTextFocused ? 0 : 6) + .padding(.top, 4) + .disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading) + } + .padding(.horizontal, isMessageTextFocused ? 0 : 2) + + HStack { + if #available(iOS 16.0, *) { + TextField("Say something...", text: $conversationViewModel.messageText, axis: .vertical) + .default_text_style(styleSize: 15) + .focused($isMessageTextFocused) + .padding(.vertical, 5) + } else { + ZStack(alignment: .leading) { + TextEditor(text: $conversationViewModel.messageText) + .multilineTextAlignment(.leading) + .frame(maxHeight: 160) + .fixedSize(horizontal: false, vertical: true) + .default_text_style(styleSize: 15) + .focused($isMessageTextFocused) + + if conversationViewModel.messageText.isEmpty { + Text("Say something...") + .padding(.leading, 4) + .opacity(conversationViewModel.messageText.isEmpty ? 1 : 0) + .foregroundStyle(Color.gray300) + .default_text_style(styleSize: 15) } } - .transition(.move(edge: .bottom)) + .onTapGesture { + isMessageTextFocused = true + } } - HStack(spacing: 0) { + if conversationViewModel.messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty { Button { } label: { - Image("smiley") + Image("microphone") .renderingMode(.template) .resizable() .foregroundStyle(Color.grayMain2c500) @@ -429,383 +593,313 @@ struct ConversationFragment: View { .padding(.all, 6) .padding(.top, 4) } - .padding(.horizontal, isMessageTextFocused ? 0 : 2) - + } else { Button { - self.isShowPhotoLibrary = true - self.mediasIsLoading = true + if conversationViewModel.displayedConversationHistorySize > 0 { + NotificationCenter.default.post(name: .onScrollToBottom, object: nil) + } + conversationViewModel.sendMessage() } label: { - Image("paperclip") + Image("paper-plane-tilt") .renderingMode(.template) .resizable() - .foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500) - .frame(width: isMessageTextFocused ? 0 : 28, height: isMessageTextFocused ? 0 : 28, alignment: .leading) - .padding(.all, isMessageTextFocused ? 0 : 6) + .foregroundStyle(Color.orangeMain500) + .frame(width: 28, height: 28, alignment: .leading) + .padding(.all, 6) .padding(.top, 4) - .disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading) + .rotationEffect(.degrees(45)) } - .padding(.horizontal, isMessageTextFocused ? 0 : 2) - - Button { - self.isShowCamera = true - } label: { - Image("camera") - .renderingMode(.template) - .resizable() - .foregroundStyle(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading ? Color.grayMain2c300 : Color.grayMain2c500) - .frame(width: isMessageTextFocused ? 0 : 28, height: isMessageTextFocused ? 0 : 28, alignment: .leading) - .padding(.all, isMessageTextFocused ? 0 : 6) - .padding(.top, 4) - .disabled(conversationViewModel.maxMediaCount <= conversationViewModel.mediasToSend.count || mediasIsLoading) - } - .padding(.horizontal, isMessageTextFocused ? 0 : 2) - - HStack { - if #available(iOS 16.0, *) { - TextField("Say something...", text: $conversationViewModel.messageText, axis: .vertical) - .default_text_style(styleSize: 15) - .focused($isMessageTextFocused) - .padding(.vertical, 5) - } else { - ZStack(alignment: .leading) { - TextEditor(text: $conversationViewModel.messageText) - .multilineTextAlignment(.leading) - .frame(maxHeight: 160) - .fixedSize(horizontal: false, vertical: true) - .default_text_style(styleSize: 15) - .focused($isMessageTextFocused) - - if conversationViewModel.messageText.isEmpty { - Text("Say something...") - .padding(.leading, 4) - .opacity(conversationViewModel.messageText.isEmpty ? 1 : 0) - .foregroundStyle(Color.gray300) - .default_text_style(styleSize: 15) - } - } - .onTapGesture { - isMessageTextFocused = true - } - } - - if conversationViewModel.messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty { - Button { - } label: { - Image("microphone") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c500) - .frame(width: 28, height: 28, alignment: .leading) - .padding(.all, 6) - .padding(.top, 4) - } - } else { - Button { - if conversationViewModel.displayedConversationHistorySize > 0 { - NotificationCenter.default.post(name: .onScrollToBottom, object: nil) - } - conversationViewModel.sendMessage() - } label: { - Image("paper-plane-tilt") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.orangeMain500) - .frame(width: 28, height: 28, alignment: .leading) - .padding(.all, 6) - .padding(.top, 4) - .rotationEffect(.degrees(45)) - } - .padding(.trailing, 4) - } - } - .padding(.leading, 15) - .padding(.trailing, 5) - .padding(.vertical, 6) - .frame(maxWidth: .infinity, minHeight: 55) - .background(.white) - .cornerRadius(30) - .overlay( - RoundedRectangle(cornerRadius: 30) - .inset(by: 0.5) - .stroke(Color.gray200, lineWidth: 1.5) - ) - .padding(.horizontal, 4) + .padding(.trailing, 4) } - .frame(maxWidth: .infinity, minHeight: 60) - .padding(.top, 12) - .padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? (isMessageTextFocused ? 12 : 0) : 12) - .padding(.horizontal, 10) - .background(Color.gray100) } + .padding(.leading, 15) + .padding(.trailing, 5) + .padding(.vertical, 6) + .frame(maxWidth: .infinity, minHeight: 55) + .background(.white) + .cornerRadius(30) + .overlay( + RoundedRectangle(cornerRadius: 30) + .inset(by: 0.5) + .stroke(Color.gray200, lineWidth: 1.5) + ) + .padding(.horizontal, 4) } - .blur(radius: conversationViewModel.selectedMessage != nil ? 8 : 0) + .frame(maxWidth: .infinity, minHeight: 60) + .padding(.top, 12) + .padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? (isMessageTextFocused ? 12 : 0) : 12) + .padding(.horizontal, 10) + .background(Color.gray100) + } + } + .blur(radius: conversationViewModel.selectedMessage != nil ? 8 : 0) + + if conversationViewModel.selectedMessage != nil && conversationViewModel.displayedConversation != nil { + let iconSize = ((geometry.size.width - (conversationViewModel.displayedConversation!.isGroup ? 43 : 10) - 10) / 6) - 30 + VStack { + Spacer() - if conversationViewModel.selectedMessage != nil && conversationViewModel.displayedConversation != nil { - let iconSize = ((geometry.size.width - (conversationViewModel.displayedConversation!.isGroup ? 43 : 10) - 10) / 6) - 30 - VStack { - Spacer() + VStack { + HStack { + if conversationViewModel.selectedMessage!.message.isOutgoing { + 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() - } + HStack { + Button { + conversationViewModel.sendReaction(emoji: "👍") + } label: { + Text("👍") + .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } - .frame(maxWidth: .infinity) - .padding(.horizontal, 10) - .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) - .shadow(color: .black.opacity(0.1), radius: 10) + .padding(.horizontal, 8) + .background(conversationViewModel.selectedMessage?.message.ownReaction == "👍" ? Color.gray200 : .white) + .cornerRadius(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) - } - - Divider() - - if !conversationViewModel.selectedMessage!.message.text.isEmpty { - Button { - UIPasteboard.general.setValue( - conversationViewModel.selectedMessage!.message.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) - } - - 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 { - } 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) - - if !conversationViewModel.selectedMessage!.message.isOutgoing { - Spacer() - } + Button { + conversationViewModel.sendReaction(emoji: "❤️") + } label: { + Text("❤️") + .default_text_style(styleSize: iconSize > 50 ? 50 : iconSize) } - .frame(maxWidth: .infinity) - .padding(.horizontal, 10) - .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) - .shadow(color: .black.opacity(0.1), radius: 10) + .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) - .background(.gray.opacity(0.1)) - .onTapGesture { - withAnimation { - conversationViewModel.selectedMessage = nil + .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) + } + + Divider() + + if !conversationViewModel.selectedMessage!.message.text.isEmpty { + Button { + UIPasteboard.general.setValue( + conversationViewModel.selectedMessage!.message.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) + } + + 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 { + } 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) + + if !conversationViewModel.selectedMessage!.message.isOutgoing { + Spacer() } } - .onAppear { - touchFeedback() - } - .onDisappear { - if conversationViewModel.selectedMessage != nil { - conversationViewModel.selectedMessage = nil - } - } - } - - if isShowConversationForwardMessageFragment { - ConversationForwardMessageFragment( - conversationViewModel: conversationViewModel, - conversationsListViewModel: conversationsListViewModel, - conversationForwardMessageViewModel: conversationForwardMessageViewModel, - isShowConversationForwardMessageFragment: $isShowConversationForwardMessageFragment - ) - .zIndex(5) - .transition(.move(edge: .trailing)) + .frame(maxWidth: .infinity) + .padding(.horizontal, 10) + .padding(.leading, conversationViewModel.displayedConversation!.isGroup ? 43 : 0) + .shadow(color: .black.opacity(0.1), radius: 10) } } - .background(.white) - .navigationBarHidden(true) - .onRotate { newOrientation in - orientation = newOrientation + .frame(maxWidth: .infinity) + .background(.gray.opacity(0.1)) + .onTapGesture { + withAnimation { + conversationViewModel.selectedMessage = nil + } } .onAppear { - conversationViewModel.addConversationDelegate() + touchFeedback() } .onDisappear { - conversationViewModel.removeConversationDelegate() - } - .sheet(isPresented: $isShowPhotoLibrary) { - PhotoPicker(filter: nil, limit: conversationViewModel.maxMediaCount - conversationViewModel.mediasToSend.count) { results in - PhotoPicker.convertToAttachmentArray(fromResults: results) { mediasOrNil, errorOrNil in - if let error = errorOrNil { - print(error) - } - - if let medias = mediasOrNil { - conversationViewModel.mediasToSend.append(contentsOf: medias) - } - - self.mediasIsLoading = false - } + if conversationViewModel.selectedMessage != nil { + conversationViewModel.selectedMessage = nil } - .edgesIgnoringSafeArea(.all) - } - .fullScreenCover(isPresented: $isShowCamera) { - ImagePicker(conversationViewModel: conversationViewModel, selectedMedia: self.$conversationViewModel.mediasToSend) - .edgesIgnoringSafeArea(.all) } } + + if isShowConversationForwardMessageFragment { + ConversationForwardMessageFragment( + conversationViewModel: conversationViewModel, + conversationsListViewModel: conversationsListViewModel, + conversationForwardMessageViewModel: conversationForwardMessageViewModel, + isShowConversationForwardMessageFragment: $isShowConversationForwardMessageFragment + ) + .zIndex(5) + .transition(.move(edge: .trailing)) + } } - .navigationViewStyle(.stack) } -} - -struct ScrollOffsetPreferenceKey: PreferenceKey { - static var defaultValue: CGPoint = .zero + //swiftlint:enable cyclomatic_complexity + //swiftlint:enable function_body_length - static func reduce(value: inout CGPoint, nextValue: () -> CGPoint) { + @ViewBuilder + func imdnSheet() -> some View { + VStack { + Picker("Categories", selection: $selectedCategoryIndex) { + ForEach(0..