MeViewController.swift 32.2 KB
Newer Older
Guillaume Roguez's avatar
Guillaume Roguez committed
1
/*
2
 *  Copyright (C) 2017-2019 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3 4
 *
 *  Author: Edric Ladent-Milaret <edric.ladent-milaret@savoirfairelinux.com>
5
 *  Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
6
 *  Author: Quentin Muret <quentin.muret@savoirfairelinux.com>
Guillaume Roguez's avatar
Guillaume Roguez committed
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

import UIKit
24
import Reusable
25
import RxSwift
26 27
import RxCocoa
import RxDataSources
28
import PKHUD
Guillaume Roguez's avatar
Guillaume Roguez committed
29

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
30 31
// swiftlint:disable type_body_length
// swiftlint:disable file_length
32
class MeViewController: EditProfileViewController, StoryboardBased, ViewModelBased {
33
    // MARK: - outlets
34
    @IBOutlet private weak var settingsTable: SettingsTableView!
Edric Milaret's avatar
Edric Milaret committed
35

36 37
    // MARK: - members
    var viewModel: MeViewModel!
38
    fileprivate let disposeBag = DisposeBag()
39
    private var stretchyHeader: AccountHeader!
40

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
41 42 43
    var sipCredentialsMargin: CGFloat = 0
    let sipCredentialsTAG: Int = 100

44 45 46 47
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .default
    }

48
    // MARK: - functions
Guillaume Roguez's avatar
Guillaume Roguez committed
49
    override func viewDidLoad() {
50
        self.addHeaderView()
51
        super.viewDidLoad()
52
        self.applyL10n()
53
        self.configureBindings()
54
        self.configureRingNavigationBar()
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
55
        self.calculateSipCredentialsMargin()
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
56
        self.adaptTableToKeyboardState(for: self.settingsTable, with: self.disposeBag, topOffset: self.stretchyHeader.minimumContentHeight)
57 58 59 60
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
61
        self.navigationController?.navigationBar
62 63
            .titleTextAttributes = [NSAttributedString.Key.font: UIFont(name: "HelveticaNeue-Light", size: 25)!,
                                    NSAttributedString.Key.foregroundColor: UIColor.jamiMain]
Guillaume Roguez's avatar
Guillaume Roguez committed
64
    }
Edric Milaret's avatar
Edric Milaret committed
65

66 67 68 69 70
    func applyL10n() {
        self.navigationItem.title = L10n.Global.meTabBarTitle
        self.profileName.placeholder = L10n.AccountPage.namePlaceholder
    }

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    private func addHeaderView() {
        guard let nibViews = Bundle.main
            .loadNibNamed("AccountHeader", owner: self, options: nil) else {
                supportEditProfile()
                return
        }
        guard let headerView = nibViews.first as? AccountHeader else {
            supportEditProfile()
            return
        }
        self.stretchyHeader = headerView
        self.settingsTable.addSubview(self.stretchyHeader)
        self.settingsTable.delegate = self
        self.profileImageView = stretchyHeader.profileImageView
        self.profileName = stretchyHeader.profileName
    }
Edric Milaret's avatar
Edric Milaret committed
87

88 89 90 91 92 93 94 95 96
    private func supportEditProfile() {
        // if loading grom nib failed add empty views requered by EditProfileViewController
        let image = UIImageView()
        let name = UITextField()
        self.view.addSubview(image)
        self.view.addSubview(name)
        self.profileImageView = image
        self.profileName = name
    }
Edric Milaret's avatar
Edric Milaret committed
97

98
    private func configureBindings() {
99
        let infoButton = UIButton(type: .infoLight)
Quentin Muret's avatar
Quentin Muret committed
100
        let imageQrCode = UIImage(asset: Asset.qrCode) as UIImage?
101
        let qrCodeButton   = UIButton(type: UIButton.ButtonType.custom) as UIButton
Quentin Muret's avatar
Quentin Muret committed
102
        qrCodeButton.setImage(imageQrCode, for: .normal)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
103 104 105 106 107 108 109
        self.viewModel.isAccountSip
            .asObservable()
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { [weak qrCodeButton](isSip) in
                qrCodeButton?.isHidden = isSip
                qrCodeButton?.isEnabled = !isSip
            }).disposed(by: self.disposeBag)
110
        let infoItem = UIBarButtonItem(customView: infoButton)
Quentin Muret's avatar
Quentin Muret committed
111
        let qrCodeButtonItem = UIBarButtonItem(customView: qrCodeButton)
112 113 114 115 116
        infoButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
            .subscribe(onNext: { [unowned self] in
                self.infoItemTapped()
            })
            .disposed(by: self.disposeBag)
Quentin Muret's avatar
Quentin Muret committed
117 118 119 120 121
        qrCodeButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
            .subscribe(onNext: { [unowned self] in
                self.qrCodeItemTapped()
            })
            .disposed(by: self.disposeBag)
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
        self.viewModel.showActionState.asObservable()
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { [weak self](action) in
                switch action {
                case .noAction:
                    break
                case .hideLoading:
                    self?.stopLoadingView()
                case .showLoading:
                    self?.showLoadingView()
                case .deviceRevokedWithSuccess(let deviceId):
                    self?.showDeviceRevokedAlert(deviceId: deviceId)
                case .deviceRevokationError(let deviceId, let errorMessage):
                    self?.showDeviceRevocationError(deviceId: deviceId, errorMessage: errorMessage)
                }
            }).disposed(by: self.disposeBag)
138
        self.navigationItem.rightBarButtonItem = infoItem
Quentin Muret's avatar
Quentin Muret committed
139
        self.navigationItem.leftBarButtonItem = qrCodeButtonItem
140

141
        //setup Table
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
142
        self.settingsTable.estimatedRowHeight = 35
143
        self.settingsTable.rowHeight = UITableView.automaticDimension
144
        self.settingsTable.tableFooterView = UIView()
145 146 147 148 149

        //Register cell
        self.setUpDataSource()
        self.settingsTable.register(cellType: DeviceCell.self)
        self.settingsTable.register(cellType: LinkNewDeviceCell.self)
150
        self.settingsTable.register(cellType: ProxyCell.self)
151
        self.settingsTable.register(cellType: BlockContactsCell.self)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
152
        self.settingsTable.register(cellType: NotificationCell.self)
153 154

        self.settingsTable.rx.itemSelected
155
            //.throttle(RxTimeInterval(2), scheduler: MainScheduler.instance)
156 157 158 159 160 161 162 163 164
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { [weak self] indexPath in
                if (self?.settingsTable.cellForRow(at: indexPath) as? BlockContactsCell) != nil {
                    self?.openBlockedList()
                    self?.settingsTable.deselectRow(at: indexPath, animated: true)
                }
            }).disposed(by: self.disposeBag)
    }

165
    private func openBlockedList() {
166
        self.viewModel.showBlockedContacts()
167 168
    }

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
    private func stopLoadingView() {
        HUD.hide(animated: false)
    }

    private func showLoadingView() {
        HUD.show(.labeledProgress(title: L10n.AccountPage.deviceRevocationProgress, subtitle: nil))
    }

    private func showDeviceRevocationError(deviceId: String, errorMessage: String) {
        HUD.hide(animated: true) { _ in
            let alert = UIAlertController(title: errorMessage,
                                          message: nil,
                                          preferredStyle: .alert)
            let actionCancel = UIAlertAction(title: L10n.Actions.cancelAction,
                                             style: .cancel)
            let actionAgain = UIAlertAction(title: L10n.AccountPage.deviceRevocationTryAgain,
                                            style: .default) { [weak self] _ in
                                                self?.confirmRevokeDeviceAlert(deviceID: deviceId)
            }
            alert.addAction(actionCancel)
            alert.addAction(actionAgain)
            self.present(alert, animated: true, completion: nil)
        }
    }

    private func showDeviceRevokedAlert(deviceId: String) {
        HUD.hide(animated: true) { _ in
            let alert = UIAlertController(title: L10n.AccountPage.deviceRevocationSuccess,
                                          message: nil,
                                          preferredStyle: .alert)
            let actionOk = UIAlertAction(title: L10n.Global.ok,
                                         style: .default)
            alert.addAction(actionOk)
            self.present(alert, animated: true, completion: nil)
        }
    }

206
    private func infoItemTapped() {
207 208 209 210 211 212 213 214 215 216 217 218
        var compileDate: String {
            let dateDefault = "20180131"
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "YYYYMMdd"
            let bundleName = Bundle.main.infoDictionary!["CFBundleName"] as? String ?? "Info.plist"
            if let infoPath = Bundle.main.path(forResource: bundleName, ofType: nil),
                let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath),
                let infoDate = infoAttr[FileAttributeKey.creationDate] as? Date {
                return dateFormatter.string(from: infoDate)
            }
            return dateDefault
        }
Quentin Muret's avatar
Quentin Muret committed
219
        let alert = UIAlertController(title: "\nJami\nbuild: \(compileDate)\n\"Live Free or Die\"", message: "", preferredStyle: .alert)
220
        alert.addAction(UIAlertAction(title: L10n.Global.ok, style: .default, handler: nil))
Quentin Muret's avatar
Quentin Muret committed
221
        let image = UIImageView(image: UIImage(asset: Asset.jamiIcon))
222 223 224 225 226 227 228 229 230
        alert.view.addSubview(image)
        image.translatesAutoresizingMaskIntoConstraints = false
        alert.view.addConstraint(NSLayoutConstraint(item: image, attribute: .centerX, relatedBy: .equal, toItem: alert.view, attribute: .centerX, multiplier: 1, constant: 0))
        alert.view.addConstraint(NSLayoutConstraint(item: image, attribute: .centerY, relatedBy: .equal, toItem: alert.view, attribute: .top, multiplier: 1, constant: 0.0))
        alert.view.addConstraint(NSLayoutConstraint(item: image, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 64.0))
        alert.view.addConstraint(NSLayoutConstraint(item: image, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 64.0))
        self.present(alert, animated: true, completion: nil)
    }

Quentin Muret's avatar
Quentin Muret committed
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
    private func qrCodeItemTapped() {
        let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
        guard let ringId = viewModel.getRingId() else { return }
        let imageQRCode = UIImageView(image: generateQRCode(from: ringId))
        imageQRCode.layer.cornerRadius = 8.0
        imageQRCode.clipsToBounds = true
        imageQRCode.translatesAutoresizingMaskIntoConstraints = false
        alert.view.addSubview(imageQRCode)
        alert.view.addConstraint(NSLayoutConstraint(item: imageQRCode, attribute: .centerX, relatedBy: .equal, toItem: alert.view, attribute: .centerX, multiplier: 1, constant: 0))
        alert.view.addConstraint(NSLayoutConstraint(item: imageQRCode, attribute: .centerY, relatedBy: .equal, toItem: alert.view, attribute: .top, multiplier: 1, constant: 0.0))
        alert.view.addConstraint(NSLayoutConstraint(item: imageQRCode, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 270))
        alert.view.addConstraint(NSLayoutConstraint(item: imageQRCode, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 270))
        self.present(alert, animated: true, completion: {
            alert.view.superview?.isUserInteractionEnabled = true
            alert.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertControllerBackgroundTapped)))
        })
    }

    @objc func alertControllerBackgroundTapped() {
        self.dismiss(animated: true, completion: nil)
    }

    func generateQRCode(from string: String) -> UIImage? {
        let data = string.data(using: String.Encoding.ascii)
        if let filter = CIFilter(name: "CIQRCodeGenerator") {
            filter.setValue(data, forKey: "inputMessage")
            let transform = CGAffineTransform(scaleX: 100, y: 100)
            if let output = filter.outputImage?.transformed(by: transform) {
                return UIImage(ciImage: output)
            }
        }
        return nil
    }

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
265 266
    // swiftlint:disable function_body_length
    // swiftlint:disable cyclomatic_complexity
267
    private func setUpDataSource() {
268 269 270 271 272 273 274 275 276 277 278 279 280

        let configureCell: (TableViewSectionedDataSource, UITableView, IndexPath, SettingsSection.Item)
            -> UITableViewCell = {
                ( dataSource: TableViewSectionedDataSource<SettingsSection>,
                tableView: UITableView,
                indexPath: IndexPath,
                item: SettingsSection.Item) in
                switch dataSource[indexPath] {

                case .device(let device):
                    let cell = tableView.dequeueReusableCell(for: indexPath, cellType: DeviceCell.self)

                    cell.deviceIdLabel.text = device.deviceId
281 282 283
                    if let deviceName = device.deviceName {
                        cell.deviceNameLabel.text = deviceName
                    }
284
                    cell.selectionStyle = .none
285 286 287 288
                    cell.removeDevice.isHidden = device.isCurrent
                    cell.removeDevice.rx.tap.subscribe(onNext: { [weak self, device] in
                        self?.confirmRevokeDeviceAlert(deviceID: device.deviceId)
                    }).disposed(by: cell.disposeBag)
289 290 291 292 293
                    return cell

                case .linkNew:
                    let cell = tableView.dequeueReusableCell(for: indexPath, cellType: LinkNewDeviceCell.self)

294 295
                    cell.addDeviceButton.rx.tap.subscribe(onNext: { [weak self] in
                        self?.viewModel.linkDevice()
296
                    }).disposed(by: cell.disposeBag)
297 298
                    cell.addDeviceTitle.rx.tap.subscribe(onNext: { [weak self] in
                        self?.viewModel.linkDevice()
299
                    }).disposed(by: cell.disposeBag)
300
                    cell.addDeviceTitle.setTitle(L10n.AccountPage.linkDeviceTitle, for: .normal)
301 302
                    cell.selectionStyle = .none
                    return cell
303

304 305 306
                case .blockedList:
                    let cell = tableView.dequeueReusableCell(for: indexPath,
                    cellType: BlockContactsCell.self)
307
                    cell.label.text = L10n.AccountPage.blockedContacts
308
                    return cell
309 310 311 312

                case .sectionHeader(let title):
                    let cell = UITableViewCell()
                    cell.textLabel?.text = title
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
313
                    cell.backgroundColor = UIColor.jamiNavigationBar
314
                    cell.selectionStyle = .none
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
315
                    cell.heightAnchor.constraint(equalToConstant: 35).isActive = true
316 317
                    return cell

318 319 320
                case .removeAccount:
                    let cell = DisposableCell()
                    cell.textLabel?.text = L10n.AccountPage.removeAccountTitle
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
321 322 323
                    cell.textLabel?.textColor = UIColor.jamiMain
                    cell.textLabel?.textAlignment = .center
                    cell.selectionStyle = .none
324
                    let button = UIButton.init(frame: cell.frame)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
325 326
                    let size = CGSize(width: self.view.frame.width, height: button.frame.height)
                    button.frame.size = size
327 328 329 330 331 332
                    cell.addSubview(button)
                    button.rx.tap.subscribe(onNext: { [weak self] in
                        self?.confirmRemoveAccountAlert()
                    }).disposed(by: cell.disposeBag)
                    return cell

333 334 335 336 337
                case .ordinary(let label):
                    let cell = UITableViewCell()
                    cell.textLabel?.text = label
                    cell.selectionStyle = .none
                    return cell
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
                case .shareAccountDetails:
                    let cell = DisposableCell()
                    cell.textLabel?.text = L10n.AccountPage.shareAccountDetails
                    cell.textLabel?.textColor = UIColor.jamiMain
                    cell.textLabel?.textAlignment = .center
                    cell.selectionStyle = .none
                    let button = UIButton.init(frame: cell.frame)
                    let size = CGSize(width: self.view.frame.width, height: button.frame.height)
                    button.frame.size = size
                    cell.addSubview(button)
                    button.rx.tap.subscribe(onNext: { [weak self] in
                        self?.shareAccountInfo()
                    }).disposed(by: cell.disposeBag)
                    return cell

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
353 354 355 356
                case .notifications:
                    let cell = tableView.dequeueReusableCell(for: indexPath,
                                                             cellType: NotificationCell.self)
                    cell.selectionStyle = .none
357
                    cell.enableNotificationsLabel.text = L10n.AccountPage.enableNotifications
358
                    self.viewModel.notificationsEnabled.bind(to: cell.enableNotificationsSwitch.rx.value)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
359 360 361 362
                        .disposed(by: cell.disposeBag)
                    cell.enableNotificationsSwitch.rx.value.skip(1)
                        .observeOn(MainScheduler.instance)
                        .subscribe(onNext: { [weak self] (enable) in
363
                            self?.viewModel.enableNotifications(enable: enable)
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
364 365
                        }).disposed(by: cell.disposeBag)
                    return cell
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
                case .sipUserName(let value):
                    let cell = self
                        .configureSipCredentialsCell(cellType: .sipUserName(value: value),
                                                     value: value)
                    return cell
                case .sipPassword(let value):
                    let cell = self
                    .configureSipCredentialsCell(cellType: .sipPassword(value: value),
                                                 value: value)
                    return cell
                case .sipServer(let value):
                    let cell = self
                        .configureSipCredentialsCell(cellType: .sipServer(value: value),
                                                     value: value)
                    return cell
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
381 382 383 384 385
                case .proxyServer(let value):
                let cell = self
                    .configureSipCredentialsCell(cellType: .proxyServer(value: value),
                                                 value: value)
                return cell
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
                case .port(let value):
                    let cell = self
                        .configureSipCredentialsCell(cellType: .port(value: value),
                                                     value: value)
                    return cell
                case .accountState(let state):
                    let cell = DisposableCell()
                    cell.textLabel?.text = L10n.Account.accountStatus
                    cell.selectionStyle = .none
                    cell.textLabel?.sizeToFit()
                    let text = UILabel()
                    text.font = self.getSettingsFont()
                    text.frame = CGRect(x: self.sipCredentialsMargin, y: 0,
                                        width: cell.frame.width - self.sipCredentialsMargin,
                                        height: cell.frame.height)
                    text.text = state.value
                    state.asObservable()
                        .observeOn(MainScheduler.instance)
                        .bind(to: text.rx.text)
                        .disposed(by: cell.disposeBag)
                    cell.contentView.addSubview(text)
                    return cell
                case .enableAccount:
                    let cell = DisposableCell()
                    cell.textLabel?.text = L10n.Account.enableAccount
                    let switchView = UISwitch()
                    cell.selectionStyle = .none
                    switchView.frame = CGRect(x: self.view.frame.size.width - 63,
                                              y: cell.frame.size.height * 0.5 - 15,
                                              width: 49, height: 30)
                    cell.contentView.addSubview(switchView)
                    switchView.setOn(self.viewModel.accountEnabled.value,
                                     animated: false)
                    self.viewModel.accountEnabled
                        .asObservable()
                        .observeOn(MainScheduler.instance)
                        .bind(to: switchView.rx.value)
                        .disposed(by: cell.disposeBag)
                    switchView.rx.value
                        .observeOn(MainScheduler.instance)
                        .subscribe(onNext: { [weak self] (enable) in
                            self?.viewModel.enableAccount(enable: enable)
                        }).disposed(by: cell.disposeBag)
                    return cell
430 431 432
                }
        }

433
        let settingsItemDataSource = RxTableViewSectionedReloadDataSource<SettingsSection>(configureCell: configureCell)
434
        self.viewModel.settings
435
            .bind(to: self.settingsTable.rx.items(dataSource: settingsItemDataSource))
436
            .disposed(by: disposeBag)
437
    }
438

Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
    func getSettingsFont() -> UIFont {
        return UIFont.systemFont(ofSize: 18, weight: .light)
    }

    func configureSipCredentialsCell(cellType: SettingsSection.SectionRow,
                                     value: String) -> UITableViewCell {
        let cell = DisposableCell()
        cell.selectionStyle = .none
        let text = UITextField()
        text.tag = self.sipCredentialsTAG
        text.font = self.getSettingsFont()
        text.frame = CGRect(x: self.sipCredentialsMargin, y: 0,
                            width: self.view.frame.width - self.sipCredentialsMargin,
                            height: cell.frame.height)
        text.text = value
        text.returnKeyType = .done
        text.rx.controlEvent(.editingDidEndOnExit)
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { [weak self] _ in
                self?.viewModel.updateSipSettings()
            }).disposed(by: cell.disposeBag)
        switch cellType {
        case .port:
            text.rx.text.orEmpty.distinctUntilChanged()
                .bind(to: self.viewModel.port)
                .disposed(by: cell.disposeBag)
            cell.textLabel?.text = L10n.Account.port
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
466 467 468 469 470
        case .proxyServer:
            text.rx.text.orEmpty.distinctUntilChanged()
                .bind(to: self.viewModel.proxyServer)
                .disposed(by: cell.disposeBag)
            cell.textLabel?.text = L10n.Account.proxyServer
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
        case .sipServer:
            text.rx.text.orEmpty.distinctUntilChanged()
                .bind(to: self.viewModel.sipServer)
                .disposed(by: cell.disposeBag)
            cell.textLabel?.text = L10n.Account.sipServer
        case .sipPassword:
            cell.textLabel?.text = L10n.Account.sipPassword
            //show password button
            var rightButton  = UIButton(type: .custom)
            rightButton.frame = CGRect(x: 0, y: 0, width: 55, height: 30)
            self.viewModel.secureTextEntry
                .asObservable()
                .observeOn(MainScheduler.instance)
                .subscribe(onNext: { (secure) in
                    text.isSecureTextEntry = secure
                    if secure {
                        rightButton.setImage(UIImage(asset: Asset.icHideInput),
                                             for: .normal)
                    } else {
                        rightButton.setImage(UIImage(asset: Asset.icShowInput),
                                             for: .normal)
                    }
                }).disposed(by: cell.disposeBag)
            rightButton.tintColor = UIColor.darkGray
            text.rightViewMode = .always
            text.rightView = rightButton
            rightButton.rx.tap
                .subscribe(onNext: { [unowned self] _ in
                    self.viewModel.secureTextEntry
                        .onNext(!text.isSecureTextEntry)
                }).disposed(by: cell.disposeBag)
            text.rx.text.orEmpty.distinctUntilChanged()
                .bind { [weak self, weak rightButton] newText in
                    self?.viewModel.sipPassword.value = newText
                    rightButton?.isHidden = newText.isEmpty
                    rightButton?.isEnabled = !newText.isEmpty
                }.disposed(by: cell.disposeBag)
        case .sipUserName:
            text.rx.text.orEmpty.distinctUntilChanged()
                .bind(to: self.viewModel.sipUsername)
                .disposed(by: cell.disposeBag)
            cell.textLabel?.text = L10n.Account.sipUsername
        default:
            break
        }
        cell.contentView.addSubview(text)
        return cell
    }

    func calculateSipCredentialsMargin() {
        let margin: CGFloat = 30
        var usernameLength, passwordLength,
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
523 524
        sipServerLength, portLength,
        statusLength, proxyLength: CGFloat
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
525 526 527 528 529
        let username = L10n.Account.sipUsername
        let password = L10n.Account.sipPassword
        let sipServer = L10n.Account.port
        let port = L10n.Account.sipServer
        let status = L10n.Account.accountStatus
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
530
        let proxy = L10n.Account.proxyServer
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
        let label = UITextView()
        label.font = UIFont.systemFont(ofSize: 16, weight: .regular)
        label.text = status
        label.sizeToFit()
        statusLength = label.frame.size.width
        label.text = username
        label.sizeToFit()
        usernameLength = label.frame.size.width
        label.text = password
        label.sizeToFit()
        passwordLength = label.frame.size.width
        label.text = sipServer
        label.sizeToFit()
        sipServerLength = label.frame.size.width
        label.text = port
        label.sizeToFit()
        portLength = label.frame.size.width
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
548 549 550 551
        label.text = proxy
        label.sizeToFit()
        proxyLength = label.frame.size.width
        sipCredentialsMargin = max(max(max(max(max(usernameLength, passwordLength), sipServerLength), portLength), statusLength), proxyLength) + margin
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
552 553
    }

554
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
Kateryna Kostiuk's avatar
Kateryna Kostiuk committed
555 556 557 558 559 560 561 562 563 564 565
        if self.profileName.isFirstResponder {
            resetProfileName()
            self.viewModel.updateSipSettings()
            self.profileName.resignFirstResponder()
            return
        }
        guard let activeField = self
            .findActiveTextField(in: settingsTable) else {return}
        activeField.resignFirstResponder()
        if activeField.tag != sipCredentialsTAG {return}
        self.viewModel.updateSipSettings()
566
    }
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594

    func confirmRevokeDeviceAlert(deviceID: String) {
        let alert = UIAlertController(title: L10n.AccountPage.revokeDeviceTitle,
                                      message: L10n.AccountPage.revokeDeviceMessage,
                                      preferredStyle: .alert)
        let actionCancel = UIAlertAction(title: L10n.Actions.cancelAction,
                                         style: .cancel)
        let actionConfirm = UIAlertAction(title: L10n.AccountPage.revokeDeviceButton,
                                          style: .default) { [weak self] _ in
                                            self?.showLoadingView()
                                            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                                                if let textFields = alert.textFields,
                                                    !textFields.isEmpty,
                                                    let text = textFields[0].text,
                                                    !text.isEmpty {
                                                    self?.viewModel.revokeDevice(deviceId: deviceID, accountPassword: text)
                                                } else {
                                                    self?.viewModel.revokeDevice(deviceId: deviceID, accountPassword: "")
                                                    self?.stopLoadingView()
                                                }
                                            }
        }
        alert.addAction(actionCancel)
        alert.addAction(actionConfirm)

        if self.viewModel.havePassord {
            alert.addTextField {(textField) in
                textField.placeholder = L10n.AccountPage.revokeDevicePlaceholder
595
                textField.isSecureTextEntry = true
596 597 598 599 600 601 602 603 604 605 606 607
            }
            if let textFields = alert.textFields {
                textFields[0].rx.text.map({text in
                    if let text = text {
                        return !text.isEmpty
                    }
                    return false
                }).bind(to: actionConfirm.rx.isEnabled).disposed(by: self.disposeBag)
            }
        }
        self.present(alert, animated: true, completion: nil)
    }
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627

    func confirmRemoveAccountAlert() {
        let alert = UIAlertController(title: L10n.AccountPage.removeAccountTitle,
                                      message: L10n.AccountPage.removeAccountMessage,
                                      preferredStyle: .alert)
        let actionCancel = UIAlertAction(title: L10n.Actions.cancelAction,
                                         style: .cancel)
        let actionConfirm = UIAlertAction(title: L10n.AccountPage.removeAccountButton,
                                          style: .destructive) { [weak self] _ in
                                            UIView.animate(withDuration: 0.1, animations: {
                                                self?.view.alpha = 0
                                            }, completion: { _ in
                                                self?.viewModel.startAccountRemoving()
                                                self?.view.alpha = 1
                                            })
        }
        alert.addAction(actionCancel)
        alert.addAction(actionConfirm)
        self.present(alert, animated: true, completion: nil)
    }
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
}

extension MeViewController: UITableViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let navigationHeight = self.navigationController?.navigationBar.bounds.height
        var size = self.view.bounds.size
        let screenSize = UIScreen.main.bounds.size
        if let height = navigationHeight {
            //height for ihoneX
            if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.phone,
                screenSize.height == 812.0 {
                size.height -= (height - 10)
            }
        }
        if scrollView.contentSize.height < size.height {
            scrollView.contentSize = size
        }

        // hide keebord if it was open when user performe scrolling
        if self.stretchyHeader.frame.height < self.stretchyHeader.maximumContentHeight * 0.5 {
            resetProfileName()
            self.profileName.resignFirstResponder()
        }
    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        self.scrollViewDidStopScrolling()
    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
        if !decelerate {
            self.scrollViewDidStopScrolling()
        }
    }

663 664 665 666 667 668 669 670 671 672
    func shareAccountInfo() {
        guard let content = self.viewModel.accountInfoToShare else {return}
        let title = L10n.AccountPage.contactMeOnJamiTitle
        let activityViewController = UIActivityViewController(activityItems: content,
                                                              applicationActivities: nil)
        activityViewController.setValue(title, forKey: "Subject")
        activityViewController.popoverPresentationController?.sourceView = self.view
        self.present(activityViewController, animated: true, completion: nil)
    }

673 674 675 676 677 678 679 680 681 682
    private func scrollViewDidStopScrolling() {
        var contentOffset = self.settingsTable.contentOffset
        if self.stretchyHeader.frame.height <= self.stretchyHeader.minimumContentHeight {
            return
        }
        let middle = (self.stretchyHeader.maximumContentHeight - self.stretchyHeader.minimumContentHeight) * 0.4
        if self.stretchyHeader.frame.height > middle {
            contentOffset.y = -self.stretchyHeader.maximumContentHeight
        } else {
            contentOffset.y = -self.stretchyHeader.minimumContentHeight
683
        }
684
        self.settingsTable.setContentOffset(contentOffset, animated: true)
Guillaume Roguez's avatar
Guillaume Roguez committed
685 686
    }
}