Further attach menu refinements

This commit is contained in:
Ilya Laktyushin
2022-02-13 04:11:04 +03:00
parent 7da0357b6d
commit 9994bb44ad
158 changed files with 1843 additions and 5512 deletions

View File

@@ -21,21 +21,60 @@ public enum AttachmentButtonType: Equatable {
case app(String)
}
public protocol AttachmentContainable: ViewController {
var requestAttachmentMenuExpansion: () -> Void { get set }
}
public protocol AttachmentMediaPickerContext {
var selectionCount: Signal<Int, NoError> { get }
var caption: Signal<NSAttributedString?, NoError> { get }
func setCaption(_ caption: NSAttributedString)
func send(silently: Bool)
func schedule()
}
public class AttachmentController: ViewController {
private let context: AccountContext
private let buttons: [AttachmentButtonType]
private final class Node: ASDisplayNode {
private weak var controller: AttachmentController?
private let dim: ASDisplayNode
private let container: AttachmentContainer
private let panel: AttachmentPanel
let panel: AttachmentPanel
private var validLayout: ContainerViewLayout?
private var modalProgress: CGFloat = 0.0
private var currentType: AttachmentButtonType?
private var currentController: ViewController?
private var currentController: AttachmentContainable?
private let captionDisposable = MetaDisposable()
private let mediaSelectionCountDisposable = MetaDisposable()
private var mediaPickerContext: AttachmentMediaPickerContext? {
didSet {
if let mediaPickerContext = self.mediaPickerContext {
self.captionDisposable.set((mediaPickerContext.caption
|> deliverOnMainQueue).start(next: { [weak self] caption in
if let strongSelf = self {
strongSelf.panel.updateCaption(caption ?? NSAttributedString())
}
}))
self.mediaSelectionCountDisposable.set((mediaPickerContext.selectionCount
|> deliverOnMainQueue).start(next: { [weak self] count in
if let strongSelf = self {
strongSelf.updateSelectionCount(count)
}
}))
} else {
self.updateSelectionCount(0)
self.mediaSelectionCountDisposable.set(nil)
}
}
}
init(controller: AttachmentController) {
self.controller = controller
@@ -43,14 +82,15 @@ public class AttachmentController: ViewController {
self.dim.alpha = 0.0
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
self.container = AttachmentContainer(controllerRemoved: { _ in
})
let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
self.container = AttachmentContainer(presentationData: presentationData)
self.container.canHaveKeyboardFocus = true
self.panel = AttachmentPanel(context: controller.context)
super.init()
self.addSubnode(self.dim)
self.container.updateModalProgress = { [weak self] progress, transition in
if let strongSelf = self, let layout = strongSelf.validLayout {
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
@@ -65,17 +105,65 @@ public class AttachmentController: ViewController {
}
}
self.container.interactivelyDismissed = { [weak self] in
if let strongSelf = self {
strongSelf.controller?.dismiss(animated: true)
}
}
self.panel.selectionChanged = { [weak self] type, ascending in
if let strongSelf = self {
strongSelf.switchToController(type, ascending)
}
}
self.container.interactivelyDismissed = { [weak self] in
self.panel.beganTextEditing = { [weak self] in
if let strongSelf = self {
strongSelf.controller?.dismiss(animated: true)
strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
}
}
self.panel.textUpdated = { [weak self] text in
if let strongSelf = self {
strongSelf.mediaPickerContext?.setCaption(text)
}
}
self.panel.sendMessagePressed = { [weak self] mode in
if let strongSelf = self {
switch mode {
case .generic:
strongSelf.mediaPickerContext?.send(silently: false)
case .silent:
strongSelf.mediaPickerContext?.send(silently: true)
case .schedule:
strongSelf.mediaPickerContext?.schedule()
}
}
}
self.panel.requestLayout = { [weak self] in
if let strongSelf = self, let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
self.panel.present = { [weak self] c in
if let strongSelf = self {
strongSelf.controller?.present(c, in: .window(.root))
}
}
self.panel.presentInGlobalOverlay = { [weak self] c in
if let strongSelf = self {
strongSelf.controller?.presentInGlobalOverlay(c, with: nil)
}
}
}
deinit {
self.captionDisposable.dispose()
self.mediaSelectionCountDisposable.dispose()
}
override func didLoad() {
@@ -86,6 +174,14 @@ public class AttachmentController: ViewController {
self.switchToController(.gallery, false)
}
private var selectionCount: Int = 0
private func updateSelectionCount(_ count: Int) {
self.selectionCount = count
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
}
}
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.controller?.dismiss(animated: true)
@@ -108,6 +204,8 @@ public class AttachmentController: ViewController {
})
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
} else {
self.controller?.dismiss(animated: false, completion: nil)
}
@@ -119,36 +217,64 @@ public class AttachmentController: ViewController {
}
let previousType = self.currentType
self.currentType = type
self.controller?.requestController(type, { [weak self] controller in
if let strongSelf = self, let controller = controller {
controller._presentedInModal = true
controller.navigation_setPresenting(strongSelf.controller)
let animateTransition = previousType != nil
strongSelf.currentController = controller
if animateTransition, let snapshotView = strongSelf.container.scrollNode.view.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = strongSelf.container.scrollNode.frame
strongSelf.container.view.insertSubview(snapshotView, belowSubview: strongSelf.panel.view)
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
}
if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in
if let strongSelf = self {
strongSelf.mediaPickerContext = mediaPickerContext
if let controller = controller {
controller._presentedInModal = true
controller.navigation_setPresenting(strongSelf.controller)
controller.requestAttachmentMenuExpansion = { [weak self] in
self?.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
}
let animateTransition = previousType != nil
strongSelf.currentController = controller
if animateTransition, let snapshotView = strongSelf.container.container.view.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = strongSelf.container.container.frame
strongSelf.container.clipNode.view.addSubview(snapshotView)
let _ = (controller.ready.get()
|> filter {
$0
}
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak snapshotView] _ in
guard let strongSelf = self else {
return
}
if ascending {
strongSelf.container.container.view.layer.animatePosition(from: CGPoint(x: 70.0, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
} else {
strongSelf.container.container.view.layer.animatePosition(from: CGPoint(x: -70.0, y: 0.0), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
})
}
if let layout = strongSelf.validLayout {
strongSelf.switchingController = true
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
strongSelf.switchingController = false
}
}
}
})
}
func animateIn(transition: ContainedViewLayoutTransition) {
transition.updateAlpha(node: self.dim, alpha: 1.0)
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
transition.animatePositionAdditive(node: self.container, offset: CGPoint(x: 0.0, y: self.bounds.height + self.container.bounds.height / 2.0 - (self.container.position.y - self.bounds.height)))
}
private var isCollapsed: Bool = false
private var isUpdatingContainer = false
private var switchingController = false
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.validLayout = layout
@@ -166,7 +292,7 @@ public class AttachmentController: ViewController {
let controllers = self.currentController.flatMap { [$0] } ?? []
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: layout.size))
self.container.update(layout: layout, controllers: controllers, coveredByModalTransition: 0.0, transition: .immediate)
self.container.update(layout: layout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition)
if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady {
self.addSubnode(self.container)
@@ -177,40 +303,36 @@ public class AttachmentController: ViewController {
self.isUpdatingContainer = false
}
let buttons: [AttachmentButtonType] = [.camera, .gallery, .file, .location, .contact, .poll, .app("App")]
let sideInset: CGFloat = 16.0
let bottomInset: CGFloat
if layout.intrinsicInsets.bottom > 0.0 {
bottomInset = layout.intrinsicInsets.bottom - 4.0
} else {
bottomInset = 4.0
}
if self.modalProgress < 0.75 {
if self.modalProgress < 0.5 {
self.isCollapsed = false
} else if self.modalProgress == 1.0 {
self.isCollapsed = true
}
let panelSize = CGSize(width: layout.size.width - sideInset * 2.0, height: panelButtonSize.height)
transition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - panelSize.height - bottomInset), size: panelSize))
self.panel.update(buttons: buttons, isCollapsed: self.isCollapsed, size: panelSize, transition: transition)
let isEffecitvelyCollapsedUpdated = (self.isCollapsed || self.selectionCount > 0) != (self.panel.isCollapsed || self.panel.isSelecting)
let panelHeight = self.panel.update(layout: layout, buttons: self.controller?.buttons ?? [], isCollapsed: self.isCollapsed, isSelecting: self.selectionCount > 0, transition: transition)
var panelTransition = transition
if isEffecitvelyCollapsedUpdated {
panelTransition = .animated(duration: 0.25, curve: .easeInOut)
}
panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)))
}
}
public var requestController: (AttachmentButtonType, @escaping (ViewController?) -> Void) -> Void = { _, completion in
completion(nil)
public var requestController: (AttachmentButtonType, @escaping (AttachmentContainable?, AttachmentMediaPickerContext?) -> Void) -> Void = { _, completion in
completion(nil, nil)
}
public init(context: AccountContext) {
public init(context: AccountContext, buttons: [AttachmentButtonType]) {
self.context = context
self.buttons = buttons
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
self.blocksBackgroundWhenInOverlay = true
self.acceptsFocusWhenInOverlay = true
}
public required init(coder aDecoder: NSCoder) {