mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Various improvements
This commit is contained in:
@@ -4,6 +4,7 @@ import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
@@ -16,6 +17,8 @@ import LegacyMessageInputPanelInputView
|
||||
import AttachmentTextInputPanelNode
|
||||
import ChatSendMessageActionUI
|
||||
import MinimizedContainer
|
||||
import ComponentFlow
|
||||
import GlassBackgroundComponent
|
||||
|
||||
public enum AttachmentButtonType: Equatable {
|
||||
case gallery
|
||||
@@ -308,42 +311,74 @@ public extension AttachmentMediaPickerContext {
|
||||
}
|
||||
}
|
||||
|
||||
private func generateShadowImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in
|
||||
private func generateShadowImage(glass: Bool) -> UIImage? {
|
||||
let shadowInset: CGFloat = 60.0
|
||||
let middle: CGFloat = 10.0
|
||||
let innerSide = glass ? 62.0 * 2.0 : 10.0 * 2.0
|
||||
let side = innerSide + middle + shadowInset * 2.0
|
||||
return generateImage(CGSize(width: side, height: side), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.saveGState()
|
||||
context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
|
||||
context.setShadow(offset: CGSize(), blur: glass ? 80.0 : 60.0, color: UIColor(white: 0.0, alpha: glass ? 0.3 : 0.4).cgColor)
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 10.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70)
|
||||
if glass {
|
||||
let firstPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide + middle), cornerRadius: 62.0).cgPath
|
||||
let secondPath = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide + middle, height: innerSide * 0.65), cornerRadius: 38.0).cgPath
|
||||
context.addPath(firstPath)
|
||||
context.addPath(secondPath)
|
||||
context.fillPath()
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.addPath(firstPath)
|
||||
context.addPath(secondPath)
|
||||
context.fillPath()
|
||||
} else {
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: shadowInset, y: shadowInset, width: innerSide, height: innerSide), cornerRadius: 10.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
context.restoreGState()
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
}
|
||||
})?.stretchableImage(withLeftCapWidth: Int(side / 2.0), topCapHeight: Int(side / 2.0))
|
||||
}
|
||||
|
||||
private func generateMaskImage() -> UIImage? {
|
||||
private func generateMaskImage(glass: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 390.0, height: 220.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
if glass {
|
||||
let firstPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 62.0).cgPath
|
||||
let secondPath = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 130.0), cornerRadius: 38.0).cgPath
|
||||
context.addPath(firstPath)
|
||||
context.addPath(secondPath)
|
||||
context.fillPath()
|
||||
} else {
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: 390.0, height: 209.0), cornerRadius: 10.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
}
|
||||
try? drawSvgPath(context, path: "M183.219,208.89 H206.781 C205.648,208.89 204.567,209.371 203.808,210.214 L197.23,217.523 C196.038,218.848 193.962,218.848 192.77,217.523 L186.192,210.214 C185.433,209.371 184.352,208.89 183.219,208.89 Z ")
|
||||
})?.stretchableImage(withLeftCapWidth: 195, topCapHeight: 110)
|
||||
}
|
||||
|
||||
public class AttachmentController: ViewController, MinimizableController {
|
||||
public enum Style {
|
||||
case glass
|
||||
case legacy
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
private let style: Style
|
||||
private let chatLocation: ChatLocation?
|
||||
private let isScheduledMessages: Bool
|
||||
private var buttons: [AttachmentButtonType]
|
||||
@@ -380,6 +415,8 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
public var isFullscreen: Bool {
|
||||
return self.mainController.isFullscreen
|
||||
}
|
||||
|
||||
public weak var attachmentButton: UIView?
|
||||
|
||||
private final class Node: ASDisplayNode {
|
||||
private weak var controller: AttachmentController?
|
||||
@@ -426,7 +463,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.panel.updateLoadingProgress(progress)
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
}))
|
||||
@@ -441,7 +478,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.panel.updateMainButtonState(mainButtonState)
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -458,7 +495,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.panel.updateSecondaryButtonState(mainButtonState)
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -491,12 +528,21 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
private let wrapperNode: ASDisplayNode
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private var isMinimizing = false
|
||||
|
||||
init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
|
||||
self.controller = controller
|
||||
self.makeEntityInputView = makeEntityInputView
|
||||
|
||||
if let updatedPresentationData = controller.updatedPresentationData {
|
||||
self.presentationData = updatedPresentationData.initial
|
||||
} else {
|
||||
self.presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
|
||||
}
|
||||
|
||||
self.dim = ASDisplayNode()
|
||||
self.dim.alpha = 0.0
|
||||
self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25)
|
||||
@@ -507,9 +553,18 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
self.wrapperNode = ASDisplayNode()
|
||||
self.wrapperNode.clipsToBounds = true
|
||||
|
||||
self.container = AttachmentContainer(isFullSize: controller.isFullSize)
|
||||
self.container = AttachmentContainer(presentationData: self.presentationData, isFullSize: controller.isFullSize, glass: controller.style == .glass)
|
||||
self.container.canHaveKeyboardFocus = true
|
||||
self.panel = AttachmentPanel(controller: controller, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView)
|
||||
|
||||
let panelStyle: AttachmentPanel.Style
|
||||
switch controller.style {
|
||||
case .glass:
|
||||
panelStyle = .glass
|
||||
case .legacy:
|
||||
panelStyle = .legacy
|
||||
}
|
||||
|
||||
self.panel = AttachmentPanel(controller: controller, style: panelStyle, context: controller.context, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, updatedPresentationData: controller.updatedPresentationData, makeEntityInputView: makeEntityInputView)
|
||||
self.panel.fromMenu = controller.fromMenu
|
||||
self.panel.isStandalone = controller.isStandalone
|
||||
|
||||
@@ -529,10 +584,10 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
}
|
||||
|
||||
self.container.updateModalProgress = { [weak self] progress, topInset, bounds, transition in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout, !strongSelf.isDismissing {
|
||||
if let strongSelf = self, let controller = strongSelf.controller, let layout = strongSelf.validLayout, !strongSelf.isDismissing {
|
||||
var transition = transition
|
||||
if strongSelf.container.supernode == nil {
|
||||
transition = .animated(duration: 0.4, curve: .spring)
|
||||
transition = .animated(duration: 0.5, curve: .spring)
|
||||
}
|
||||
|
||||
strongSelf.modalProgress = progress
|
||||
@@ -540,33 +595,37 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
strongSelf.controller?.minimizedBounds = bounds
|
||||
|
||||
if !strongSelf.isMinimizing {
|
||||
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
|
||||
if controller.style != .glass {
|
||||
strongSelf.controller?.updateModalStyleOverlayTransitionFactor(progress, transition: transition)
|
||||
}
|
||||
strongSelf.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.container.isReadyUpdated = { [weak self] in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
self.container.interactivelyDismissed = { [weak self] velocity in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout {
|
||||
if let controller = strongSelf.controller, controller.shouldMinimizeOnSwipe?(strongSelf.currentType) == true {
|
||||
var delta = layout.size.height
|
||||
if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset {
|
||||
delta -= minimizedTopEdgeOffset
|
||||
}
|
||||
let damping: CGFloat = 180.0
|
||||
let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0
|
||||
|
||||
strongSelf.minimize(damping: damping, initialVelocity: initialVelocity)
|
||||
|
||||
return false
|
||||
} else {
|
||||
strongSelf.controller?.dismiss(animated: true)
|
||||
}
|
||||
guard let self, let controller = self.controller, let layout = self.validLayout else {
|
||||
return true
|
||||
}
|
||||
let damping: CGFloat = 180.0
|
||||
var delta = layout.size.height
|
||||
if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset {
|
||||
delta -= minimizedTopEdgeOffset
|
||||
}
|
||||
let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0
|
||||
|
||||
if controller.shouldMinimizeOnSwipe?(self.currentType) == true {
|
||||
self.minimize(damping: damping, initialVelocity: initialVelocity)
|
||||
return false
|
||||
} else {
|
||||
self.animateOut(damping: damping, initialVelocity: initialVelocity, completion: {
|
||||
self.controller?.dismiss(animated: false)
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -637,7 +696,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
self.panel.beganTextEditing = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,6 +758,17 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
return currentController.getCurrentSendMessageContextMediaPreview?()
|
||||
}
|
||||
|
||||
if let updatedPresentationData = controller.updatedPresentationData {
|
||||
self.presentationDataDisposable = (updatedPresentationData.signal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.presentationData = presentationData
|
||||
self.container.presentationData = presentationData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@@ -708,6 +778,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
self.mainButtonStateDisposable.dispose()
|
||||
self.secondaryButtonStateDisposable.dispose()
|
||||
self.bottomPanelBackgroundColorDisposable.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
private var inputContainerHeight: CGFloat?
|
||||
@@ -783,7 +854,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) {
|
||||
self.selectionCount = count
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
|
||||
self.containerLayoutUpdated(layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -808,7 +879,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
func switchToController(_ type: AttachmentButtonType, animated: Bool = true) -> Bool {
|
||||
guard self.currentType != type else {
|
||||
if self.animating {
|
||||
if self.isAnimating {
|
||||
return false
|
||||
}
|
||||
if let controller = self.currentControllers.last {
|
||||
@@ -822,12 +893,15 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
self.controller?.requestController(type, { [weak self] controller, mediaPickerContext in
|
||||
if let strongSelf = self {
|
||||
if let controller = controller {
|
||||
if case .glass = strongSelf.controller?.style {
|
||||
controller._hasGlassStyle = true
|
||||
}
|
||||
strongSelf.controller?._ready.set(controller.ready.get())
|
||||
controller._presentedInModal = true
|
||||
controller.navigation_setPresenting(strongSelf.controller)
|
||||
controller.requestAttachmentMenuExpansion = { [weak self] in
|
||||
if let strongSelf = self, !strongSelf.container.isTracking {
|
||||
strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.container.update(isExpanded: true, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
controller.updateNavigationStack = { [weak self] f in
|
||||
@@ -836,7 +910,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
strongSelf.currentControllers = controllers
|
||||
strongSelf.mediaPickerContext = mediaPickerContext
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.5, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -902,9 +976,9 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
guard let snapshotView = self.container.container.view.snapshotView(afterScreenUpdates: false) else {
|
||||
return
|
||||
}
|
||||
|
||||
snapshotView.alpha = 0.0
|
||||
snapshotView.frame = self.container.container.frame
|
||||
self.container.clipNode.view.addSubview(snapshotView)
|
||||
self.container.bottomClipNode.view.insertSubview(snapshotView, at: self.container.bottomClipNode.view.subviews.count)
|
||||
|
||||
let _ = (controller.ready.get()
|
||||
|> filter {
|
||||
@@ -915,22 +989,22 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = layout
|
||||
if case .compact = layout.metrics.widthClass {
|
||||
let offset = 25.0
|
||||
let offset = 10.0
|
||||
|
||||
let initialPosition = strongSelf.container.clipNode.layer.position
|
||||
let initialPosition = strongSelf.container.wrappingNode.layer.position
|
||||
let targetPosition = initialPosition.offsetBy(dx: 0.0, dy: offset)
|
||||
var startPosition = initialPosition
|
||||
if let presentation = strongSelf.container.clipNode.layer.presentation() {
|
||||
if let presentation = strongSelf.container.wrappingNode.layer.presentation() {
|
||||
startPosition = presentation.position
|
||||
}
|
||||
|
||||
strongSelf.container.clipNode.layer.animatePosition(from: startPosition, to: targetPosition, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
strongSelf.container.wrappingNode.layer.animatePosition(from: startPosition, to: targetPosition, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
if let strongSelf = self, finished {
|
||||
strongSelf.container.clipNode.layer.animateSpring(from: NSValue(cgPoint: targetPosition), to: NSValue(cgPoint: initialPosition), keyPath: "position", duration: 0.4, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
strongSelf.container.wrappingNode.layer.animateSpring(from: NSValue(cgPoint: targetPosition), to: NSValue(cgPoint: initialPosition), keyPath: "position", duration: 0.3, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
if finished {
|
||||
self?.container.clipNode.layer.removeAllAnimations()
|
||||
self?.container.wrappingNode.layer.removeAllAnimations()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -944,13 +1018,13 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
})
|
||||
}
|
||||
|
||||
private var animating = false
|
||||
private var isAnimating = false
|
||||
func animateIn() {
|
||||
guard let layout = self.validLayout, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
self.animating = true
|
||||
self.isAnimating = true
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
if controller.animateAppearance {
|
||||
let targetPosition = self.position
|
||||
@@ -960,30 +1034,103 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
transition.animateView(allowUserInteraction: true, {
|
||||
self.position = targetPosition
|
||||
}, completion: { _ in
|
||||
self.animating = false
|
||||
}, completion: { _ in
|
||||
self.isAnimating = false
|
||||
})
|
||||
} else {
|
||||
self.animating = false
|
||||
self.isAnimating = false
|
||||
}
|
||||
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1)
|
||||
} else {
|
||||
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
|
||||
|
||||
let targetPosition = self.container.position
|
||||
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
|
||||
|
||||
self.container.position = startPosition
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
transition.animateView(allowUserInteraction: true, {
|
||||
self.container.position = targetPosition
|
||||
}, completion: { _ in
|
||||
self.animating = false
|
||||
})
|
||||
if case .glass = controller.style, let attachmentButton = controller.attachmentButton {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .customSpring(damping: 115.0, initialVelocity: 0.0))
|
||||
let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut)
|
||||
|
||||
let targetFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view)
|
||||
|
||||
let sourceButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view)
|
||||
let sourceButtonScale = sourceButtonFrame.width / targetFrame.width
|
||||
|
||||
if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params {
|
||||
let containerView = UIView()
|
||||
containerView.clipsToBounds = true
|
||||
containerView.frame = targetFrame
|
||||
if #available(iOS 26.0, *) {
|
||||
containerView.cornerConfiguration = .uniformCorners(radius: .fixed(containerView.bounds.width * 0.5))
|
||||
} else {
|
||||
containerView.layer.cornerRadius = containerView.bounds.width * 0.5
|
||||
}
|
||||
self.view.addSubview(containerView)
|
||||
|
||||
let localGlassView = GlassBackgroundView()
|
||||
localGlassView.update(
|
||||
size: targetFrame.size,
|
||||
cornerRadius: 0.0,
|
||||
isDark: glassParams.isDark,
|
||||
tintColor: glassParams.tintColor,
|
||||
transition: .immediate
|
||||
)
|
||||
localGlassView.frame = CGRect(origin: .zero, size: targetFrame.size)
|
||||
containerView.addSubview(localGlassView)
|
||||
|
||||
let initialContainerBounds = self.container.bounds
|
||||
let initialContainerFrame = self.container.frame
|
||||
|
||||
let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view)
|
||||
self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size)
|
||||
self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels ((targetFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size)
|
||||
self.container.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
containerView.addSubnode(self.container)
|
||||
|
||||
let buttonIcon = GlassBackgroundView.ContentImageView()
|
||||
let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
|
||||
buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme)
|
||||
buttonIcon.tintColor = presentationData.theme.chat.inputPanel.panelControlColor
|
||||
if let image = buttonIcon.image {
|
||||
buttonIcon.bounds = CGRect(origin: .zero, size: image.size)
|
||||
buttonIcon.center = CGPoint(x: targetFrame.width * 0.5, y: targetFrame.height * 0.5)
|
||||
buttonIcon.transform = CGAffineTransformMakeScale(1.0 / sourceButtonScale, 1.0 / sourceButtonScale)
|
||||
}
|
||||
localGlassView.contentView.addSubview(buttonIcon)
|
||||
ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 0.0, toRadius: 10.0)
|
||||
|
||||
transition.animateBounds(layer: containerView.layer, from: CGRect(origin: CGPoint(x: 0.0, y: (targetFrame.height - targetFrame.width) * 0.5), size: CGSize(width: targetFrame.width, height: targetFrame.width)))
|
||||
ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut).animateView {
|
||||
if #available(iOS 26.0, *) {
|
||||
containerView.cornerConfiguration = .corners(topLeftRadius: 38.0, topRightRadius: 38.0, bottomLeftRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0), bottomRightRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0))
|
||||
} else {
|
||||
containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0
|
||||
}
|
||||
}
|
||||
transition.animateTransformScale(view: containerView, from: sourceButtonScale)
|
||||
transition.animatePosition(layer: containerView.layer, from: sourceButtonFrame.center, to: containerView.center, completion: { _ in
|
||||
self.container.bounds = initialContainerBounds
|
||||
self.container.frame = initialContainerFrame
|
||||
self.wrapperNode.addSubnode(self.container)
|
||||
containerView.removeFromSuperview()
|
||||
sourceGlassView.isHidden = false
|
||||
self.isAnimating = false
|
||||
})
|
||||
sourceGlassView.isHidden = true
|
||||
}
|
||||
} else {
|
||||
let targetPosition = self.container.position
|
||||
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
|
||||
|
||||
self.container.position = startPosition
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
transition.animateView(allowUserInteraction: true, {
|
||||
self.container.position = targetPosition
|
||||
}, completion: { _ in
|
||||
self.isAnimating = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void = {}) {
|
||||
func animateOut(damping: CGFloat? = nil, initialVelocity: CGFloat? = nil, completion: @escaping () -> Void = {}) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
@@ -993,28 +1140,102 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
return
|
||||
}
|
||||
|
||||
self.animating = true
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
self.isAnimating = true
|
||||
|
||||
switch layout.metrics.widthClass {
|
||||
case .regular:
|
||||
self.layer.allowsGroupOpacity = true
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
let _ = self?.container.dismiss(transition: .immediate, completion: completion)
|
||||
self?.animating = false
|
||||
self?.isAnimating = false
|
||||
self?.layer.removeAllAnimations()
|
||||
})
|
||||
} else {
|
||||
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in
|
||||
let _ = self?.container.dismiss(transition: .immediate, completion: completion)
|
||||
self?.animating = false
|
||||
})
|
||||
case .compact:
|
||||
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
alphaTransition.updateAlpha(node: self.dim, alpha: 0.0)
|
||||
|
||||
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
|
||||
|
||||
if controller.fromMenu && self.hasButton, let (_, _, getTransition) = controller.getInputContainerNode(), let inputTransition = getTransition() {
|
||||
self.panel.animateTransitionOut(inputTransition: inputTransition, dismissed: true, transition: positionTransition)
|
||||
self.containerLayoutUpdated(layout, transition: positionTransition)
|
||||
if case .glass = controller.style, let attachmentButton = controller.attachmentButton {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .customSpring(damping: damping ?? 124.0, initialVelocity: initialVelocity ?? 0.0))
|
||||
let buttonTransition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
|
||||
|
||||
let initialFrame = self.container.clipNode.view.convert(self.container.clipNode.view.bounds, to: self.view)
|
||||
|
||||
let targetButtonFrame = attachmentButton.convert(attachmentButton.bounds, to: self.view)
|
||||
let targetButtonScale = targetButtonFrame.width / initialFrame.width
|
||||
|
||||
if let sourceGlassView = findParentGlassBackgroundView(attachmentButton), let glassParams = sourceGlassView.params {
|
||||
let containerView = UIView()
|
||||
containerView.clipsToBounds = true
|
||||
containerView.frame = initialFrame
|
||||
if #available(iOS 26.0, *) {
|
||||
containerView.cornerConfiguration = .corners(topLeftRadius: 38.0, topRightRadius: 38.0, bottomLeftRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0), bottomRightRadius: .fixed(layout.deviceMetrics.screenCornerRadius - 2.0))
|
||||
} else {
|
||||
containerView.layer.cornerRadius = layout.deviceMetrics.screenCornerRadius - 2.0
|
||||
}
|
||||
self.view.addSubview(containerView)
|
||||
|
||||
let localGlassView = GlassBackgroundView()
|
||||
localGlassView.update(
|
||||
size: initialFrame.size,
|
||||
cornerRadius: 0.0,
|
||||
isDark: glassParams.isDark,
|
||||
tintColor: glassParams.tintColor,
|
||||
transition: .immediate
|
||||
)
|
||||
localGlassView.frame = CGRect(origin: .zero, size: initialFrame.size)
|
||||
containerView.addSubview(localGlassView)
|
||||
|
||||
let clipInnerFrame = self.container.container.view.convert(self.container.container.view.bounds, to: self.container.view)
|
||||
self.container.bounds = CGRect(origin: .zero, size: self.container.bounds.size)
|
||||
self.container.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((initialFrame.width - self.container.frame.width) / 2.0), y: -clipInnerFrame.minY), size: self.container.frame.size)
|
||||
self.container.isUserInteractionEnabled = false
|
||||
self.container.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
containerView.addSubnode(self.container)
|
||||
|
||||
let buttonIcon = GlassBackgroundView.ContentImageView()
|
||||
let presentationData = controller.context.sharedContext.currentPresentationData.with { $0 }
|
||||
buttonIcon.image = PresentationResourcesChat.chatInputPanelAttachmentButtonImage(presentationData.theme)
|
||||
buttonIcon.tintColor = presentationData.theme.chat.inputPanel.panelControlColor
|
||||
if let image = buttonIcon.image {
|
||||
buttonIcon.bounds = CGRect(origin: .zero, size: image.size)
|
||||
buttonIcon.center = CGPoint(x: initialFrame.width * 0.5, y: initialFrame.height * 0.5)
|
||||
buttonIcon.transform = CGAffineTransformMakeScale(1.0 / targetButtonScale, 1.0 / targetButtonScale)
|
||||
}
|
||||
localGlassView.contentView.addSubview(buttonIcon)
|
||||
ComponentTransition(buttonTransition).animateBlur(layer: buttonIcon.layer, fromRadius: 10.0, toRadius: 0.0)
|
||||
|
||||
transition.updateBounds(layer: containerView.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: (initialFrame.height - initialFrame.width) * 0.5), size: CGSize(width: initialFrame.width, height: initialFrame.width)))
|
||||
transition.animateView {
|
||||
if #available(iOS 26.0, *) {
|
||||
containerView.cornerConfiguration = .uniformCorners(radius: .fixed(containerView.bounds.width * 0.5))
|
||||
} else {
|
||||
containerView.layer.cornerRadius = containerView.bounds.width * 0.5
|
||||
}
|
||||
}
|
||||
transition.updateTransformScale(layer: containerView.layer, scale: targetButtonScale)
|
||||
transition.updatePosition(layer: containerView.layer, position: targetButtonFrame.center, completion: { [weak self] _ in
|
||||
sourceGlassView.isHidden = false
|
||||
let _ = self?.container.dismiss(transition: .immediate, completion: completion)
|
||||
self?.isAnimating = false
|
||||
})
|
||||
|
||||
sourceGlassView.isHidden = true
|
||||
}
|
||||
} else {
|
||||
let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
positionTransition.updatePosition(node: self.container, position: CGPoint(x: self.container.position.x, y: self.bounds.height + self.container.bounds.height / 2.0), completion: { [weak self] _ in
|
||||
let _ = self?.container.dismiss(transition: .immediate, completion: completion)
|
||||
self?.isAnimating = false
|
||||
})
|
||||
|
||||
if controller.style != .glass {
|
||||
self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition)
|
||||
}
|
||||
|
||||
if controller.fromMenu && self.hasButton, let (_, _, getTransition) = controller.getInputContainerNode(), let inputTransition = getTransition() {
|
||||
self.panel.animateTransitionOut(inputTransition: inputTransition, dismissed: true, transition: positionTransition)
|
||||
self.containerLayoutUpdated(layout, transition: positionTransition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1080,9 +1301,9 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
|
||||
|
||||
let position: CGPoint
|
||||
let positionY = layout.size.height - size.height - insets.bottom - 40.0
|
||||
let positionY = layout.size.height - size.height - insets.bottom - 54.0
|
||||
if let sourceRect = controller.getSourceRect?() {
|
||||
position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0)), y: min(positionY, sourceRect.minY - size.height))
|
||||
position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0) - 2.0), y: min(positionY, sourceRect.minY - size.height))
|
||||
} else {
|
||||
position = CGPoint(x: masterWidth - 174.0, y: positionY)
|
||||
}
|
||||
@@ -1104,7 +1325,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
self.wrapperNode.cornerRadius = 10.0
|
||||
} else if self.wrapperNode.view.mask == nil {
|
||||
let maskView = UIImageView()
|
||||
maskView.image = generateMaskImage()
|
||||
maskView.image = generateMaskImage(glass: controller.style == .glass)
|
||||
maskView.contentMode = .scaleToFill
|
||||
self.wrapperNode.view.mask = maskView
|
||||
}
|
||||
@@ -1115,7 +1336,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
self.shadowNode.alpha = 1.0
|
||||
if self.shadowNode.image == nil {
|
||||
self.shadowNode.image = generateShadowImage()
|
||||
self.shadowNode.image = generateShadowImage(glass: controller.style == .glass)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1148,22 +1369,36 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
if !self.isPanelVisible {
|
||||
hasPanel = false
|
||||
}
|
||||
|
||||
|
||||
var panelOffset: CGFloat = 0.0
|
||||
if case .glass = controller.style {
|
||||
if layout.metrics.isTablet {
|
||||
panelOffset = 18.0
|
||||
} else {
|
||||
panelOffset = 8.0
|
||||
}
|
||||
}
|
||||
|
||||
let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting)
|
||||
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition)
|
||||
if hasPanel || hasButton {
|
||||
containerInsets.bottom = panelHeight
|
||||
containerInsets.bottom = panelHeight + panelOffset
|
||||
}
|
||||
|
||||
var panelTransition = transition
|
||||
if isEffecitvelyCollapsedUpdated {
|
||||
panelTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
if case .glass = controller.style {
|
||||
} else {
|
||||
panelTransition = .animated(duration: 0.25, curve: .easeInOut)
|
||||
}
|
||||
}
|
||||
var panelY = containerRect.height - panelHeight
|
||||
if case .glass = controller.style {
|
||||
panelY -= panelOffset
|
||||
}
|
||||
if !hasPanel && !hasButton {
|
||||
panelY = containerRect.height
|
||||
}
|
||||
|
||||
panelTransition.updateFrame(node: self.panel, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: containerRect.width, height: panelHeight)))
|
||||
|
||||
var shadowFrame = containerRect.insetBy(dx: -60.0, dy: -60.0)
|
||||
@@ -1182,7 +1417,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
}
|
||||
|
||||
let controllers = self.currentControllers
|
||||
if !self.animating {
|
||||
if !self.isAnimating {
|
||||
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size))
|
||||
}
|
||||
|
||||
@@ -1190,9 +1425,9 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
self.container.update(layout: containerLayout, controllers: controllers, coveredByModalTransition: 0.0, transition: self.switchingController ? .immediate : transition)
|
||||
|
||||
if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing {
|
||||
if self.container.supernode == nil, !controllers.isEmpty && self.container.isReady && !self.isDismissing && !self.isAnimating {
|
||||
self.wrapperNode.addSubnode(self.container)
|
||||
|
||||
|
||||
if fromMenu, let _ = controller.getInputContainerNode() {
|
||||
self.addSubnode(self.panel)
|
||||
} else {
|
||||
@@ -1220,6 +1455,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
public init(
|
||||
context: AccountContext,
|
||||
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
|
||||
style: Style = .legacy,
|
||||
chatLocation: ChatLocation?,
|
||||
isScheduledMessages: Bool = false,
|
||||
buttons: [AttachmentButtonType],
|
||||
@@ -1231,6 +1467,7 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
{
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.style = style
|
||||
self.chatLocation = chatLocation
|
||||
self.isScheduledMessages = isScheduledMessages
|
||||
self.buttons = buttons
|
||||
@@ -1242,6 +1479,8 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self._hasGlassStyle = style == .glass
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
self.acceptsFocusWhenInOverlay = true
|
||||
@@ -1446,3 +1685,12 @@ public class AttachmentController: ViewController, MinimizableController {
|
||||
return snapshotView
|
||||
}
|
||||
}
|
||||
|
||||
private func findParentGlassBackgroundView(_ view: UIView) -> GlassBackgroundView? {
|
||||
if let view = view as? GlassBackgroundView {
|
||||
return view
|
||||
} else if let superview = view.superview {
|
||||
return findParentGlassBackgroundView(superview)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user