philips-software/cogito

View on GitHub
workspaces/cogito-ios-app/Cogito/Identity manager/FacetDetailsViewController.swift

Summary

Maintainability
A
1 hr
Test Coverage
F
0%
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
import JWTDecode
import Toast_Swift
import MobileCoreServices

class FacetDetailsViewController: UITableViewController {
    var facet: Identity? {
        didSet {
            self.title = facet?.description
            updateViewModel()
        }
    }

    var sections: [ViewModel.SectionModel] = []
    let disposeBag = DisposeBag()

    func updateViewModel() {
        createViewModel()
        bindToViewModel()
    }

    func retireveJWTFrom(attestation: Attestation) -> JWT? {
        return try? JWTDecode.decode(jwt: attestation.value)
    }

    func processOpenIdAttestation(_ attestation: Attestation) {
        if let jwt = retireveJWTFrom(attestation: attestation) {
            let items = createItems(for: jwt)
            if items.count > 0 {
                sections.append(.attestationsSection(title: "OpenID Token", items: items))
            }
        }
    }

    func processOpenIdAttestations(_ attestations: [Attestation]) {
        for attestation in attestations {
            processOpenIdAttestation(attestation)
        }
    }

    func processRegularAttestations(_ attestations: [Attestation]) {
        let attestationRows: [ViewModel.SectionItem] = attestations.map {
            .facetDetailItem(title: $0.type.capitalized + ":", detail: $0.value)
        }
        if attestationRows.count > 0 {
            sections.append(.attestationsSection(title: "Attestations", items: attestationRows))
        }
    }

    func splitAttestationsIn(facet: Identity)
        -> (regularAttestations: [Attestation], openIdAttestations: [Attestation]) {
        var attestations = facet.attestations
        let pivot = attestations.partition { $0.isOidcToken }
        return (
            regularAttestations: Array(attestations[0..<pivot]),
            openIdAttestations: Array(attestations[pivot...])
        )
    }

    func processAttestations(_ facet: Identity) {
        let attestations = splitAttestationsIn(facet: facet)
        processRegularAttestations(attestations.regularAttestations)
        processOpenIdAttestations(attestations.openIdAttestations)
    }

    func createViewModel() {
        guard let facet = self.facet else {
            sections = []
            return
        }

        sections = [
            .facetDetailsSection(title: "Details", items: [
                .facetDetailItem(title: "Created:",
                                 detail: facet.created.description(with: Locale.autoupdatingCurrent)),
                .facetDetailItem(title: "Address:",
                                 detail: facet.address.description)
            ])
        ]

        processAttestations(facet)
    }

    func createItems(for jwt: JWT) -> [ViewModel.SectionItem] {
        var items = [ViewModel.SectionItem]()
        if let issuer = jwt.issuer {
            items.append(.facetDetailItem(title: "Issuer:", detail: issuer))
        }
        if let subject = jwt.subject {
            items.append(.facetDetailItem(title: "Subject:", detail: subject))
        }
        if let expiresAt = jwt.expiresAt {
            items.append(.facetDetailItem(
                title: "Expires at:",
                detail: expiresAt.description(with: Locale.autoupdatingCurrent)))
        }
        if let audience = jwt.audience {
            items.append(.facetDetailItem(title: "Audience:",
                                          detail: audience.joined(separator: ", ")))
        }
        return items
    }

    func bindToViewModel() {
        self.tableView.dataSource = nil

        let dataSource = RxTableViewSectionedReloadDataSource<ViewModel.SectionModel>(
            configureCell: { dataSource, table, indexPath, _ in
                switch dataSource[indexPath] {
                case let .facetDetailItem(title, detail):
                    let cell = table.dequeueReusableCell(withIdentifier: "Normal", for: indexPath)
                    cell.textLabel?.text = title
                    cell.detailTextLabel?.text = detail
                    cell.selectionStyle = title == "Address:" ? .default : .none
                    return cell
                }
            },
            titleForHeaderInSection: { dataSource, index in
                let section = dataSource[index]
                return section.title
            }
        )
        tableView.rx.itemSelected.subscribe(onNext: { [unowned self] indexPath in
            self.tableView.deselectRow(at: indexPath, animated: true)
            guard let text = self.facet?.address.description else { return }
            let pasteBoard = UIPasteboard.general
            pasteBoard.setValue(text, forPasteboardType: kUTTypeUTF8PlainText as String)
            self.view.makeToast("Copied!", duration: 1, position: .center)
        }).disposed(by: disposeBag)

        Observable.just(sections)
            .bind(to: tableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
    }
}

extension FacetDetailsViewController {
    struct ViewModel {
        // swiftlint:disable nesting

        enum SectionModel {
            case facetDetailsSection(title: String, items: [SectionItem])
            case attestationsSection(title: String, items: [SectionItem])
        }

        enum SectionItem {
            case facetDetailItem(title: String, detail: String)
        }
    }
}

extension FacetDetailsViewController.ViewModel.SectionModel: SectionModelType {
    typealias Item = FacetDetailsViewController.ViewModel.SectionItem

    var title: String {
        switch self {
        case .facetDetailsSection(title: let title, items: _): return title
        case .attestationsSection(title: let title, items: _): return title
        }
    }

    var items: [FacetDetailsViewController.ViewModel.SectionItem] {
        switch self {
        case .facetDetailsSection(title: _, items: let items):
            return items.map { $0 }
        case .attestationsSection(title: _, items: let items):
            return items.map { $0 }
        }
    }

    init(original: FacetDetailsViewController.ViewModel.SectionModel,
         items: [FacetDetailsViewController.ViewModel.SectionItem]) {
        switch original {
        case let .facetDetailsSection(title: title, items: _):
            self = .facetDetailsSection(title: title, items: items)
        case let .attestationsSection(title: title, items: _):
            self = .attestationsSection(title: title, items: items)
        }
    }
}