chatsecureios / ChatSecure / Classes / View Controllers / OTRRoomOccupantsViewController.swift @ 80684e32
History | View | Annotate | Download (18.7 KB)
| 1 |
// |
|---|---|
| 2 |
// OTRRoomOccupantsViewController.swift |
| 3 |
// ChatSecure |
| 4 |
// |
| 5 |
// Created by David Chiles on 10/28/15. |
| 6 |
// Copyright © 2015 Chris Ballinger. All rights reserved. |
| 7 |
// |
| 8 |
|
| 9 |
import Foundation |
| 10 |
import UIKit |
| 11 |
import PureLayout |
| 12 |
import BButton |
| 13 |
import OTRAssets |
| 14 |
|
| 15 |
@objc public protocol OTRRoomOccupantsViewControllerDelegate {
|
| 16 |
func didLeaveRoom(_ roomOccupantsViewController: OTRRoomOccupantsViewController) -> Void |
| 17 |
func didArchiveRoom(_ roomOccupantsViewController: OTRRoomOccupantsViewController) -> Void |
| 18 |
} |
| 19 |
|
| 20 |
open class OTRRoomOccupantsViewController: UIViewController {
|
| 21 |
|
| 22 |
@objc public weak var delegate:OTRRoomOccupantsViewControllerDelegate? = nil |
| 23 |
|
| 24 |
@IBOutlet open weak var tableView:UITableView! |
| 25 |
@IBOutlet weak var largeAvatarView:UIImageView! |
| 26 |
|
| 27 |
// For matching navigation bar and avatar |
| 28 |
var navigationBarShadow:UIImage? |
| 29 |
var navigationBarBackground:UIImage? |
| 30 |
var topBounceView:UIView? |
| 31 |
|
| 32 |
open var viewHandler:OTRYapViewHandler? |
| 33 |
open var room:OTRXMPPRoom? |
| 34 |
open var ownOccupant:OTRXMPPRoomOccupant? |
| 35 |
open var headerRows:[String] = [] |
| 36 |
open var footerRows:[String] = [] |
| 37 |
fileprivate let readConnection = OTRDatabaseManager.shared.readOnlyDatabaseConnection |
| 38 |
open var crownImage:UIImage? |
| 39 |
|
| 40 |
static let CellIdentifier = "Cell" |
| 41 |
|
| 42 |
static let HeaderCellGroupName = "cellGroupName" |
| 43 |
static let HeaderCellShare = "cellGroupShare" |
| 44 |
static let HeaderCellAddFriends = "cellGroupAddFriends" |
| 45 |
static let HeaderCellMute = "cellGroupMute" |
| 46 |
static let HeaderCellMembers = "cellGroupMembers" |
| 47 |
static let FooterCellLeave = "cellGroupLeave" |
| 48 |
|
| 49 |
open var tableHeaderView:OTRVerticalStackView? |
| 50 |
open var tableFooterView:OTRVerticalStackView? |
| 51 |
|
| 52 |
@objc public init(databaseConnection:YapDatabaseConnection, roomKey:String) {
|
| 53 |
super.init(nibName: nil, bundle: nil) |
| 54 |
setupViewHandler(databaseConnection: databaseConnection, roomKey: roomKey) |
| 55 |
} |
| 56 |
|
| 57 |
required public init?(coder aDecoder: NSCoder) {
|
| 58 |
super.init(coder: aDecoder) |
| 59 |
} |
| 60 |
|
| 61 |
@objc public func setupViewHandler(databaseConnection:YapDatabaseConnection, roomKey:String) {
|
| 62 |
databaseConnection.read({ (transaction) in
|
| 63 |
self.room = OTRXMPPRoom.fetchObject(withUniqueID: roomKey, transaction: transaction) |
| 64 |
if let room = self.room, let accountId = room.accountUniqueId, let roomJidStr = room.jid, let roomJid = XMPPJID(string: roomJidStr), let ownJidStr = room.ownJID, let ownJid = XMPPJID(string: ownJidStr) {
|
| 65 |
self.ownOccupant = OTRXMPPRoomOccupant.occupant(jid: ownJid, realJID: ownJid, roomJID: roomJid, accountId: accountId, createIfNeeded: true, transaction: transaction) |
| 66 |
} |
| 67 |
}) |
| 68 |
self.fetchMembersList() |
| 69 |
viewHandler = OTRYapViewHandler(databaseConnection: databaseConnection) |
| 70 |
if let viewHandler = self.viewHandler {
|
| 71 |
viewHandler.delegate = self |
| 72 |
viewHandler.setup(DatabaseExtensionName.groupOccupantsViewName.name(), groups: [roomKey]) |
| 73 |
} |
| 74 |
} |
| 75 |
|
| 76 |
override open func viewDidLoad() {
|
| 77 |
super.viewDidLoad() |
| 78 |
|
| 79 |
tableHeaderView = OTRVerticalStackView() |
| 80 |
tableFooterView = OTRVerticalStackView() |
| 81 |
|
| 82 |
var headerCells = [ |
| 83 |
OTRRoomOccupantsViewController.HeaderCellGroupName, |
| 84 |
OTRRoomOccupantsViewController.HeaderCellMute, |
| 85 |
OTRRoomOccupantsViewController.HeaderCellMembers |
| 86 |
] |
| 87 |
|
| 88 |
// Add friends depends on the role |
| 89 |
if let ownOccupant = self.ownOccupant, ownOccupant.role.canInviteOthers() {
|
| 90 |
headerCells.insert(OTRRoomOccupantsViewController.HeaderCellAddFriends, at: 1) |
| 91 |
} |
| 92 |
|
| 93 |
let footerCells = [ |
| 94 |
OTRRoomOccupantsViewController.FooterCellLeave |
| 95 |
] |
| 96 |
|
| 97 |
for name in headerCells {
|
| 98 |
let cell = createHeaderCell(type: name) |
| 99 |
tableHeaderView?.addStackedSubview(cell, identifier: name, gravity: .middle, height: 44, callback: {
|
| 100 |
self.didSelectHeaderCell(type: name) |
| 101 |
}) |
| 102 |
} |
| 103 |
for name in footerCells {
|
| 104 |
let cell = createFooterCell(type: name) |
| 105 |
tableFooterView?.addStackedSubview(cell, identifier: name, gravity: .middle, height: 44, callback: {
|
| 106 |
self.didSelectFooterCell(type: name) |
| 107 |
}) |
| 108 |
} |
| 109 |
|
| 110 |
// Add the avatar view topmost |
| 111 |
tableHeaderView?.addStackedSubview(largeAvatarView, identifier: "avatar", gravity: .top) |
| 112 |
|
| 113 |
self.tableView.tableHeaderView = self.tableHeaderView |
| 114 |
self.tableView.tableFooterView = self.tableFooterView |
| 115 |
updateNotificationSwitchCell() |
| 116 |
|
| 117 |
if let room = self.room {
|
| 118 |
let seed = room.avatarSeed |
| 119 |
let image = OTRGroupAvatarGenerator.avatarImage(withSeed: seed, width: Int(largeAvatarView.frame.width), height: Int(largeAvatarView.frame.height)) |
| 120 |
largeAvatarView.image = image |
| 121 |
} |
| 122 |
|
| 123 |
self.crownImage = UIImage(named: "crown", in: OTRAssets.resourcesBundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) |
| 124 |
|
| 125 |
self.tableView.dataSource = self |
| 126 |
self.tableView.delegate = self |
| 127 |
self.tableView.register(OTRBuddyInfoCheckableCell.self, forCellReuseIdentifier: OTRRoomOccupantsViewController.CellIdentifier) |
| 128 |
} |
| 129 |
|
| 130 |
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
| 131 |
if scrollView == self.tableView {
|
| 132 |
// Adjust the frame of the overscroll view |
| 133 |
if let topBounceView = self.topBounceView {
|
| 134 |
let frame = CGRect(x: 0, y: 0, width: self.tableView.frame.size.width, height: self.tableView.contentOffset.y) |
| 135 |
topBounceView.frame = frame |
| 136 |
} |
| 137 |
} |
| 138 |
} |
| 139 |
|
| 140 |
open override func viewWillAppear(_ animated: Bool) {
|
| 141 |
super.viewWillAppear(animated) |
| 142 |
|
| 143 |
// Store shadow and background, so we can restore them |
| 144 |
self.navigationBarShadow = self.navigationController?.navigationBar.shadowImage |
| 145 |
self.navigationBarBackground = self.navigationController?.navigationBar.backgroundImage(for: .default) |
| 146 |
|
| 147 |
// Make the navigation bar the same color as the top color of the avatar image |
| 148 |
self.navigationController?.navigationBar.shadowImage = UIImage() |
| 149 |
self.navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) |
| 150 |
if let room = self.room {
|
| 151 |
let seed = room.avatarSeed |
| 152 |
let avatarTopColor = UIColor(cgColor: OTRGroupAvatarGenerator.avatarTopColor(withSeed: seed)) |
| 153 |
self.navigationController?.navigationBar.barTintColor = avatarTopColor |
| 154 |
|
| 155 |
// Create a view for the bounce background, with same color as the topmost |
| 156 |
// avatar color. |
| 157 |
if self.topBounceView == nil {
|
| 158 |
self.topBounceView = UIView() |
| 159 |
if let view = self.topBounceView {
|
| 160 |
view.backgroundColor = avatarTopColor |
| 161 |
self.tableView.addSubview(view) |
| 162 |
} |
| 163 |
} |
| 164 |
} |
| 165 |
} |
| 166 |
|
| 167 |
open override func viewWillDisappear(_ animated: Bool) {
|
| 168 |
super.viewWillDisappear(animated) |
| 169 |
|
| 170 |
// Restore navigation bar |
| 171 |
self.navigationController?.navigationBar.barTintColor = UINavigationBar.appearance().barTintColor |
| 172 |
self.navigationController?.navigationBar.shadowImage = self.navigationBarShadow |
| 173 |
self.navigationController?.navigationBar.setBackgroundImage(self.navigationBarBackground, for: .default) |
| 174 |
} |
| 175 |
|
| 176 |
private func updateNotificationSwitchCell() {
|
| 177 |
var notificationsEnabled = true |
| 178 |
if let room = self.room {
|
| 179 |
notificationsEnabled = !room.isMuted |
| 180 |
} |
| 181 |
if let view = self.tableHeaderView?.viewWithIdentifier(identifier: OTRRoomOccupantsViewController.HeaderCellMute) as? UITableViewCell, let switchView = view.accessoryView as? UISwitch {
|
| 182 |
switchView.setOn(notificationsEnabled, animated: true) |
| 183 |
} |
| 184 |
} |
| 185 |
|
| 186 |
open func createHeaderCell(type:String) -> UITableViewCell {
|
| 187 |
var cell:UITableViewCell? |
| 188 |
switch type {
|
| 189 |
case OTRRoomOccupantsViewController.HeaderCellGroupName: |
| 190 |
cell = tableView.dequeueReusableCell(withIdentifier: type) |
| 191 |
if let room = self.room {
|
| 192 |
cell?.textLabel?.text = room.subject |
| 193 |
cell?.detailTextLabel?.text = "" // Do we have creation date? |
| 194 |
} |
| 195 |
|
| 196 |
let font:UIFont? = UIFont(name: "Material Icons", size: 24) |
| 197 |
let button = UIButton(type: UIButtonType.custom) |
| 198 |
if font != nil, let ownOccupant = self.ownOccupant, ownOccupant.role.canModifySubject() {
|
| 199 |
button.titleLabel?.font = font |
| 200 |
button.setTitle("", for: UIControlState())
|
| 201 |
button.setTitleColor(UIColor.black, for: UIControlState()) |
| 202 |
button.frame = CGRect(x: 0, y: 0, width: 44, height: 44) |
| 203 |
button.addTarget(self, action: #selector(self.didPressEditGroupSubject(_:withEvent:)), for: UIControlEvents.touchUpInside) |
| 204 |
cell?.accessoryView = button |
| 205 |
cell?.isUserInteractionEnabled = true |
| 206 |
} |
| 207 |
cell?.selectionStyle = .none |
| 208 |
break |
| 209 |
case OTRRoomOccupantsViewController.HeaderCellMute: |
| 210 |
cell = tableView.dequeueReusableCell(withIdentifier: type) |
| 211 |
let muteswitch = UISwitch() |
| 212 |
if let room = self.room {
|
| 213 |
muteswitch.setOn(!room.isMuted, animated: false) |
| 214 |
} |
| 215 |
muteswitch.addTarget(self, action: #selector(self.didChangeNotificationSwitch(_:)), for: .valueChanged) |
| 216 |
cell?.accessoryView = muteswitch |
| 217 |
cell?.isUserInteractionEnabled = true |
| 218 |
cell?.selectionStyle = .none |
| 219 |
break |
| 220 |
default: |
| 221 |
cell = tableView.dequeueReusableCell(withIdentifier: type) |
| 222 |
break |
| 223 |
} |
| 224 |
return cell ?? UITableViewCell() |
| 225 |
} |
| 226 |
|
| 227 |
open func createFooterCell(type:String) -> UITableViewCell {
|
| 228 |
return tableView.dequeueReusableCell(withIdentifier: type) ?? UITableViewCell() |
| 229 |
} |
| 230 |
|
| 231 |
open func didSelectHeaderCell(type:String) {
|
| 232 |
switch type {
|
| 233 |
case OTRRoomOccupantsViewController.HeaderCellAddFriends: |
| 234 |
addMoreFriends() |
| 235 |
break |
| 236 |
default: break |
| 237 |
} |
| 238 |
} |
| 239 |
|
| 240 |
open func didSelectFooterCell(type:String) {
|
| 241 |
switch type {
|
| 242 |
case OTRRoomOccupantsViewController.FooterCellLeave: |
| 243 |
if let room = self.room, let roomJidStr = room.jid, let roomJid = XMPPJID(string: roomJidStr), let xmppRoomManager = self.xmppRoomManager() {
|
| 244 |
//Leave room |
| 245 |
xmppRoomManager.leaveRoom(roomJid) |
| 246 |
if let delegate = self.delegate {
|
| 247 |
delegate.didLeaveRoom(self) |
| 248 |
} |
| 249 |
} |
| 250 |
break |
| 251 |
default: break |
| 252 |
} |
| 253 |
} |
| 254 |
@objc func didChangeNotificationSwitch(_ sender: UIControl!) {
|
| 255 |
if let room = self.room {
|
| 256 |
if room.isMuted {
|
| 257 |
room.muteExpiration = nil |
| 258 |
} else {
|
| 259 |
room.muteExpiration = Date.distantFuture |
| 260 |
} |
| 261 |
OTRDatabaseManager.shared.readWriteDatabaseConnection?.asyncReadWrite({ (transaction) in
|
| 262 |
room.save(with: transaction) |
| 263 |
}) |
| 264 |
updateNotificationSwitchCell() |
| 265 |
} |
| 266 |
} |
| 267 |
|
| 268 |
@objc func didPressEditGroupSubject(_ sender: UIControl!, withEvent: UIEvent!) {
|
| 269 |
let alert = UIAlertController(title: NSLocalizedString("Change room subject", comment: "Title for change room subject"), message: nil, preferredStyle: UIAlertControllerStyle.alert)
|
| 270 |
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "OK button"), style: UIAlertActionStyle.default, handler: {(action: UIAlertAction!) in
|
| 271 |
if let newSubject = alert.textFields?.first?.text {
|
| 272 |
if let cell = self.tableHeaderView?.viewWithIdentifier(identifier: OTRRoomOccupantsViewController.HeaderCellGroupName) as? UITableViewCell {
|
| 273 |
cell.textLabel?.text = newSubject |
| 274 |
} |
| 275 |
if let xmppRoom = self.xmppRoom() {
|
| 276 |
xmppRoom.changeSubject(newSubject) |
| 277 |
} |
| 278 |
} |
| 279 |
})) |
| 280 |
alert.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel button"), style: UIAlertActionStyle.cancel, handler: nil))
|
| 281 |
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
| 282 |
textField.placeholder = self.room?.subject |
| 283 |
textField.isSecureTextEntry = false |
| 284 |
}) |
| 285 |
self.present(alert, animated: true, completion: nil) |
| 286 |
} |
| 287 |
|
| 288 |
private func addMoreFriends() {
|
| 289 |
let storyboard = UIStoryboard(name: "OTRComposeGroup", bundle: OTRAssets.resourcesBundle) |
| 290 |
if let vc = storyboard.instantiateInitialViewController() as? OTRComposeGroupViewController {
|
| 291 |
vc.delegate = self |
| 292 |
vc.setExistingRoomOccupants(viewHandler: self.viewHandler, room: self.room) |
| 293 |
self.navigationController?.pushViewController(vc, animated: true) |
| 294 |
} |
| 295 |
} |
| 296 |
|
| 297 |
open func viewOccupantInfo(_ occupant:OTRXMPPRoomOccupant) {
|
| 298 |
// Show profile view? |
| 299 |
} |
| 300 |
} |
| 301 |
|
| 302 |
extension OTRRoomOccupantsViewController {
|
| 303 |
|
| 304 |
/** Do not call this within a yap transaction! */ |
| 305 |
fileprivate func xmppRoom() -> XMPPRoom? {
|
| 306 |
var xmpp: OTRXMPPManager? = nil |
| 307 |
self.readConnection?.read { transaction in
|
| 308 |
if let account = self.room?.account(with: transaction) {
|
| 309 |
xmpp = OTRProtocolManager.shared.protocol(for: account) as? OTRXMPPManager |
| 310 |
} |
| 311 |
} |
| 312 |
guard let room = self.room, |
| 313 |
let jid = room.jid, |
| 314 |
let roomJid = XMPPJID(string: jid), |
| 315 |
let xmppRoom = xmpp?.roomManager.room(for: roomJid) |
| 316 |
else { return nil }
|
| 317 |
return xmppRoom |
| 318 |
} |
| 319 |
|
| 320 |
fileprivate func xmppRoomManager() -> OTRXMPPRoomManager? {
|
| 321 |
var xmpp: OTRXMPPManager? = nil |
| 322 |
self.readConnection?.read { transaction in
|
| 323 |
if let account = self.room?.account(with: transaction) {
|
| 324 |
xmpp = OTRProtocolManager.shared.protocol(for: account) as? OTRXMPPManager |
| 325 |
} |
| 326 |
} |
| 327 |
return xmpp?.roomManager |
| 328 |
} |
| 329 |
|
| 330 |
fileprivate func fetchMembersList() {
|
| 331 |
guard let xmppRoom = xmppRoom() else { return }
|
| 332 |
xmppRoom.fetchMembersList() |
| 333 |
} |
| 334 |
} |
| 335 |
|
| 336 |
extension OTRRoomOccupantsViewController: OTRYapViewHandlerDelegateProtocol {
|
| 337 |
|
| 338 |
public func didSetupMappings(_ handler: OTRYapViewHandler) {
|
| 339 |
self.tableView?.reloadData() |
| 340 |
} |
| 341 |
|
| 342 |
public func didReceiveChanges(_ handler: OTRYapViewHandler, sectionChanges: [YapDatabaseViewSectionChange], rowChanges: [YapDatabaseViewRowChange]) {
|
| 343 |
//TODO: pretty animations |
| 344 |
self.tableView?.reloadData() |
| 345 |
} |
| 346 |
} |
| 347 |
|
| 348 |
extension OTRRoomOccupantsViewController: UITableViewDataSource {
|
| 349 |
//Int and UInt issue https://github.com/yapstudios/YapDatabase/issues/116 |
| 350 |
public func numberOfSections(in tableView: UITableView) -> Int {
|
| 351 |
return Int(self.viewHandler?.mappings?.numberOfSections() ?? 0) |
| 352 |
} |
| 353 |
|
| 354 |
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
| 355 |
return Int(self.viewHandler?.mappings?.numberOfItems(inSection: UInt(section)) ?? 0) |
| 356 |
} |
| 357 |
|
| 358 |
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
| 359 |
let cell:OTRBuddyInfoCheckableCell = tableView.dequeueReusableCell(withIdentifier: OTRRoomOccupantsViewController.CellIdentifier, for: indexPath) as! OTRBuddyInfoCheckableCell |
| 360 |
cell.setCheckImage(image: self.crownImage) |
| 361 |
var buddy:OTRXMPPBuddy? = nil |
| 362 |
if let roomOccupant = self.viewHandler?.object(indexPath) as? OTRXMPPRoomOccupant, let room = self.room, let jidString = roomOccupant.realJID ?? roomOccupant.jid, let jid = XMPPJID(string: jidString), let account = room.accountUniqueId {
|
| 363 |
OTRDatabaseManager.shared.readOnlyDatabaseConnection?.read({ (transaction) in
|
| 364 |
buddy = OTRXMPPBuddy.fetchBuddy(jid: jid, accountUniqueId: account, transaction: transaction) |
| 365 |
}) |
| 366 |
if let buddy = buddy {
|
| 367 |
cell.setThread(buddy, account: nil) |
| 368 |
if let occupantJid = roomOccupant.jid, let ownJid = ownOccupant?.jid, occupantJid.compare(ownJid) == .orderedSame {
|
| 369 |
cell.nameLabel.text?.append(" (" + GROUP_INFO_YOU() + ")")
|
| 370 |
} |
| 371 |
} else if let roomJid = room.jid {
|
| 372 |
// Create temporary buddy |
| 373 |
// Do not save here or it will auto-trust random people |
| 374 |
let uniqueId = roomJid + account |
| 375 |
let buddy = OTRXMPPBuddy(uniqueId: uniqueId) |
| 376 |
buddy.username = jid.bare |
| 377 |
buddy.displayName = roomOccupant.roomName ?? jid.bare |
| 378 |
var status: OTRThreadStatus = .available |
| 379 |
if !roomOccupant.available {
|
| 380 |
status = .offline |
| 381 |
} |
| 382 |
OTRBuddyCache.shared.setThreadStatus(status, for: buddy, resource: nil) |
| 383 |
cell.setThread(buddy, account: nil) |
| 384 |
} |
| 385 |
|
| 386 |
if roomOccupant.affiliation == .owner || roomOccupant.affiliation == .admin {
|
| 387 |
cell.setChecked(checked: true) |
| 388 |
} else {
|
| 389 |
cell.setChecked(checked: false) |
| 390 |
} |
| 391 |
if roomOccupant.role == .none {
|
| 392 |
// Not present in the room |
| 393 |
cell.nameLabel.textColor = UIColor.lightGray |
| 394 |
cell.identifierLabel.textColor = UIColor.lightGray |
| 395 |
cell.accountLabel.textColor = UIColor.lightGray |
| 396 |
} |
| 397 |
} |
| 398 |
cell.selectionStyle = .none |
| 399 |
return cell |
| 400 |
} |
| 401 |
} |
| 402 |
|
| 403 |
extension OTRRoomOccupantsViewController:UITableViewDelegate {
|
| 404 |
public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
|
| 405 |
return OTRBuddyInfoCellHeight |
| 406 |
} |
| 407 |
|
| 408 |
public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
| 409 |
return OTRBuddyInfoCellHeight |
| 410 |
} |
| 411 |
|
| 412 |
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
| 413 |
tableView.deselectRow(at: indexPath, animated: true) |
| 414 |
if let roomOccupant = self.viewHandler?.object(indexPath) as? OTRXMPPRoomOccupant {
|
| 415 |
viewOccupantInfo(roomOccupant) |
| 416 |
} |
| 417 |
} |
| 418 |
} |
| 419 |
|
| 420 |
extension OTRRoomOccupantsViewController: OTRComposeGroupViewControllerDelegate {
|
| 421 |
|
| 422 |
public func groupSelectionCancelled(_ composeViewController: OTRComposeGroupViewController) {
|
| 423 |
} |
| 424 |
|
| 425 |
public func groupBuddiesSelected(_ composeViewController: OTRComposeGroupViewController, buddyUniqueIds: [String], groupName: String) {
|
| 426 |
// Add them to the room |
| 427 |
if let xmppRoom = self.xmppRoom(), let xmppRoomManager = self.xmppRoomManager() {
|
| 428 |
xmppRoomManager.inviteBuddies(buddyUniqueIds, to: xmppRoom) |
| 429 |
} |
| 430 |
self.navigationController?.popToViewController(self, animated: true) |
| 431 |
} |
| 432 |
} |