Signal-iOS/SignalUI/ViewControllers/SheetViewController.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

224 lines
8.1 KiB
Swift

//
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
@objc(OWSSheetViewController)
open class SheetViewController: UIViewController {
public var dismissHandler: ((SheetViewController) -> Void)?
@objc
public let contentView: UIView = UIView()
private let sheetView: SheetView = SheetView()
private let handleView: UIView = UIView()
public var isHandleHidden: Bool {
get { handleView.isHidden }
set { handleView.isHidden = newValue }
}
deinit {
Logger.verbose("")
}
@objc
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.transitioningDelegate = self
self.modalPresentationStyle = .overCurrentContext
}
public required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: View LifeCycle
var sheetViewVerticalConstraint: NSLayoutConstraint?
override public func loadView() {
self.view = UIView()
sheetView.preservesSuperviewLayoutMargins = true
sheetView.addSubview(contentView)
contentView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom)
contentView.autoPinEdge(toSuperviewMargin: .bottom)
view.addSubview(sheetView)
sheetView.autoPinWidthToSuperview()
sheetView.setContentHuggingVerticalHigh()
sheetView.setCompressionResistanceHigh()
self.sheetViewVerticalConstraint = sheetView.autoPinEdge(.top, to: .bottom, of: self.view)
handleView.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_white : UIColor.ows_gray05
let kHandleViewHeight: CGFloat = 5
handleView.autoSetDimensions(to: CGSize(width: 40, height: kHandleViewHeight))
handleView.layer.cornerRadius = kHandleViewHeight / 2
view.addSubview(handleView)
handleView.autoAlignAxis(.vertical, toSameAxisOf: sheetView)
handleView.autoPinEdge(.bottom, to: .top, of: sheetView, withOffset: -6)
// Gestures
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapBackground))
self.view.addGestureRecognizer(tapGesture)
let swipeDownGesture = UISwipeGestureRecognizer(target: self, action: #selector(didSwipeDown))
swipeDownGesture.direction = .down
self.view.addGestureRecognizer(swipeDownGesture)
}
// MARK: Present / Dismiss animations
fileprivate func animatePresentation(completion: @escaping (Bool) -> Void) {
guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else {
owsFailDebug("sheetViewVerticalConstraint was unexpectedly nil")
return
}
let backgroundDuration: TimeInterval = 0.1
UIView.animate(withDuration: backgroundDuration) {
let alpha: CGFloat = Theme.isDarkThemeEnabled ? 0.7 : 0.6
self.view.backgroundColor = UIColor.black.withAlphaComponent(alpha)
}
self.sheetView.superview?.layoutIfNeeded()
NSLayoutConstraint.deactivate([sheetViewVerticalConstraint])
self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(toSuperviewEdge: .bottom)
UIView.animate(withDuration: 0.2,
delay: backgroundDuration,
options: .curveEaseOut,
animations: {
self.sheetView.superview?.layoutIfNeeded()
},
completion: completion)
}
fileprivate func animateDismiss(completion: @escaping (Bool) -> Void) {
guard let sheetViewVerticalConstraint = self.sheetViewVerticalConstraint else {
owsFailDebug("sheetVerticalConstraint was unexpectedly nil")
return
}
self.sheetView.superview?.layoutIfNeeded()
NSLayoutConstraint.deactivate([sheetViewVerticalConstraint])
let dismissDuration: TimeInterval = 0.2
self.sheetViewVerticalConstraint = self.sheetView.autoPinEdge(.top, to: .bottom, of: self.view)
UIView.animate(withDuration: dismissDuration,
delay: 0,
options: .curveEaseOut,
animations: {
self.view.backgroundColor = UIColor.clear
self.sheetView.superview?.layoutIfNeeded()
},
completion: completion)
}
// MARK: Actions
@objc
func didTapBackground() {
dismissHandler?(self)
}
@objc
func didSwipeDown() {
dismissHandler?(self)
}
}
extension SheetViewController: UIViewControllerTransitioningDelegate {
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SheetViewPresentationController(sheetViewController: self)
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SheetViewDismissalController(sheetViewController: self)
}
}
private class SheetViewPresentationController: NSObject, UIViewControllerAnimatedTransitioning {
let sheetViewController: SheetViewController
init(sheetViewController: SheetViewController) {
self.sheetViewController = sheetViewController
}
// This is used for percent driven interactive transitions, as well as for
// container controllers that have companion animations that might need to
// synchronize with the main animation.
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
// This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
Logger.debug("")
transitionContext.containerView.addSubview(sheetViewController.view)
sheetViewController.view.autoPinEdgesToSuperviewEdges()
sheetViewController.animatePresentation { didComplete in
Logger.debug("completed: \(didComplete)")
transitionContext.completeTransition(didComplete)
}
}
}
private class SheetViewDismissalController: NSObject, UIViewControllerAnimatedTransitioning {
let sheetViewController: SheetViewController
init(sheetViewController: SheetViewController) {
self.sheetViewController = sheetViewController
}
// This is used for percent driven interactive transitions, as well as for
// container controllers that have companion animations that might need to
// synchronize with the main animation.
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
// This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
Logger.debug("")
sheetViewController.animateDismiss { didComplete in
Logger.debug("completed: \(didComplete)")
transitionContext.completeTransition(didComplete)
}
}
}
private class SheetView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = Theme.isDarkThemeEnabled ? UIColor.ows_gray90
: UIColor.ows_gray05
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var bounds: CGRect {
didSet {
updateMask()
}
}
private func updateMask() {
let cornerRadius: CGFloat = 16
let path: UIBezierPath = UIBezierPath(roundedRect: bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(square: cornerRadius))
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
}
}