mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Further attach menu refinements
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user