Signal-iOS/SignalUI/ViewControllers/ApprovalFooterView.swift
Evan Hahn 370ff654e7
Change license to AGPL
Change license to AGPL

This commit:

- Updates the `LICENSE` file

- Start every file with something like:

      // Copyright YEAR_FIRST_PUBLISHED Signal Messenger, LLC
      // SPDX-License-Identifier: AGPL-3.0-only

---

First, I removed existing license headers with this Ruby 3.1.2 script:

    require 'set'

    EXTENSIONS_TO_CHECK = Set['.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift']

    same = 0
    different = 0

    all_files = `git ls-files`.lines.map { |line| line.strip }
    all_files.each do |relative_path|
      if relative_path == 'Pods'
        next
      end

      unless EXTENSIONS_TO_CHECK.include? File.extname(relative_path)
        next
      end

      path = File.expand_path(relative_path)

      contents = File.read(path)
      new_contents = contents.sub(/\/\/\n\/\/  Copyright .*\n\/\/\n\n/, '')

      if contents == new_contents
        same += 1
      else
        different += 1
      end

      File.write(path, new_contents)
    end

    puts "updated #{different} file(s), left #{same} untouched"

I'm sure this script could be improved, but it worked well enough.

Then, I created `Scripts/lint/lint-license-headers` and ran it to auto-
fix a lot of files. This changed the mode of some files, but I think
that's actually desirable. For example,
`SignalServiceKit/src/Util/AppContext.m` previously had a mode of
`0755/-rwxr-xr-x`, and it's now `0644/-rw-r--r--`.

Then I fixed some stragglers and updated the precommit script.

See [a similar change in the Desktop app][0].

[0]: 8bfaf598af
2022-10-13 08:25:37 -05:00

264 lines
8.0 KiB
Swift

//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalMessaging
// Outgoing message approval can be a multi-step process.
@objc
public enum ApprovalMode: UInt {
// This is the final step of approval; continuing will send.
case send
// This is not the final step of approval; continuing will not send.
case next
// This is the final step of approval; but it does not send it just selects.
case select
// This step is not yet ready to proceed.
case loading
}
// MARK: -
public protocol ApprovalFooterDelegate: AnyObject {
func approvalFooterDelegateDidRequestProceed(_ approvalFooterView: ApprovalFooterView)
func approvalMode(_ approvalFooterView: ApprovalFooterView) -> ApprovalMode
func approvalFooterDidBeginEditingText()
}
// MARK: -
public class ApprovalFooterView: UIView {
public weak var delegate: ApprovalFooterDelegate? {
didSet {
updateContents()
}
}
private let backgroundView = UIView()
private let topStrokeView = UIView()
private let hStackView = UIStackView()
private let vStackView = UIStackView()
private var textfieldBackgroundView: UIView?
public var textInput: String? {
approvalTextMode == .none ? nil : textfield.text
}
private var approvalMode: ApprovalMode {
guard let delegate = delegate else {
return .send
}
return delegate.approvalMode(self)
}
public enum ApprovalTextMode: Equatable {
case none
case active(placeholderText: String)
}
public var approvalTextMode: ApprovalTextMode = .none {
didSet {
if oldValue != approvalTextMode {
updateContents()
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
autoresizingMask = .flexibleHeight
translatesAutoresizingMaskIntoConstraints = false
layoutMargins = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16)
// We extend our background view below the keyboard to avoid any gaps.
addSubview(backgroundView)
backgroundView.autoPinWidthToSuperview()
backgroundView.autoPinEdge(toSuperviewEdge: .top)
backgroundView.autoPinEdge(toSuperviewEdge: .bottom, withInset: -30)
addSubview(topStrokeView)
topStrokeView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom)
topStrokeView.autoSetDimension(.height, toSize: CGHairlineWidth())
hStackView.addArrangedSubviews([labelScrollView, proceedButton])
hStackView.axis = .horizontal
hStackView.spacing = 12
hStackView.alignment = .center
vStackView.addArrangedSubviews([textfieldStack, hStackView])
vStackView.axis = .vertical
vStackView.spacing = 16
vStackView.alignment = .fill
addSubview(vStackView)
vStackView.autoPinEdgesToSuperviewMargins()
updateContents()
let textfieldBackgroundView = textfieldStack.addBackgroundView(withBackgroundColor: textfieldBackgroundColor)
textfieldBackgroundView.layer.cornerRadius = 10
self.textfieldBackgroundView = textfieldBackgroundView
NotificationCenter.default.addObserver(self, selector: #selector(applyTheme), name: .ThemeDidChange, object: nil)
applyTheme()
}
@objc
private func applyTheme() {
backgroundView.backgroundColor = Theme.keyboardBackgroundColor
topStrokeView.backgroundColor = Theme.hairlineColor
namesLabel.textColor = Theme.secondaryTextAndIconColor
textfieldBackgroundView?.backgroundColor = textfieldBackgroundColor
}
private var textfieldBackgroundColor: UIColor {
OWSTableViewController2.cellBackgroundColor(isUsingPresentedStyle: true)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override var intrinsicContentSize: CGSize {
return CGSize.zero
}
// MARK: public
private var namesText: String? { namesLabel.text }
public func setNamesText(_ newValue: String?, animated: Bool) {
let changes = {
self.namesLabel.text = newValue
self.layoutIfNeeded()
let offset = max(0, self.labelScrollView.contentSize.width - self.labelScrollView.bounds.width)
let trailingEdge = CGPoint(x: offset, y: 0)
self.labelScrollView.setContentOffset(trailingEdge, animated: false)
}
if animated {
UIView.animate(withDuration: 0.1, animations: changes)
} else {
changes()
}
}
// MARK: private subviews
lazy var labelScrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.showsHorizontalScrollIndicator = false
scrollView.addSubview(namesLabel)
namesLabel.autoPinEdgesToSuperviewEdges()
namesLabel.autoMatch(.height, to: .height, of: scrollView)
return scrollView
}()
lazy var namesLabel: UILabel = {
let label = UILabel()
label.font = UIFont.ows_dynamicTypeBody
label.setContentHuggingLow()
return label
}()
lazy var textfield: TextFieldWithPlaceholder = {
let textfield = TextFieldWithPlaceholder()
textfield.delegate = self
textfield.font = UIFont.ows_dynamicTypeBody
return textfield
}()
lazy var textfieldStack: UIStackView = {
let textfieldStack = UIStackView(arrangedSubviews: [textfield])
textfieldStack.axis = .vertical
textfieldStack.alignment = .fill
textfieldStack.layoutMargins = UIEdgeInsets(hMargin: 8, vMargin: 7)
textfieldStack.isLayoutMarginsRelativeArrangement = true
return textfieldStack
}()
var proceedLoadingIndicator = UIActivityIndicatorView(style: .white)
lazy var proceedButton: OWSButton = {
let button = OWSButton.sendButton(
imageName: self.approvalMode.proceedButtonImageName ?? "arrow-right-24"
) { [weak self] in
guard let self = self else { return }
self.delegate?.approvalFooterDelegateDidRequestProceed(self)
}
button.addSubview(proceedLoadingIndicator)
proceedLoadingIndicator.autoCenterInSuperview()
proceedLoadingIndicator.isHidden = true
return button
}()
func updateContents() {
proceedButton.setImage(imageName: approvalMode.proceedButtonImageName)
proceedButton.accessibilityLabel = approvalMode.proceedButtonAccessibilityLabel
switch approvalTextMode {
case .none:
textfieldStack.isHidden = true
textfield.resignFirstResponder()
case .active(let placeholderText):
textfieldStack.isHidden = false
textfield.placeholderText = placeholderText
}
if approvalMode == .loading {
proceedLoadingIndicator.isHidden = false
proceedLoadingIndicator.startAnimating()
} else {
proceedLoadingIndicator.stopAnimating()
proceedLoadingIndicator.isHidden = true
}
}
}
// MARK: -
fileprivate extension ApprovalMode {
var proceedButtonAccessibilityLabel: String? {
switch self {
case .next: return CommonStrings.nextButton
case .send: return MessageStrings.sendButton
case .select: return CommonStrings.doneButton
case .loading: return nil
}
}
var proceedButtonImageName: String? {
switch self {
case .next: return "arrow-right-24"
case .send: return "send-solid-24"
case .select: return "check-24"
case .loading: return nil
}
}
}
// MARK: -
extension ApprovalFooterView: TextFieldWithPlaceholderDelegate {
public func textFieldDidBeginEditing(_ textField: UITextField) {
delegate?.approvalFooterDidBeginEditingText()
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return true
}
}