Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 44 additions & 21 deletions NextcloudTalk/Calls/CallViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ class CallViewController: UIViewController,
self.audioMuteButton.addGestureRecognizer(pushToTalkRecognizer)

self.participantsLabelContainer.isHidden = true
let participantsLabelTapGesture = UITapGestureRecognizer(target: self, action: #selector(showParticipantsInRoomInfo))
self.participantsLabelContainer.addGestureRecognizer(participantsLabelTapGesture)

self.screensharingView.isHidden = true
self.screensharingView.clipsToBounds = true
Expand Down Expand Up @@ -1106,13 +1108,26 @@ class CallViewController: UIViewController,

guard self.room.type != .oneToOne, let personImage = UIImage(systemName: "person.2") else { return }

DispatchQueue.main.async {
let participantAttachment = NSTextAttachment(image: personImage.withTintColor(self.participantsLabel.textColor))
let participantText = NSMutableAttributedString(attachment: participantAttachment)
participantText.append(" \(self.peersInCall.count + 1)".withFont(self.participantsLabel.font))
WebRTCCommon.shared.dispatch {
var participantsInCall: [TalkActor] = []

self.peersInCall.forEach { peerConnection in
let actor = self.callController?.getActorFromSessionId(peerConnection.peerId) ?? TalkActor()
participantsInCall.append(actor)
}

let ownActor = TalkActor(actorId: self.room.account?.userId, actorType: kParticipantTypeUser)
participantsInCall.append(ownActor)

self.participantsLabel.attributedText = participantText
self.participantsLabelContainer.isHidden = false
DispatchQueue.main.async {
let participantAttachment = NSTextAttachment(image: personImage.withTintColor(self.participantsLabel.textColor))
let participantText = NSMutableAttributedString(attachment: participantAttachment)
let uniqueParticipantsCount = Set(participantsInCall.map({ $0.id })).count
participantText.append(" \(uniqueParticipantsCount)".withFont(self.participantsLabel.font))

self.participantsLabel.attributedText = participantText
self.participantsLabelContainer.isHidden = false
}
}
}

Expand Down Expand Up @@ -1991,6 +2006,28 @@ class CallViewController: UIViewController,
}
}

func showParticipantsInRoomInfo() {
self.showRoomInfo(scrollToParticipantsSection: true)
}

func showRoomInfo(scrollToParticipantsSection: Bool = false) {
let roomInfoVC = RoomInfoUIViewFactory.create(room: self.room, showDestructiveActions: false, scrollToParticipantsSectionOnAppear: scrollToParticipantsSection)
roomInfoVC.modalPresentationStyle = .pageSheet

let navController = UINavigationController(rootViewController: roomInfoVC)
let cancelButton = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction { _ in
roomInfoVC.dismiss(animated: true)
})

if #unavailable(iOS 26.0) {
cancelButton.tintColor = NCAppBranding.themeTextColor()
}

navController.navigationBar.topItem?.leftBarButtonItem = cancelButton

self.present(navController, animated: true)
}

func showChat() {
if chatNavigationController == nil {
guard let room = NCDatabaseManager.sharedInstance().room(withToken: room.token, forAccountId: room.accountId),
Expand Down Expand Up @@ -2451,20 +2488,6 @@ class CallViewController: UIViewController,
// MARK: - NCChatTitleViewDelegate

func chatTitleViewTapped(_ chatTitleView: NCChatTitleView?) {
let roomInfoVC = RoomInfoUIViewFactory.create(room: self.room, showDestructiveActions: false)
roomInfoVC.modalPresentationStyle = .pageSheet

let navController = UINavigationController(rootViewController: roomInfoVC)
let cancelButton = UIBarButtonItem(systemItem: .cancel, primaryAction: UIAction { _ in
roomInfoVC.dismiss(animated: true)
})

if #unavailable(iOS 26.0) {
cancelButton.tintColor = NCAppBranding.themeTextColor()
}

navController.navigationBar.topItem?.leftBarButtonItem = cancelButton

self.present(navController, animated: true)
showRoomInfo()
}
}
170 changes: 104 additions & 66 deletions NextcloudTalk/Rooms/RoomInfo/RoomInfoParticipantsSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,29 @@ struct RoomInfoParticipantsSection: View {
}

private var participantCountText: String {
return participants == nil ? "" : String.localizedStringWithFormat(NSLocalizedString("%ld participants", comment: ""), participants?.count ?? 0)
guard let participants else { return "" }
return String.localizedStringWithFormat(NSLocalizedString("%ld participants", comment: ""), participants.count)
}

private var participantsInCallCountText: String {
return String.localizedStringWithFormat(NSLocalizedString("%ld participants in this call", comment: ""), participantsInCall.count)
}

private var otherParticipansText: String {
guard let participants else { return "" }
if room.hasCall, !participantsInCall.isEmpty, participantsInCall.count == participants.count {
return ""
}
return String.localizedStringWithFormat(NSLocalizedString("Other participants", comment: ""))
}

private var participantsInCall: [NCRoomParticipant] {
return participants?.filter { $0.inCall.contains(.inCall) } ?? []
}

var body: (some View)? {
if room.canAddParticipants {
Section(participantCountText) {
Section(NSLocalizedString("Conversation participants", comment: "")) {
Button(action: addParticipants) {
if room.type == .oneToOne {
ImageSublabelView(image: Image(systemName: "person.badge.plus")) {
Expand All @@ -59,71 +76,26 @@ struct RoomInfoParticipantsSection: View {
}
}

Section(room.canAddParticipants ? "" : participantCountText) {
if let participants = Binding($participants) {
ForEach(participants, id: \.self) { $participant in
Menu {
if room.canModerate, participant.canBeModerated {
if participant.canBeDemoted {
Button {
self.changeModerationPermission(forParticipant: participant, canModerate: false)
} label: {
Label(NSLocalizedString("Demote from moderator", comment: ""), systemImage: "person")
}
}

if participant.canBePromoted {
Button {
self.changeModerationPermission(forParticipant: participant, canModerate: true)
} label: {
Label(NSLocalizedString("Promote to moderator", comment: ""), systemImage: "crown")
}
}
}

if participant.canBeNotifiedAboutCall, room.permissions.contains(.startCall), room.participantFlags != [] {
Button {
self.sendCallNotification(forParticipant: participant)
} label: {
Label(NSLocalizedString("Send call notification", comment: ""), systemImage: "bell")
}
}

if participant.actorType == .email {
Button {
self.resendInvitation(forParticipant: participant)
} label: {
Label(NSLocalizedString("Resend invitation", comment: ""), systemImage: "envelope")
}
}

if room.canModerate, participant.canBeModerated {
if participant.canBeBanned {
Button(role: .destructive) {
participantToBan = participant
banConfirmationShown = true
} label: {
Label(NSLocalizedString("Ban participant", comment: ""), systemImage: "person.badge.minus")
}
.foregroundStyle(.primary)
.disabled(isBanActionRunning)
}

Button(role: .destructive) {
Task {
await removeParticipant(participant: participant)
}
} label: {
Label(getRemoveLabel(forParticipant: participant), systemImage: "trash")
}
if room.hasCall {
Section(participantsInCallCountText) {
if let participants = Binding($participants) {
ForEach(participants, id: \.self) { $participant in
if participant.inCall.contains(.inCall) {
participantView(participant: $participant)
}
} label: {
ContactsTableViewCellWrapper(room: $room, participant: $participant)
.frame(height: 72) // Height set in the XIB file
}
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 12))
.alignmentGuide(.listRowSeparatorLeading) { _ in
72
} else {
ProgressView()
.listRowInsets(nil)
}
}
}

Section(room.hasCall ? otherParticipansText : participantCountText) {
if let participants = Binding($participants) {
ForEach(participants, id: \.self) { $participant in
if !room.hasCall || participant.inCall.isEmpty {
participantView(participant: $participant)
}
}
} else {
Expand All @@ -134,7 +106,6 @@ struct RoomInfoParticipantsSection: View {
.task {
getParticipants()
}
.listRowInsets(room.canAddParticipants ? EdgeInsets(top: -12, leading: 0, bottom: 0, trailing: 0) : nil)
.alert(String(format: NSLocalizedString("Ban %@", comment: "e.g. Ban John Doe"), participantToBan?.displayName ?? "Unknown"), isPresented: $banConfirmationShown) {
// Can't move alert inside a menu element, it needs to be outside of the menu

Expand Down Expand Up @@ -259,4 +230,71 @@ struct RoomInfoParticipantsSection: View {
self.getParticipants()
}
}

func participantView(participant: Binding<NCRoomParticipant>) -> some View {
let wrappedParticipant = participant.wrappedValue
return Menu {
if room.canModerate, wrappedParticipant.canBeModerated {
if wrappedParticipant.canBeDemoted {
Button {
self.changeModerationPermission(forParticipant: wrappedParticipant, canModerate: false)
} label: {
Label(NSLocalizedString("Demote from moderator", comment: ""), systemImage: "person")
}
}

if wrappedParticipant.canBePromoted {
Button {
self.changeModerationPermission(forParticipant: wrappedParticipant, canModerate: true)
} label: {
Label(NSLocalizedString("Promote to moderator", comment: ""), systemImage: "crown")
}
}
}

if wrappedParticipant.canBeNotifiedAboutCall, room.permissions.contains(.startCall), room.participantFlags.contains(.inCall) {
Button {
self.sendCallNotification(forParticipant: wrappedParticipant)
} label: {
Label(NSLocalizedString("Send call notification", comment: ""), systemImage: "bell")
}
}

if wrappedParticipant.actorType == .email {
Button {
self.resendInvitation(forParticipant: wrappedParticipant)
} label: {
Label(NSLocalizedString("Resend invitation", comment: ""), systemImage: "envelope")
}
}

if room.canModerate, wrappedParticipant.canBeModerated {
if wrappedParticipant.canBeBanned {
Button(role: .destructive) {
participantToBan = wrappedParticipant
banConfirmationShown = true
} label: {
Label(NSLocalizedString("Ban participant", comment: ""), systemImage: "person.badge.minus")
}
.foregroundStyle(.primary)
.disabled(isBanActionRunning)
}

Button(role: .destructive) {
Task {
await removeParticipant(participant: wrappedParticipant)
}
} label: {
Label(getRemoveLabel(forParticipant: wrappedParticipant), systemImage: "trash")
}
}
} label: {
ContactsTableViewCellWrapper(room: $room, participant: participant)
.frame(height: 72) // Height set in the XIB file
}
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 12))
.alignmentGuide(.listRowSeparatorLeading) { _ in
72
}
}
}
39 changes: 23 additions & 16 deletions NextcloudTalk/Rooms/RoomInfo/RoomInfoSwiftUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,40 @@ class HostingControllerWrapper {

struct RoomInfoSwiftUIView: View {
let hostingWrapper: HostingControllerWrapper
let scrollToParticipantsSectionOnAppear: Bool

@State var room: NCRoom
@State var showDestructiveActions: Bool = true
@State var quickLookUrl: URL?
@State var profileInfo: ProfileInfo?

var body: some View {
List {
RoomInfoHeaderSection(hostingWrapper: hostingWrapper, room: $room, profileInfo: $profileInfo)
ScrollViewReader { proxy in
List {
RoomInfoHeaderSection(hostingWrapper: hostingWrapper, room: $room, profileInfo: $profileInfo)

RoomInfoFileSection(hostingWrapper: hostingWrapper, room: $room, quickLookUrl: $quickLookUrl)
RoomInfoSharedItemsSection(hostingWrapper: hostingWrapper, room: $room)
RoomInfoFileSection(hostingWrapper: hostingWrapper, room: $room, quickLookUrl: $quickLookUrl)
RoomInfoSharedItemsSection(hostingWrapper: hostingWrapper, room: $room)

RoomInfoNotificationSection(room: $room)
RoomInfoConversationSettingsSection(hostingWrapper: hostingWrapper, room: $room)
RoomInfoNotificationSection(room: $room)
RoomInfoConversationSettingsSection(hostingWrapper: hostingWrapper, room: $room)

RoomInfoGuestSection(room: $room)
RoomInfoWebinarSection(room: $room)
RoomInfoSIPInfoSection(room: $room)
RoomInfoGuestSection(room: $room)
RoomInfoWebinarSection(room: $room)
RoomInfoSIPInfoSection(room: $room)

RoomInfoParticipantsSection(hostingWrapper: hostingWrapper, room: $room)
RoomInfoParticipantsSection(hostingWrapper: hostingWrapper, room: $room).id("participantsSection")

RoomInfoNonDestructiveSection(room: $room)
RoomInfoNonDestructiveSection(room: $room)

if showDestructiveActions {
RoomInfoDestructiveSection(room: $room)
}
if showDestructiveActions {
RoomInfoDestructiveSection(room: $room)
}
}.onAppear(perform: {
if scrollToParticipantsSectionOnAppear {
proxy.scrollTo("participantsSection", anchor: .center)
}
})
}
.quickLookPreview($quickLookUrl)
.environment(\.defaultMinListHeaderHeight, 1)
Expand Down Expand Up @@ -77,9 +84,9 @@ struct RoomInfoSwiftUIView: View {

@objc class RoomInfoUIViewFactory: NSObject {

@objc static func create(room: NCRoom, showDestructiveActions: Bool) -> UIViewController {
@objc static func create(room: NCRoom, showDestructiveActions: Bool, scrollToParticipantsSectionOnAppear: Bool = false) -> UIViewController {
let wrapper = HostingControllerWrapper()
let roomInfoView = RoomInfoSwiftUIView(hostingWrapper: wrapper, room: room, showDestructiveActions: showDestructiveActions)
let roomInfoView = RoomInfoSwiftUIView(hostingWrapper: wrapper, scrollToParticipantsSectionOnAppear: scrollToParticipantsSectionOnAppear, room: room, showDestructiveActions: showDestructiveActions)
let hostingController = UIHostingController(rootView: roomInfoView)
hostingController.title = NSLocalizedString("Conversation settings", comment: "")
NCAppBranding.styleViewController(hostingController)
Expand Down
2 changes: 1 addition & 1 deletion NextcloudTalk/Rooms/RoomsTableViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -1324,7 +1324,7 @@ - (void)removeRoomFromFavorites:(NCRoom *)room

- (void)presentRoomInfoForRoom:(NCRoom *)room
{
UIViewController *roomInfoVC = [RoomInfoUIViewFactory createWithRoom:room showDestructiveActions:YES];
UIViewController *roomInfoVC = [RoomInfoUIViewFactory createWithRoom:room showDestructiveActions:YES scrollToParticipantsSectionOnAppear:NO];
NCNavigationController *navigationController = [[NCNavigationController alloc] initWithRootViewController:roomInfoVC];

UIAction *cancelAction = [UIAction actionWithHandler:^(__kindof UIAction * _Nonnull action) {
Expand Down
Loading
Loading