Allow admins to update conversation participants list

This commit is contained in:
Benoit Martins 2024-11-12 18:29:10 +01:00
parent 8e13089798
commit de59f3e889
3 changed files with 311 additions and 23 deletions

View file

@ -1500,6 +1500,57 @@
}
}
},
"conversation_info_admin_menu_remove_participant" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Remove from the group"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Retirer du groupe"
}
}
}
},
"conversation_info_admin_menu_set_participant_admin" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Give admin rights"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Donner les privilèges administrateur"
}
}
}
},
"conversation_info_admin_menu_unset_participant_admin" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Remove admin rights"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Retirer les privilèges administrateur"
}
}
}
},
"conversation_info_confirm_start_group_call_dialog_message" : {
"extractionState" : "manual",
"localizations" : {

View file

@ -19,6 +19,7 @@
import SwiftUI
// swiftlint:disable type_body_length
struct ConversationInfoFragment: View {
@State private var orientation = UIDevice.current.orientation
@ -30,6 +31,8 @@ struct ConversationInfoFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@State var addParticipantsViewModel = AddParticipantsViewModel()
@Binding var isMuted: Bool
@Binding var isShowEphemeralFragment: Bool
@Binding var isShowStartCallGroupPopup: Bool
@ -282,7 +285,10 @@ struct ConversationInfoFragment: View {
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
if conversationViewModel.participantConversationModelAdmin != nil && participantConversationModel.address == conversationViewModel.participantConversationModelAdmin!.address {
let participantConversationModelIsAdmin = conversationViewModel.participantConversationModelAdmin.first(
where: {$0.address == participantConversationModel.address})
if participantConversationModelIsAdmin != nil {
Text("conversation_info_participant_is_admin_label")
.foregroundStyle(Color.grayMain2c400)
.default_text_style(styleSize: 12)
@ -290,12 +296,144 @@ struct ConversationInfoFragment: View {
.lineLimit(1)
}
}
if conversationViewModel.myParticipantConversationModel != nil && conversationViewModel.myParticipantConversationModel!.address != participantConversationModel.address {
Menu {
Button(
action: {
let addressConv = participantConversationModel.address
let friendIndex = contactsManager.lastSearch.firstIndex(
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})})
if friendIndex != nil {
withAnimation {
conversationViewModel.displayedConversation = nil
indexPage = 0
contactViewModel.indexDisplayedFriend = friendIndex
}
} else {
withAnimation {
conversationViewModel.displayedConversation = nil
indexPage = 0
isShowEditContactFragment.toggle()
editContactViewModel.sipAddresses.removeAll()
editContactViewModel.sipAddresses.append(String(participantConversationModel.address.dropFirst(4) ?? ""))
editContactViewModel.sipAddresses.append("")
}
}
},
label: {
HStack {
let addressConv = participantConversationModel.address
let friendIndex = contactsManager.lastSearch.firstIndex(
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})})
if friendIndex != nil {
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)
} else {
Image("user-plus")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
Text("conversation_info_menu_add_to_contacts")
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
}
)
if conversationViewModel.isUserAdmin {
let participantConversationModelIsAdmin = conversationViewModel.participantConversationModelAdmin.first(
where: {$0.address == participantConversationModel.address})
Button {
conversationViewModel.toggleAdminRights(address: participantConversationModel.address)
} label: {
HStack {
Text(participantConversationModelIsAdmin != nil ? "conversation_info_admin_menu_unset_participant_admin" : "conversation_info_admin_menu_set_participant_admin")
Spacer()
Image("user-circle")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
}
Button(role: .destructive) {
conversationViewModel.removeParticipant(address: participantConversationModel.address)
} label: {
HStack {
Text("conversation_info_admin_menu_remove_participant")
Spacer()
Image("trash-simple-red")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.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)
}
}
}
.padding(.vertical, 15)
.padding(.horizontal, 20)
}
if conversationViewModel.isUserAdmin {
NavigationLink(destination: {
AddParticipantsFragment(addParticipantsViewModel: addParticipantsViewModel, confirmAddParticipantsFunc: conversationViewModel.addParticipants)
.onAppear {
conversationViewModel.getParticipants()
addParticipantsViewModel.participantsToAdd = conversationViewModel.participants
}
}, label: {
HStack {
Image("plus-circle")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 20, height: 20)
Text("conversation_info_add_participants_label")
.default_text_style_orange_500(styleSize: 14)
.frame(height: 35)
}
})
.padding(.horizontal, 20)
.padding(.vertical, 5)
.background(Color.orangeMain100)
.cornerRadius(60)
.padding(.top, 10)
.padding(.bottom, 20)
/*
Button(
action: {
},
@ -319,6 +457,7 @@ struct ConversationInfoFragment: View {
.cornerRadius(60)
.padding(.top, 10)
.padding(.bottom, 20)
*/
}
}
.background(.white)
@ -513,6 +652,7 @@ struct ConversationInfoFragment: View {
conversationsListViewModel: ConversationsListViewModel(),
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
addParticipantsViewModel: AddParticipantsViewModel(),
isMuted: .constant(false),
isShowEphemeralFragment: .constant(false),
isShowStartCallGroupPopup: .constant(false),
@ -521,3 +661,4 @@ struct ConversationInfoFragment: View {
indexPage: .constant(0)
)
}
// swiftlint:enable type_body_length

View file

@ -29,6 +29,8 @@ import AVFoundation
class ConversationViewModel: ObservableObject {
static let TAG = "[ConversationViewModel]"
private var coreContext = CoreContext.shared
@Published var displayedConversation: ConversationModel?
@ -79,8 +81,10 @@ class ConversationViewModel: ObservableObject {
@Published var conversationMessagesSection: [MessagesSection] = []
@Published var participantConversationModel: [ContactAvatarModel] = []
@Published var participantConversationModelAdmin: ContactAvatarModel?
@Published var participantConversationModelAdmin: [ContactAvatarModel] = []
@Published var myParticipantConversationModel: ContactAvatarModel? = nil
@Published var isUserAdmin: Bool = false
@Published var participants: [SelectedAddressModel] = []
@Published var mediasToSend: [Attachment] = []
var maxMediaCount = 12
@ -125,10 +129,13 @@ class ConversationViewModel: ObservableObject {
self.getNewMessages(eventLogs: [eventLog])
}, onParticipantAdded: { (_: ChatRoom, eventLogs: EventLog) in
self.getNewMessages(eventLogs: [eventLogs])
self.getParticipantConversationModel()
}, onParticipantRemoved: { (_: ChatRoom, eventLogs: EventLog) in
self.getNewMessages(eventLogs: [eventLogs])
self.getParticipantConversationModel()
}, onParticipantAdminStatusChanged: { (_: ChatRoom, eventLogs: EventLog) in
self.getNewMessages(eventLogs: [eventLogs])
self.getParticipantConversationModel()
}, onSubjectChanged: { (_: ChatRoom, eventLogs: EventLog) in
self.getNewMessages(eventLogs: [eventLogs])
}, onConferenceJoined: {(_: ChatRoom, eventLog: EventLog) in
@ -317,7 +324,7 @@ class ConversationViewModel: ObservableObject {
if self.displayedConversation != nil {
DispatchQueue.main.async {
self.isUserAdmin = false
self.participantConversationModelAdmin = nil
self.participantConversationModelAdmin.removeAll()
self.participantConversationModel.removeAll()
}
self.displayedConversation!.chatRoom.participants.forEach { participant in
@ -326,7 +333,7 @@ class ConversationViewModel: ObservableObject {
let avatarModelTmp = avatarResult
if participant.isAdmin {
DispatchQueue.main.async {
self.participantConversationModelAdmin = avatarModelTmp
self.participantConversationModelAdmin.append(avatarModelTmp)
self.participantConversationModel.append(avatarModelTmp)
}
} else {
@ -344,12 +351,14 @@ class ConversationViewModel: ObservableObject {
if self.displayedConversation!.chatRoom.me!.isAdmin {
DispatchQueue.main.async {
self.isUserAdmin = true
self.participantConversationModelAdmin = avatarModelTmp
self.participantConversationModelAdmin.append(avatarModelTmp)
self.participantConversationModel.append(avatarModelTmp)
self.myParticipantConversationModel = avatarModelTmp
}
} else {
DispatchQueue.main.async {
self.participantConversationModel.append(avatarModelTmp)
self.myParticipantConversationModel = avatarModelTmp
}
}
}
@ -2076,24 +2085,6 @@ class ConversationViewModel: ObservableObject {
}
}
func setNewChatRoomSubject() {
if self.displayedConversation != nil && self.conversationInfoPopupText != self.displayedConversation!.subject {
coreContext.doOnCoreQueue { _ in
self.displayedConversation!.chatRoom.subject = self.conversationInfoPopupText
}
self.displayedConversation!.subject = self.conversationInfoPopupText
self.displayedConversation!.avatarModel = ContactAvatarModel(
friend: self.displayedConversation!.avatarModel.friend,
name: self.conversationInfoPopupText,
address: self.displayedConversation!.avatarModel.address,
withPresence: false
)
self.isShowConversationInfoPopup = false
}
}
func getEphemeralTime() {
coreContext.doOnCoreQueue { _ in
if self.displayedConversation != nil {
@ -2124,6 +2115,111 @@ class ConversationViewModel: ObservableObject {
}
}
}
func setNewChatRoomSubject() {
if self.displayedConversation != nil && self.conversationInfoPopupText != self.displayedConversation!.subject {
coreContext.doOnCoreQueue { _ in
self.displayedConversation!.chatRoom.subject = self.conversationInfoPopupText
}
self.displayedConversation!.subject = self.conversationInfoPopupText
self.displayedConversation!.avatarModel = ContactAvatarModel(
friend: self.displayedConversation!.avatarModel.friend,
name: self.conversationInfoPopupText,
address: self.displayedConversation!.avatarModel.address,
withPresence: false
)
self.isShowConversationInfoPopup = false
}
}
func getParticipants() {
self.participants = []
var list: [SelectedAddressModel] = []
for participant in participantConversationModel {
let addr = try? Factory.Instance.createAddress(addr: participant.address)
if addr != nil {
if let found = list.first(where: { $0.address.weakEqual(address2: addr!) }) {
Log.info("\(ConversationViewModel.TAG) Participant \(found.address.asStringUriOnly()) already in list, skipping")
continue
}
if self.displayedConversation!.chatRoom.me != nil && self.displayedConversation!.chatRoom.me!.address != nil && !self.displayedConversation!.chatRoom.me!.address!.weakEqual(address2: addr!) {
list.append(SelectedAddressModel(addr: addr!, avModel: participant))
Log.info("\(ConversationViewModel.TAG) Added participant \(addr!.asStringUriOnly())")
}
}
}
self.participants = list
Log.info("\(ConversationViewModel.TAG) \(list.count) participants added to chat room")
}
func addParticipants(participantsToAdd: [SelectedAddressModel]) {
var list: [SelectedAddressModel] = []
for selectedAddr in participantsToAdd {
if let found = list.first(where: { $0.address.weakEqual(address2: selectedAddr.address) }) {
Log.info("\(ConversationViewModel.TAG) Participant \(found.address.asStringUriOnly()) already in list, skipping")
continue
}
list.append(selectedAddr)
Log.info("\(ConversationViewModel.TAG) Added participant \(selectedAddr.address.asStringUriOnly())")
}
let participantsAddress = self.displayedConversation!.chatRoom.participants.map { $0.address?.asStringUriOnly() }
let listAddress = list.map { $0.address.asStringUriOnly() }
let differences = participantsAddress.difference(from: listAddress)
if !differences.isEmpty {
let differenceAddresses = differences.compactMap { change -> String? in
switch change {
case .insert(_, let element, _), .remove(_, let element, _):
return element
}
}
let filteredParticipants = self.displayedConversation!.chatRoom.participants.filter { participant in
differenceAddresses.contains(participant.address!.asStringUriOnly())
}
coreContext.doOnCoreQueue { _ in
_ = self.displayedConversation!.chatRoom.addParticipants(addresses: list.map { $0.address })
self.displayedConversation!.chatRoom.removeParticipants(participants: filteredParticipants)
}
} else {
coreContext.doOnCoreQueue { _ in
_ = self.displayedConversation!.chatRoom.addParticipants(addresses: list.map { $0.address })
}
}
Log.info("\(ConversationViewModel.TAG) \(list.count) participants added to chat room")
}
func toggleAdminRights(address: String) {
if self.displayedConversation != nil {
coreContext.doOnCoreQueue { _ in
if let participant = self.displayedConversation!.chatRoom.participants.first(where: {$0.address?.asStringUriOnly() == address}) {
self.displayedConversation!.chatRoom.setParticipantAdminStatus(participant: participant, isAdmin: !participant.isAdmin)
}
}
}
}
func removeParticipant(address: String) {
if self.displayedConversation != nil {
coreContext.doOnCoreQueue { _ in
if let participant = self.displayedConversation!.chatRoom.participants.first(where: {$0.address?.asStringUriOnly() == address}) {
self.displayedConversation!.chatRoom.removeParticipant(participant: participant)
}
}
}
}
}
// swiftlint:enable line_length
// swiftlint:enable type_body_length