Add Conversation info fragment

This commit is contained in:
Benoit Martins 2024-11-04 15:37:06 +01:00 committed by QuentinArguillere
parent 0a162390a3
commit baf1fcc0b9
7 changed files with 924 additions and 394 deletions

View file

@ -130,6 +130,7 @@
D78E06302BEA6A4A00CE3783 /* ChangeLayoutBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78E062F2BEA6A4A00CE3783 /* ChangeLayoutBottomSheet.swift */; };
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79622332B1DFE600037EACD /* DialerBottomSheet.swift */; };
D796F2002B0BB61A0041115F /* ToastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */; };
D79F1C162CD3D6AD00FF0A05 /* ConversationInfoFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79F1C152CD3D6AD00FF0A05 /* ConversationInfoFragment.swift */; };
D79F2D0A2C47F4BF0038FA07 /* TouchFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79F2D092C47F4BF0038FA07 /* TouchFeedback.swift */; };
D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FBC2ACC2DB60081A588 /* ContactsView.swift */; };
D7A03FC02ACC2E390081A588 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FBF2ACC2E390081A588 /* HistoryView.swift */; };
@ -319,6 +320,7 @@
D78E062F2BEA6A4A00CE3783 /* ChangeLayoutBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLayoutBottomSheet.swift; sourceTree = "<group>"; };
D79622332B1DFE600037EACD /* DialerBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialerBottomSheet.swift; sourceTree = "<group>"; };
D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewModel.swift; sourceTree = "<group>"; };
D79F1C152CD3D6AD00FF0A05 /* ConversationInfoFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationInfoFragment.swift; sourceTree = "<group>"; };
D79F2D092C47F4BF0038FA07 /* TouchFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchFeedback.swift; sourceTree = "<group>"; };
D7A03FBC2ACC2DB60081A588 /* ContactsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsView.swift; sourceTree = "<group>"; };
D7A03FBF2ACC2E390081A588 /* HistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryView.swift; sourceTree = "<group>"; };
@ -857,6 +859,7 @@
D759CB632C3FBD4200AC35E8 /* StartConversationFragment.swift */,
D7A0ACBA2C415D630043AE79 /* StartGroupConversationFragment.swift */,
D70C82A42C85EDC90087F43F /* ConversationForwardMessageFragment.swift */,
D79F1C152CD3D6AD00FF0A05 /* ConversationInfoFragment.swift */,
);
path = Fragments;
sourceTree = "<group>";
@ -1127,6 +1130,7 @@
D71556362C297DB1009A8CEF /* StartGroupCallFragment.swift in Sources */,
C6A5A9452C10B6270070FEA4 /* OIDAuthStateExtension.swift in Sources */,
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */,
D79F1C162CD3D6AD00FF0A05 /* ConversationInfoFragment.swift in Sources */,
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */,
C67586B02C09F247002E77BF /* URIHandler.swift in Sources */,
C62817282C1B389700DBA646 /* SideMenuAccountRow.swift in Sources */,

View file

@ -874,6 +874,23 @@
},
"Connexion à la réunion" : {
},
"contact_details_actions_title" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Other actions"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Autres actions"
}
}
}
},
"contact_dialog_pick_phone_number_or_sip_address_title" : {
"extractionState" : "manual",
@ -928,6 +945,57 @@
}
}
},
"conversation_action_call" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Call"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Appeler"
}
}
}
},
"conversation_action_configure_ephemeral_messages" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configure ephemeral messages"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Configurer les messages éphémères"
}
}
}
},
"conversation_action_leave_group" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Leave the group"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Quitter le groupe"
}
}
}
},
"conversation_action_mute" : {
"extractionState" : "manual",
"localizations" : {
@ -1432,6 +1500,40 @@
}
}
},
"conversation_info_delete_history_action" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Delete history"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Supprimer l'historique"
}
}
}
},
"conversation_info_menu_go_to_contact" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "See contact profile"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Voir le contact"
}
}
}
},
"conversation_invalid_participant_due_to_security_mode_toast" : {
"extractionState" : "manual",
"localizations" : {
@ -2068,6 +2170,23 @@
},
"Meeting added to iPhone calendar" : {
},
"meeting_schedule_meeting_label" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Meeting"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Réunion"
}
}
}
},
"meeting_waiting_room_join" : {
"extractionState" : "manual",

View file

@ -56,6 +56,7 @@ struct ConversationFragment: View {
@State private var isShowConversationForwardMessageFragment = false
@State private var isShowEphemeralFragment = false
@State private var isShowInfoConversationFragment = false
@Binding var isShowConversationFragment: Bool
@Binding var isShowStartCallGroupPopup: Bool
@ -231,10 +232,17 @@ struct ConversationFragment: View {
}
}
}
.background(.white)
.onTapGesture {
withAnimation {
isShowInfoConversationFragment = true
}
}
.padding(.vertical, 10)
Spacer()
if !conversationViewModel.displayedConversation!.isReadOnly {
Button {
if conversationViewModel.displayedConversation!.isGroup {
isShowStartCallGroupPopup.toggle()
@ -250,10 +258,14 @@ struct ConversationFragment: View {
.padding(.all, 10)
.padding(.top, 4)
}
}
Menu {
Button {
isMenuOpen = false
withAnimation {
isShowInfoConversationFragment = true
}
} label: {
HStack {
Text("conversation_menu_go_to_info")
@ -267,6 +279,7 @@ struct ConversationFragment: View {
}
}
if !conversationViewModel.displayedConversation!.isReadOnly {
Button {
isMenuOpen = false
conversationViewModel.displayedConversation!.toggleMute()
@ -301,6 +314,7 @@ struct ConversationFragment: View {
.padding(.all, 10)
}
}
}
} label: {
Image("dots-three-vertical")
.renderingMode(.template)
@ -454,6 +468,7 @@ struct ConversationFragment: View {
.transition(.move(edge: .bottom))
}
if conversationViewModel.displayedConversation != nil && !conversationViewModel.displayedConversation!.isReadOnly {
if conversationViewModel.messageToReply != nil {
ZStack(alignment: .top) {
HStack {
@ -727,6 +742,7 @@ struct ConversationFragment: View {
.background(Color.gray100)
}
}
}
.blur(radius: conversationViewModel.selectedMessage != nil ? 8 : 0)
if conversationViewModel.selectedMessage != nil && conversationViewModel.displayedConversation != nil {
@ -957,6 +973,19 @@ struct ConversationFragment: View {
.transition(.move(edge: .trailing))
}
if isShowInfoConversationFragment {
ConversationInfoFragment(
conversationViewModel: conversationViewModel,
conversationsListViewModel: conversationsListViewModel,
isMuted: $isMuted,
isShowEphemeralFragment: $isShowEphemeralFragment,
isShowStartCallGroupPopup: $isShowStartCallGroupPopup,
isShowInfoConversationFragment: $isShowInfoConversationFragment
)
.zIndex(5)
.transition(.move(edge: .trailing))
}
if isShowEphemeralFragment {
EphemeralFragment(
conversationViewModel: conversationViewModel,

View file

@ -0,0 +1,369 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of Linphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI
struct ConversationInfoFragment: View {
@State private var orientation = UIDevice.current.orientation
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
@ObservedObject var conversationViewModel: ConversationViewModel
@ObservedObject var conversationsListViewModel: ConversationsListViewModel
@Binding var isMuted: Bool
@Binding var isShowEphemeralFragment: Bool
@Binding var isShowStartCallGroupPopup: Bool
@Binding var isShowInfoConversationFragment: Bool
var body: some View {
NavigationView {
GeometryReader { geometry in
if conversationViewModel.displayedConversation != nil {
VStack(spacing: 1) {
Rectangle()
.foregroundColor(Color.orangeMain500)
.edgesIgnoringSafeArea(.top)
.frame(height: 0)
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
.padding(.top, 2)
.padding(.leading, -10)
.onTapGesture {
withAnimation {
isShowInfoConversationFragment = false
}
}
Spacer()
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 4)
.background(.white)
ScrollView {
VStack(spacing: 0) {
VStack(spacing: 0) {
if #unavailable(iOS 16.0) {
Rectangle()
.foregroundColor(Color.gray100)
.frame(height: 7)
}
VStack(spacing: 0) {
if conversationViewModel.displayedConversation != nil && !conversationViewModel.displayedConversation!.isGroup {
Avatar(contactAvatarModel: conversationViewModel.displayedConversation!.avatarModel, avatarSize: 100)
.padding(.top, 4)
Text(conversationViewModel.displayedConversation!.avatarModel.name)
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 10)
Text(conversationViewModel.displayedConversation!.avatarModel.address)
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
if !conversationViewModel.displayedConversation!.avatarModel.lastPresenceInfo.isEmpty {
Text(conversationViewModel.displayedConversation!.avatarModel.lastPresenceInfo)
.foregroundStyle(conversationViewModel.displayedConversation!.avatarModel.lastPresenceInfo == "Online"
? Color.greenSuccess500
: Color.orangeWarning600)
.multilineTextAlignment(.center)
.default_text_style_300(styleSize: 12)
.frame(maxWidth: .infinity)
.frame(height: 20)
.padding(.top, 5)
} else {
Text("")
.multilineTextAlignment(.center)
.default_text_style_300(styleSize: 12)
.frame(maxWidth: .infinity)
.frame(height: 20)
}
} else {
Avatar(contactAvatarModel: conversationViewModel.displayedConversation!.avatarModel, avatarSize: 100)
.padding(.top, 4)
Text(conversationViewModel.displayedConversation!.avatarModel.name)
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 10)
}
}
.frame(minHeight: 150)
.frame(maxWidth: .infinity)
.padding(.top, 10)
.padding(.bottom, 2)
.background(Color.gray100)
if !conversationViewModel.displayedConversation!.isReadOnly {
HStack {
Spacer()
Button(action: {
conversationViewModel.displayedConversation!.toggleMute()
isMuted = !isMuted
}, label: {
VStack {
HStack(alignment: .center) {
Image(isMuted ? "bell-simple" : "bell-simple-slash")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
}
.padding(16)
.background(Color.grayMain2c200)
.cornerRadius(40)
Text(isMuted ? "conversation_action_unmute" : "conversation_action_mute")
.default_text_style(styleSize: 14)
.frame(minWidth: 80)
.lineLimit(1)
}
})
.frame(width: geometry.size.width / 4)
Spacer()
Button(action: {
if conversationViewModel.displayedConversation!.isGroup {
isShowStartCallGroupPopup.toggle()
} else {
conversationViewModel.displayedConversation!.call()
}
}, label: {
VStack {
HStack(alignment: .center) {
Image("phone")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
}
.padding(16)
.background(Color.grayMain2c200)
.cornerRadius(40)
Text("conversation_action_call")
.default_text_style(styleSize: 14)
.frame(minWidth: 80)
.lineLimit(1)
}
})
.frame(width: geometry.size.width / 4)
Spacer()
Button(action: {
// TODO Create Meeting
}, label: {
VStack {
HStack(alignment: .center) {
Image("video-conference")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
}
.padding(16)
.background(Color.grayMain2c200)
.cornerRadius(40)
Text("meeting_schedule_meeting_label")
.default_text_style(styleSize: 14)
.frame(minWidth: 80)
.lineLimit(1)
}
})
.frame(width: geometry.size.width / 4)
Spacer()
}
.padding(.top, 20)
.padding(.bottom, 10)
.frame(maxWidth: .infinity)
.background(Color.gray100)
}
Text("contact_details_actions_title")
.default_text_style_800(styleSize: 18)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
.padding(.top, 20)
VStack(spacing: 0) {
if !conversationViewModel.displayedConversation!.isReadOnly {
if !conversationViewModel.displayedConversation!.isGroup {
Button(
action: {
},
label: {
HStack {
Image("address-book")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
Text("conversation_info_menu_go_to_contact")
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
)
.frame(height: 60)
Divider()
}
Button(
action: {
withAnimation {
isShowEphemeralFragment = true
}
},
label: {
HStack {
Image("clock-countdown")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
Text("conversation_action_configure_ephemeral_messages")
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
)
.frame(height: 60)
Divider()
if conversationViewModel.displayedConversation!.isGroup {
Button(
action: {
conversationViewModel.displayedConversation!.leave()
conversationViewModel.displayedConversation!.isReadOnly = true
isShowInfoConversationFragment = false
},
label: {
HStack {
Image("sign-out")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
Text("conversation_action_leave_group")
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
)
.frame(height: 60)
Divider()
}
}
Button(
action: {
conversationViewModel.displayedConversation!.deleteChatRoom()
conversationsListViewModel.computeChatRoomsList(filter: "")
conversationViewModel.displayedConversation = nil
},
label: {
HStack {
Image("trash-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.redDanger500)
.frame(width: 25, height: 25)
Text("conversation_info_delete_history_action")
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
)
.frame(height: 60)
}
.padding(.horizontal, 20)
.padding(.vertical, 4)
.background(.white)
.cornerRadius(15)
.padding(.all)
}
.frame(maxWidth: sharedMainViewModel.maxWidth)
}
.frame(maxWidth: .infinity)
.padding(.top, 2)
}
.background(Color.gray100)
}
.background(.white)
.navigationBarHidden(true)
.onRotate { newOrientation in
orientation = newOrientation
}
}
}
}
.navigationViewStyle(.stack)
}
}
#Preview {
ConversationInfoFragment(
conversationViewModel: ConversationViewModel(),
conversationsListViewModel: ConversationsListViewModel(),
isMuted: .constant(false),
isShowEphemeralFragment: .constant(false),
isShowStartCallGroupPopup: .constant(false),
isShowInfoConversationFragment: .constant(true)
)
}

View file

@ -39,7 +39,11 @@ struct ConversationsFragment: View {
conversationsListViewModel: conversationsListViewModel,
showingSheet: $showingSheet
)
.presentationDetents([.fraction(0.4)])
.presentationDetents(
conversationsListViewModel.selectedConversation != nil && !conversationsListViewModel.selectedConversation!.isReadOnly
? [.fraction(0.4)]
: [.fraction(0.1)]
)
}
} else {
ConversationsListFragment(conversationViewModel: conversationViewModel,

View file

@ -54,6 +54,7 @@ struct ConversationsListBottomSheet: View {
Spacer()
if conversationsListViewModel.selectedConversation != nil && !conversationsListViewModel.selectedConversation!.isReadOnly {
Button {
if conversationsListViewModel.selectedConversation != nil {
conversationsListViewModel.markAsReadSelectedConversation()
@ -172,6 +173,7 @@ struct ConversationsListBottomSheet: View {
}
.frame(maxWidth: .infinity)
}
}
Button {
conversationsListViewModel.selectedConversation!.deleteChatRoom()
@ -206,6 +208,7 @@ struct ConversationsListBottomSheet: View {
.padding(.horizontal, 30)
.background(Color.gray100)
if conversationsListViewModel.selectedConversation != nil && !conversationsListViewModel.selectedConversation!.isReadOnly {
VStack {
Divider()
}
@ -214,6 +217,7 @@ struct ConversationsListBottomSheet: View {
Button {
if conversationsListViewModel.selectedConversation != nil {
conversationsListViewModel.selectedConversation!.leave()
conversationsListViewModel.selectedConversation!.isReadOnly = true
}
if #available(iOS 16.0, *) {
@ -244,6 +248,7 @@ struct ConversationsListBottomSheet: View {
.padding(.horizontal, 30)
.background(Color.gray100)
}
}
.background(Color.gray100)
.frame(maxWidth: .infinity)
.onRotate { newOrientation in

View file

@ -36,7 +36,7 @@ class ConversationModel: ObservableObject, Identifiable {
let localSipUri: String
let remoteSipUri: String
let isGroup: Bool
let isReadOnly: Bool
@Published var isReadOnly: Bool
@Published var subject: String
@Published var participantsAddress: [String] = []
@Published var isComposing: Bool