Statistics
| Branch: | Tag: | Revision:

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
}