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 |
} |