summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/View controllers/Settings/SettingsInputCell.swift
blob: f3239b758d9bac5ca8626f49c7917b679c4b4753 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//
//  SettingsInputCell.swift
//  MullvadVPN
//
//  Created by Jon Petersson on 2023-05-05.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import UIKit

class SettingsInputCell: SelectableSettingsCell {
    let textField = CustomTextField(frame: CGRect(origin: .zero, size: CGSize(width: 100, height: 30)))
    var toolbarDoneButton = UIBarButtonItem()

    var isValidInput: Bool {
        didSet {
            updateTextFieldInputValidity()
        }
    }

    var inputDidChange: ((String) -> Void)?
    var inputWasConfirmed: ((String) -> Void)?

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        isValidInput = true

        super.init(style: style, reuseIdentifier: reuseIdentifier)

        toolbarDoneButton = UIBarButtonItem(
            title: NSLocalizedString("Done", comment: ""),
            style: .done,
            target: self,
            action: #selector(confirmInput)
        )

        accessoryView = textField

        setUpTextField()
        setUpTextFieldToolbar()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepareForReuse() {
        super.prepareForReuse()

        reset()
    }

    func reset() {
        textField.text = nil
        UITextField.SearchTextFieldAppearance.inactive.apply(to: textField)
    }

    func setInput(_ text: String) {
        textField.text = text
        textFieldDidChange(textField)
    }

    @objc func confirmInput() {
        _ = textFieldShouldReturn(textField)
    }

    @objc private func textFieldDidChange(_ textField: UITextField) {
        if let text = textField.text {
            inputDidChange?(text)
            toolbarDoneButton.isEnabled = isValidInput
        }
    }

    private func setUpTextField() {
        textField.borderStyle = .none
        textField.layer.cornerRadius = 4
        textField.font = .preferredFont(forTextStyle: .body)
        textField.textAlignment = .right
        textField.delegate = self
        textField.keyboardType = .numberPad
        textField.returnKeyType = .done
        textField.textMargins = UIMetrics.SettingsCell.inputCellTextFieldLayoutMargins
        textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)

        UITextField.SearchTextFieldAppearance.inactive.apply(to: textField)
    }

    private func setUpTextFieldToolbar() {
        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44))
        toolbar.items = [
            UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil),
            toolbarDoneButton,
        ]

        toolbar.sizeToFit()

        textField.inputAccessoryView = toolbar
    }

    private func updateTextFieldInputValidity() {
        if isValidInput {
            textField.textColor = textField.isEditing ? .SearchTextField.textColor : .SearchTextField.inactiveTextColor
        } else {
            textField.textColor = UIColor.TextField.invalidInputTextColor
        }
    }
}

extension SettingsInputCell: UITextFieldDelegate {
    func textFieldDidBeginEditing(_ textField: UITextField) {
        inputDidChange?(textField.text ?? "")
        toolbarDoneButton.isEnabled = isValidInput

        UITextField.SearchTextFieldAppearance.active.apply(to: textField)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        guard isValidInput else { return false }

        inputWasConfirmed?(textField.text ?? "")
        textField.resignFirstResponder()

        return true
    }

    func textFieldDidEndEditing(_ textField: UITextField) {
        UITextField.SearchTextFieldAppearance.inactive.apply(to: textField)
    }
}