Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / View Controllers / AccountDetailViewController.swift @ 8d76e2e3

History | View | Annotate | Download (16.9 KB)

1
//
2
//  AccountDetailViewController.swift
3
//  ChatSecure
4
//
5
//  Created by Chris Ballinger on 3/5/17.
6
//  Copyright © 2017 Chris Ballinger. All rights reserved.
7
//
8

    
9
import UIKit
10
import OTRAssets
11
import PureLayout
12

    
13
struct DetailCellInfo {
14
    enum ActionType {
15
        case editAccount
16
        case manageKeys
17
        case serverInfo
18
    }
19
    let title: String
20
    let type: ActionType
21
    let action: (_ tableView: UITableView, _ indexPath: IndexPath, _ sender: Any) -> (Void)
22
}
23

    
24
enum TableSections: Int {
25
    case account
26
    case invite
27
    case details
28
    case loginlogout
29
    case migrate
30
    case delete
31
    static let allValues = [account, invite, details, loginlogout, migrate, delete]
32
}
33

    
34
enum AccountRows: Int {
35
    case account
36
    static let allValues = [account]
37
}
38

    
39
@objc(OTRAccountDetailViewController)
40
open class AccountDetailViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
41
    
42
    public let tableView: UITableView
43
    public var account: OTRXMPPAccount
44
    let longLivedReadConnection: YapDatabaseConnection
45
    let writeConnection: YapDatabaseConnection
46
    var detailCells: [DetailCellInfo] = []
47
    let DetailCellIdentifier = "DetailCellIdentifier"
48
    
49
    let xmpp: XMPPManager
50
    
51
    @objc public init(account: OTRXMPPAccount, xmpp: XMPPManager, longLivedReadConnection: YapDatabaseConnection, writeConnection: YapDatabaseConnection) {
52
        self.account = account
53
        self.longLivedReadConnection = longLivedReadConnection
54
        self.writeConnection = writeConnection
55
        self.xmpp = xmpp
56
        self.tableView = UITableView(frame: CGRect.zero, style: .grouped)
57
        super.init(nibName: nil, bundle: nil)
58
    }
59
    
60
    required public init?(coder aDecoder: NSCoder) {
61
        fatalError("init(coder:) has not been implemented")
62
    }
63

    
64
    override open func viewDidLoad() {
65
        super.viewDidLoad()
66
        self.title = ACCOUNT_STRING()
67
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed(_:)))
68
        navigationItem.rightBarButtonItem = doneButton
69
        setupTableView()
70
        setupDetailCells()
71
    }
72
    
73
    private func setupTableView() {
74
        view.addSubview(tableView)
75
        tableView.delegate = self
76
        tableView.dataSource = self
77
        tableView.translatesAutoresizingMaskIntoConstraints = false
78
        tableView.autoPinEdgesToSuperviewEdges()
79
        let bundle = OTRAssets.resourcesBundle
80
        for identifier in [XMPPAccountCell.cellIdentifier(), SingleButtonTableViewCell.cellIdentifier()] {
81
            let nib = UINib(nibName: identifier, bundle: bundle)
82
            tableView.register(nib, forCellReuseIdentifier: identifier)
83
        }
84
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: DetailCellIdentifier)
85
    }
86
    
87
    private func setupDetailCells() {
88
        detailCells = [
89
            DetailCellInfo(title: EDIT_ACCOUNT_STRING(), type: .editAccount, action: { [weak self] (_, _, sender) -> (Void) in
90
                guard let strongSelf = self else { return }
91
                strongSelf.pushLoginView(account: strongSelf.account, sender: sender)
92
            }),
93
            DetailCellInfo(title: MANAGE_MY_KEYS(), type: .manageKeys, action: { [weak self] (_, _, sender) -> (Void) in
94
                guard let strongSelf = self else { return }
95
                strongSelf.pushKeyManagementView(account: strongSelf.account, sender: sender)
96
            }),
97
            DetailCellInfo(title: SERVER_INFORMATION_STRING(), type: .serverInfo, action: { [weak self] (_, _, sender) -> (Void) in
98
                guard let strongSelf = self else { return }
99
                strongSelf.pushServerInfoView(account: strongSelf.account, sender: sender)
100
            })
101
        ]
102
    }
103
    
104
    open override func viewWillAppear(_ animated: Bool) {
105
        super.viewWillAppear(animated)
106
        NotificationCenter.default.addObserver(self, selector: #selector(loginStatusChanged(_:)), name: NSNotification.Name(rawValue: OTRXMPPLoginStatusNotificationName), object: nil)
107
        NotificationCenter.default.addObserver(self, selector: #selector(serverCheckUpdate(_:)), name: ServerCheck.UpdateNotificationName, object: xmpp.serverCheck)
108
        tableView.reloadData()
109
    }
110
    
111
    open override func viewDidDisappear(_ animated: Bool) {
112
        super.viewDidDisappear(animated)
113
        NotificationCenter.default.removeObserver(self)
114
    }
115
    
116
    // MARK: - Notifications
117
    
118
    @objc func serverCheckUpdate(_ notification: Notification) {
119
        setupDetailCells() // refresh server info warning label
120
        tableView.reloadData()
121
    }
122
    
123
    @objc func loginStatusChanged(_ notification: Notification) {
124
        tableView.reloadData()
125
        // Show certificate warnings
126
        if let lastError = xmpp.lastConnectionError,
127
            let certWarning = UIAlertController.certificateWarningAlert(error: lastError, saveHandler: { action in
128
                self.attemptLogin(action)
129
            }) {
130
            present(certWarning, animated: true, completion: nil)
131
        }
132
    }
133
    
134
    // MARK: - User Actions
135
    
136
    func showMigrateAccount(account: OTRXMPPAccount, sender: Any) {
137
        let migrateVC = OTRAccountMigrationViewController(oldAccount: account)
138
        self.navigationController?.pushViewController(migrateVC, animated: true)
139
    }
140
    
141
    func showDeleteDialog(account: OTRXMPPAccount, sender: Any) {
142
        let alert = UIAlertController(title: "\(DELETE_ACCOUNT_MESSAGE_STRING()) \(account.username)?", message: nil, preferredStyle: .actionSheet)
143
        let cancel = UIAlertAction(title: CANCEL_STRING(), style: .cancel)
144
        let delete = UIAlertAction(title: DELETE_ACCOUNT_BUTTON_STRING(), style: .destructive) { (action) in
145
            let protocols = OTRProtocolManager.sharedInstance()
146
            if let xmpp = protocols.protocol(for: account) as? XMPPManager,
147
                xmpp.connectionStatus != .disconnected {
148
                xmpp.disconnect()
149
            }
150
            protocols.removeProtocol(for: account)
151
            OTRAccountsManager.remove(account)
152
            self.dismiss(animated: true, completion: nil)
153
        }
154
        alert.addAction(cancel)
155
        alert.addAction(delete)
156
        if let sourceView = sender as? UIView {
157
            alert.popoverPresentationController?.sourceView = sourceView;
158
            alert.popoverPresentationController?.sourceRect = sourceView.bounds;
159
        }
160
        present(alert, animated: true, completion: nil)
161
    }
162
    
163
    func showLogoutDialog(account: OTRXMPPAccount, sender: Any) {
164
        let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
165
        let cancel = UIAlertAction(title: CANCEL_STRING(), style: .cancel)
166
        let logout = UIAlertAction(title: LOGOUT_STRING(), style: .destructive) { (action) in
167
            let protocols = OTRProtocolManager.sharedInstance()
168
            if let xmpp = protocols.protocol(for: account) as? XMPPManager,
169
                xmpp.connectionStatus != .disconnected {
170
                xmpp.disconnect()
171
            }
172
        }
173
        alert.addAction(cancel)
174
        alert.addAction(logout)
175
        if let sourceView = sender as? UIView {
176
            alert.popoverPresentationController?.sourceView = sourceView;
177
            alert.popoverPresentationController?.sourceRect = sourceView.bounds;
178
        }
179
        present(alert, animated: true, completion: nil)
180
    }
181
    
182
    func showInviteFriends(account: OTRXMPPAccount, sender: Any) {
183
        ShareController.shareAccount(account, sender: sender, viewController: self)
184
    }
185
    
186
    func pushLoginView(account: OTRXMPPAccount, sender: Any) {
187
        let login = OTRBaseLoginViewController(account: account)
188
        navigationController?.pushViewController(login, animated: true)
189
    }
190
    
191
    func pushKeyManagementView(account: OTRXMPPAccount, sender: Any) {
192
        let form = UserProfileViewController.profileFormDescriptorForAccount(account, buddies: [], connection: writeConnection)
193
        let keys = UserProfileViewController(accountKey: account.uniqueId, connection: writeConnection, form: form)
194
        navigationController?.pushViewController(keys, animated: true)
195
    }
196
    
197
    func pushServerInfoView(account: OTRXMPPAccount, sender: Any) {
198
        let scvc = ServerCapabilitiesViewController(serverCheck: xmpp.serverCheck)
199
        navigationController?.pushViewController(scvc, animated: true)
200
    }
201
    
202
    @objc private func doneButtonPressed(_ sender: Any) {
203
        dismiss(animated: true, completion: nil)
204
    }
205
    
206
    private func attemptLogin(_ sender: Any) {
207
        let protocols = OTRProtocolManager.sharedInstance()
208
        if let _ = self.account.password,
209
            self.account.accountType != .xmppTor {
210
            protocols.loginAccount(self.account)
211
        } else {
212
            self.pushLoginView(account: self.account, sender: sender)
213
        }
214
    }
215

    
216
    // MARK: - Table view data source & delegate
217

    
218
    open func numberOfSections(in tableView: UITableView) -> Int {
219
        return TableSections.allValues.count
220
    }
221

    
222
    open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
223
        guard let tableSection = TableSections(rawValue: section) else {
224
            return 0
225
        }
226
        switch tableSection {
227
        case .account:
228
            return AccountRows.allValues.count
229
        case .details:
230
            return detailCells.count
231
        case .delete, .loginlogout, .invite, .migrate:
232
            return 1
233
        }
234
    }
235

    
236
    
237
    open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
238
        guard let section = TableSections(rawValue: indexPath.section) else {
239
            return UITableViewCell()
240
        }
241
        switch section {
242
        case .account:
243
            if let row = AccountRows(rawValue: indexPath.row) {
244
                switch row {
245
                case .account:
246
                    return accountCell(account: account, tableView: tableView, indexPath: indexPath)
247
                }
248
            }
249
        case .details:
250
            return detailCell(account: account, tableView: tableView, indexPath: indexPath)
251
        case .invite:
252
            return inviteCell(account: account, tableView: tableView, indexPath: indexPath)
253
        case .delete:
254
            return deleteCell(account: account, tableView: tableView, indexPath: indexPath)
255
        case .loginlogout:
256
            return loginLogoutCell(account: account, tableView: tableView, indexPath: indexPath)
257
        case .migrate:
258
            return migrateCell(account: account, tableView: tableView, indexPath: indexPath)
259
        }
260
        return UITableViewCell() // this should never be reached
261
    }
262
    
263
    open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
264
        guard let section = TableSections(rawValue: indexPath.section) else {
265
            return
266
        }
267
        switch section {
268
        case .account:
269
            if let row = AccountRows(rawValue: indexPath.row) {
270
                switch row {
271
                case .account:
272
                    break
273
                }
274
            }
275
        case .details:
276
            let detail = detailCells[indexPath.row]
277
            let cell = self.tableView(tableView, cellForRowAt: indexPath)
278
            detail.action(tableView, indexPath, cell)
279
            return
280
        case .invite, .delete, .loginlogout, .migrate:
281
            self.tableView.deselectRow(at: indexPath, animated: true)
282
            if let cell = self.tableView(tableView, cellForRowAt: indexPath) as? SingleButtonTableViewCell, let action = cell.buttonAction {
283
                action(cell, cell.button)
284
            }
285
            break
286
        }
287
    }
288
    
289
    open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
290
        var height = UITableViewAutomaticDimension
291
        guard let section = TableSections(rawValue: indexPath.section) else {
292
            return height
293
        }
294
        switch section {
295
        case .account:
296
            if let row = AccountRows(rawValue: indexPath.row) {
297
                switch row {
298
                case .account:
299
                    height = XMPPAccountCell.cellHeight()
300
                    break
301
                }
302
            }
303
        case .details, .invite, .delete, .loginlogout, .migrate:
304
            break
305
        }
306
        return height
307
    }
308
    
309
    // MARK: - Cells
310
    
311
    func accountCell(account: OTRXMPPAccount, tableView: UITableView, indexPath: IndexPath) -> XMPPAccountCell {
312
        guard let cell = tableView.dequeueReusableCell(withIdentifier: XMPPAccountCell.cellIdentifier(), for: indexPath) as? XMPPAccountCell else {
313
            return XMPPAccountCell()
314
        }
315
        cell.setAppearance(account: account)
316
        cell.infoButton.isHidden = true
317
        cell.selectionStyle = .none
318
        return cell
319
    }
320
    
321
    func loginLogoutCell(account: OTRXMPPAccount, tableView: UITableView, indexPath: IndexPath) -> SingleButtonTableViewCell {
322
        let cell = singleButtonCell(account: account, tableView: tableView, indexPath: indexPath)
323
        
324
        switch xmpp.connectionStatus {
325
        case .connecting:
326
            cell.button.setTitle("\(CONNECTING_STRING())...", for: .normal)
327
            cell.button.isEnabled = false
328
        case .disconnecting,
329
             .disconnected:
330
            cell.button.setTitle(LOGIN_STRING(), for: .normal)
331
            cell.buttonAction = { [weak self] (cell, sender) in
332
                guard let strongSelf = self else { return }
333
                strongSelf.attemptLogin(sender)
334
            }
335
            break
336
        case .connected:
337
            cell.button.setTitle(LOGOUT_STRING(), for: .normal)
338
            cell.buttonAction = { [weak self] (cell, sender) in
339
                guard let strongSelf = self else { return }
340
                strongSelf.showLogoutDialog(account: strongSelf.account, sender: sender)
341
            }
342
            cell.button.setTitleColor(UIColor.red, for: .normal)
343
            break
344
        }
345
        return cell
346
    }
347
    
348
    func detailCell(account: OTRXMPPAccount, tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
349
        let detail = detailCells[indexPath.row]
350
        let cell = tableView.dequeueReusableCell(withIdentifier: DetailCellIdentifier, for: indexPath)
351
        var title = detail.title
352
        switch  detail.type {
353
        case .editAccount:
354
            let editAccountText = EDIT_ACCOUNT_STRING()
355
            if xmpp.lastConnectionError != nil {
356
                title = "\(editAccountText)  ❌"
357
            }
358
        case .serverInfo:
359
            let serverInfoText = SERVER_INFORMATION_STRING()
360
            if xmpp.serverCheck.getCombinedPushStatus() == .broken &&
361
                OTRBranding.shouldShowPushWarning {
362
                title = "\(serverInfoText)  ⚠️"
363
            }
364
        default:
365
            break
366
        }
367
        
368
        cell.textLabel?.text = title
369
        cell.accessoryType = .disclosureIndicator
370
        return cell
371
    }
372
    
373
    public func singleButtonCell(account: OTRXMPPAccount, tableView: UITableView, indexPath: IndexPath) -> SingleButtonTableViewCell {
374
        guard let cell = tableView.dequeueReusableCell(withIdentifier: SingleButtonTableViewCell.cellIdentifier(), for: indexPath) as? SingleButtonTableViewCell else {
375
            return SingleButtonTableViewCell()
376
        }
377
        cell.button.setTitleColor(nil, for: .normal)
378
        cell.selectionStyle = .default
379
        cell.button.isEnabled = true
380
        return cell
381
    }
382
    
383
    func migrateCell(account: OTRXMPPAccount, tableView: UITableView, indexPath: IndexPath) -> SingleButtonTableViewCell {
384
        let cell = singleButtonCell(account: account, tableView: tableView, indexPath: indexPath)
385
        cell.button.setTitle(MIGRATE_ACCOUNT_STRING(), for: .normal)
386
        cell.buttonAction = { [weak self] (cell, sender) in
387
            guard let strongSelf = self else { return }
388
            strongSelf.showMigrateAccount(account: strongSelf.account, sender: sender)
389
        }
390
        return cell
391
    }
392
    
393
    func inviteCell(account: OTRXMPPAccount, tableView: UITableView, indexPath: IndexPath) -> SingleButtonTableViewCell {
394
        let cell = singleButtonCell(account: account, tableView: tableView, indexPath: indexPath)
395
        cell.button.setTitle(INVITE_FRIENDS_STRING(), for: .normal)
396
        cell.buttonAction = { [weak self] (cell, sender) in
397
            guard let strongSelf = self else { return }
398
            strongSelf.showInviteFriends(account: strongSelf.account, sender: sender)
399
        }
400
        return cell
401
    }
402
    
403
    func deleteCell(account: OTRXMPPAccount, tableView: UITableView, indexPath: IndexPath) -> SingleButtonTableViewCell {
404
        let cell = singleButtonCell(account: account, tableView: tableView, indexPath: indexPath)
405
        cell.button.setTitle(DELETE_ACCOUNT_BUTTON_STRING(), for: .normal)
406
        cell.buttonAction = { [weak self] (cell, sender) in
407
            guard let strongSelf = self else { return }
408
            strongSelf.showDeleteDialog(account: strongSelf.account, sender: sender)
409
        }
410
        cell.button.setTitleColor(UIColor.red, for: .normal)
411
        return cell
412
    }
413

    
414
}