Skip to content
Snippets Groups Projects
SearchContainerController.swift 8.71 KiB
Newer Older
import UIKit
import Theme
import Shared
import Combine
import XXModels
import DrawerFeature
import DependencyInjection

enum SearchSection {
    case stranger
    case connections
}

enum SearchItem: Equatable, Hashable {
    case stranger(Contact)
    case connection(Contact)
}

class SearchTableViewDiffableDataSource: UITableViewDiffableDataSource<SearchSection, SearchItem> {
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        switch snapshot().sectionIdentifiers[section] {
        case .stranger:
            return ""
        case .connections:
            return "CONNECTIONS"
        }
    }
}

public final class SearchContainerController: UIViewController {
    @Dependency var coordinator: SearchCoordinating
    @Dependency var statusBarController: StatusBarStyleControlling

    lazy private var screenView = SearchContainerView()

    private let qrController = SearchQRController()
    private var cancellables = Set<AnyCancellable>()
    private let viewModel = SearchContainerViewModel()
    private let emailController = SearchEmailController()
    private let phoneController = SearchPhoneController()
    private var drawerCancellables = Set<AnyCancellable>()
    private let usernameController = SearchUsernameController()

    public override func loadView() {
        view = screenView
        screenView.scrollView.delegate = self
        embedControllers()
    }

    public override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        statusBarController.style.send(.darkContent)

        navigationController?.navigationBar.customize(
            backgroundColor: Asset.neutralWhite.color
        )
    }

    public override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        viewModel.didAppear()
    }

    public override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationBar()
        setupBindings()
    }

    private func setupNavigationBar() {
        navigationItem.backButtonTitle = " "

        let titleLabel = UILabel()
        titleLabel.text = Localized.Ud.title
        titleLabel.textColor = Asset.neutralActive.color
        titleLabel.font = Fonts.Mulish.semiBold.font(size: 18.0)

        let backButton = UIButton.back()
        backButton.addTarget(self, action: #selector(didTapBack), for: .touchUpInside)

        navigationItem.leftBarButtonItem = UIBarButtonItem(
            customView: UIStackView(arrangedSubviews: [backButton, titleLabel])
        )
    }

    private func setupBindings() {
        screenView.segmentedControl
            .actionPublisher
            .receive(on: DispatchQueue.main)
            .sink { [unowned self] in
                let page = CGFloat($0.rawValue)
                let point: CGPoint = CGPoint(x: screenView.frame.width * page, y: 0.0)
                screenView.scrollView.setContentOffset(point, animated: true)
            }.store(in: &cancellables)

        viewModel.coverTrafficPublisher
            .receive(on: DispatchQueue.main)
            .sink { [unowned self] in presentCoverTrafficDrawer() }
            .store(in: &cancellables)

    @objc private func didTapBack() {
        navigationController?.popViewController(animated: true)
    }

    private func embedControllers() {
        addChild(qrController)
        addChild(emailController)
        addChild(phoneController)
        addChild(usernameController)

        screenView.scrollView.addSubview(qrController.view)
        screenView.scrollView.addSubview(emailController.view)
        screenView.scrollView.addSubview(phoneController.view)
        screenView.scrollView.addSubview(usernameController.view)

        usernameController.view.snp.makeConstraints {
            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
            $0.width.equalTo(screenView)
            $0.bottom.equalTo(screenView)
            $0.left.equalToSuperview()
            $0.right.equalTo(emailController.view.snp.left)
        }

        emailController.view.snp.makeConstraints {
            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
            $0.width.equalTo(screenView)
            $0.bottom.equalTo(screenView)
            $0.right.equalTo(phoneController.view.snp.left)
        }

        phoneController.view.snp.makeConstraints {
            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
            $0.width.equalTo(screenView)
            $0.bottom.equalTo(screenView)
            $0.right.equalTo(qrController.view.snp.left)
        }

        qrController.view.snp.makeConstraints {
            $0.top.equalTo(screenView.segmentedControl.snp.bottom)
            $0.width.equalTo(screenView)
            $0.bottom.equalTo(screenView)
        }

        qrController.didMove(toParent: self)
        emailController.didMove(toParent: self)
        phoneController.didMove(toParent: self)
        usernameController.didMove(toParent: self)
    }
}

extension SearchContainerController: UIScrollViewDelegate {
    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let pageOffset = scrollView.contentOffset.x / view.frame.width
        scrollSegmentedControlTrack(using: pageOffset)
        updateSegmentedControlButtonsColor(using: pageOffset)
    }

    private func scrollSegmentedControlTrack(using pageOffset: CGFloat) {
        let amountOfTabs = 4.0
        let tabWidth = screenView.bounds.width / amountOfTabs

        if let leftConstraint = screenView.segmentedControl.leftConstraint {
            leftConstraint.update(offset: pageOffset * tabWidth)
        }
    }

    private func updateSegmentedControlButtonsColor(using pageOffset: CGFloat) {
        let qrRate = highlightRateFor(page: 3, offset: pageOffset)
        let emailRate = highlightRateFor(page: 1, offset: pageOffset)
        let phoneRate = highlightRateFor(page: 2, offset: pageOffset)
        let usernameRate = highlightRateFor(page: 0, offset: pageOffset)

        screenView.segmentedControl.qrCodeButton.updateHighlighting(rate: qrRate)
        screenView.segmentedControl.emailButton.updateHighlighting(rate: emailRate)
        screenView.segmentedControl.phoneButton.updateHighlighting(rate: phoneRate)
        screenView.segmentedControl.usernameButton.updateHighlighting(rate: usernameRate)
    }

    private func highlightRateFor(page: CGFloat, offset: CGFloat) -> CGFloat {
        let lowerBound = page - 1
        let upperBound = page + 1

        if offset > lowerBound && offset < upperBound {
            if (offset - lowerBound) > 1 {
                return 1 - (offset - page)
            } else {
                return offset - lowerBound
            }
        } else {
            return 0
        }
    }

extension SearchContainerController {
    private func presentCoverTrafficDrawer() {
        let enableButton = CapsuleButton()
        enableButton.set(
            style: .brandColored,
            title: Localized.ChatList.Traffic.positive
        )

        let dismissButton = CapsuleButton()
        dismissButton.set(
            style: .seeThrough,
            title: Localized.ChatList.Traffic.negative
        )

        let drawer = DrawerController(with: [
            DrawerText(
                font: Fonts.Mulish.bold.font(size: 26.0),
                text: Localized.ChatList.Traffic.title,
                color: Asset.neutralActive.color,
                alignment: .left,
                spacingAfter: 19
            ),
            DrawerText(
                font: Fonts.Mulish.regular.font(size: 16.0),
                text: Localized.ChatList.Traffic.subtitle,
                color: Asset.neutralBody.color,
                alignment: .left,
                lineHeightMultiple: 1.1,
                spacingAfter: 39
            ),
            DrawerStack(
                axis: .horizontal,
                spacing: 20,
                distribution: .fillEqually,
                views: [enableButton, dismissButton]
            )
        ])

        enableButton
            .publisher(for: .touchUpInside)
            .receive(on: DispatchQueue.main)
            .sink {
                drawer.dismiss(animated: true) { [weak self] in
                    guard let self = self else { return }
                    self.drawerCancellables.removeAll()
                    self.viewModel.didEnableCoverTraffic()
                }
            }.store(in: &drawerCancellables)

        dismissButton
            .publisher(for: .touchUpInside)
            .receive(on: DispatchQueue.main)
            .sink {
                drawer.dismiss(animated: true) { [weak self] in
                    guard let self = self else { return }
                    self.drawerCancellables.removeAll()
                }
            }.store(in: &drawerCancellables)

        coordinator.toDrawer(drawer, from: self)
    }
}