diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 154406918a..eaa069688d 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -8,7 +8,7 @@ import DirectionalPanGesture import TelegramPresentationData import MapKit -private let overflowInset: CGFloat = 70.0 +private let overflowInset: CGFloat = 0.0 final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { let wrappingNode: ASDisplayNode @@ -406,7 +406,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { intrinsicInsets.left += overflowInset intrinsicInsets.right += overflowInset - containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width + overflowInset * 2.0, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: intrinsicInsets.left, bottom: layout.intrinsicInsets.bottom, right: intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: safeInsets.left, bottom: safeInsets.bottom, right: safeInsets.right), additionalInsets: layout.additionalInsets, statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) + containerLayout = ContainerViewLayout(size: CGSize(width: layout.size.width + overflowInset * 2.0, height: layout.size.height - containerTopInset), metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: intrinsicInsets.left, bottom: intrinsicInsets.bottom, right: intrinsicInsets.right), safeInsets: UIEdgeInsets(top: 0.0, left: safeInsets.left, bottom: safeInsets.bottom, right: safeInsets.right), additionalInsets: layout.additionalInsets, statusBarHeight: effectiveStatusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: containerLayout.size) let maxScale: CGFloat = (containerLayout.size.width - 16.0 * 2.0) / containerLayout.size.width containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition @@ -417,7 +417,7 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height) } } else { - containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver) + containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver) let unscaledFrame = CGRect(origin: CGPoint(), size: containerLayout.size) containerScale = 1.0 diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index c8e87a54c2..aa454ec925 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -157,6 +157,7 @@ public class AttachmentController: ViewController { self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) self.shadowNode = ASImageNode() + self.shadowNode.isUserInteractionEnabled = false self.wrapperNode = ASDisplayNode() self.wrapperNode.clipsToBounds = true @@ -200,7 +201,9 @@ public class AttachmentController: ViewController { self.panel.selectionChanged = { [weak self] type, ascending in if let strongSelf = self { - strongSelf.switchToController(type, ascending) + return strongSelf.switchToController(type, ascending) + } else { + return false } } @@ -258,7 +261,7 @@ public class AttachmentController: ViewController { self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - self.switchToController(.gallery, false) + let _ = self.switchToController(.gallery, false) } private func updateSelectionCount(_ count: Int) { @@ -274,16 +277,16 @@ public class AttachmentController: ViewController { } } - func switchToController(_ type: AttachmentButtonType, _ ascending: Bool) { + func switchToController(_ type: AttachmentButtonType, _ ascending: Bool) -> Bool { + guard !self.animating else { + return false + } guard self.currentType != type else { - if self.animating { - return - } if let controller = self.currentControllers.last { controller.scrollToTopWithTabBar?() controller.requestAttachmentMenuExpansion() } - return + return true } let previousType = self.currentType self.currentType = type @@ -317,28 +320,10 @@ public class AttachmentController: ViewController { } } let previousController = strongSelf.currentControllers.last - let animateTransition = previousType != nil strongSelf.currentControllers = [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 - } - strongSelf.container.container.view.layer.animatePosition(from: CGPoint(x: ascending ? 70.0 : -70.0, y: 0.0), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - previousController?.resetForReuse() - }) - }) + if previousType != nil { + strongSelf.animateSwitchTransition(controller, previousController: previousController) } if let layout = strongSelf.validLayout { @@ -350,6 +335,46 @@ public class AttachmentController: ViewController { strongSelf.mediaPickerContext = mediaPickerContext } }) + return true + } + + private func animateSwitchTransition(_ controller: AttachmentContainable, previousController: AttachmentContainable?) { + guard let snapshotView = self.container.container.view.snapshotView(afterScreenUpdates: false) else { + return + } + + snapshotView.frame = self.container.container.frame + self.container.clipNode.view.addSubview(snapshotView) + + self.animating = true + + let _ = (controller.ready.get() + |> filter { + $0 + } + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self, weak snapshotView] _ in + guard let strongSelf = self, let layout = strongSelf.validLayout else { + return + } + + if case .compact = layout.metrics.widthClass { + let offset = strongSelf.container.isExpanded ? 10.0 : 24.0 + strongSelf.container.clipNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -offset), duration: 0.18, removeOnCompletion: false, additive: true, completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.container.clipNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: 0.0, y: 0.0)), to: NSValue(cgPoint: CGPoint(x: 0.0, y: offset)), keyPath: "position", duration: 0.55, delay: 0.0, initialVelocity: 0.0, damping: 70.0, removeOnCompletion: false, additive: true, completion: { [weak self] _ in + self?.container.clipNode.layer.removeAllAnimations() + self?.animating = false + }) + } + }) + } + + snapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + previousController?.resetForReuse() + }) + }) } private var animating = false diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index aa587350eb..18bb0b80b5 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -174,7 +174,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { private var validLayout: ContainerViewLayout? private var scrollLayout: (width: CGFloat, contentSize: CGSize)? - var selectionChanged: (AttachmentButtonType, Bool) -> Void = { _, _ in } + var selectionChanged: (AttachmentButtonType, Bool) -> Bool = { _, _ in return false } var beganTextEditing: () -> Void = {} var textUpdated: (NSAttributedString) -> Void = { _ in } var sendMessagePressed: (AttachmentTextInputPanelSendMode) -> Void = { _ in } @@ -481,9 +481,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate { action: { [weak self] in if let strongSelf = self { let ascending = i > strongSelf.selectedIndex - strongSelf.selectedIndex = i - strongSelf.selectionChanged(type, ascending) - strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) + if strongSelf.selectionChanged(type, ascending) { + strongSelf.selectedIndex = i + strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring))) + } } }) ), diff --git a/submodules/Display/Source/GridNode.swift b/submodules/Display/Source/GridNode.swift index fb41e98a32..ac496381fa 100644 --- a/submodules/Display/Source/GridNode.swift +++ b/submodules/Display/Source/GridNode.swift @@ -208,7 +208,7 @@ private struct WrappedGridItemNode: Hashable { } open class GridNode: GridNodeScroller, UIScrollViewDelegate { - private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)) + public private(set) var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)) private var firstIndexInSectionOffset: Int = 0 public private(set) var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] @@ -244,7 +244,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.scrollView.indicatorStyle = self.indicatorStyle } } - + public private(set) var opaqueState: Any? public override init() { diff --git a/submodules/Display/Source/Navigation/NavigationModalFrame.swift b/submodules/Display/Source/Navigation/NavigationModalFrame.swift index 023d255262..a4e36ecda3 100644 --- a/submodules/Display/Source/Navigation/NavigationModalFrame.swift +++ b/submodules/Display/Source/Navigation/NavigationModalFrame.swift @@ -112,7 +112,7 @@ final class NavigationModalFrame: ASDisplayNode { let cornerRadius: CGFloat = 9.0 let initialCornerRadius: CGFloat if !layout.safeInsets.top.isZero { - initialCornerRadius = 40.0 + initialCornerRadius = layout.deviceMetrics.screenCornerRadius } else { initialCornerRadius = 0.0 } diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h index f7d9dd18a4..7b9c10199c 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h @@ -45,6 +45,12 @@ - (NSOrderedSet *)selectedItemsIdentifiers; - (NSArray *)selectedItems; +- (void)saveState; +- (void)restoreState; +- (void)clearSavedState; + +- (NSUInteger)savedStateDifference; + - (NSUInteger)count; + (SSignal *)combinedSelectionChangedSignalForContexts:(NSArray *)contexts; diff --git a/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m b/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m index 1c3eae6ed1..eb42d78b66 100644 --- a/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m @@ -9,6 +9,8 @@ @interface TGMediaSelectionContext () { + NSMutableArray *_savedSelectedIdentifiers; + NSMutableArray *_selectedIdentifiers; NSMutableDictionary *_selectionMap; @@ -74,9 +76,6 @@ NSString *identifier = item.uniqueIdentifier; if (selected) { - if (_selectionMap[identifier] != nil) - return false; - if (_selectedIdentifiers.count >= _selectionLimit) { if (_selectionLimitExceeded) { _selectionLimitExceeded(); @@ -92,7 +91,6 @@ if (_selectionMap[identifier] == nil) return false; - [_selectionMap removeObjectForKey:identifier]; [_selectedIdentifiers removeObject:identifier]; } @@ -186,9 +184,13 @@ if (enumerationBlock == nil) return; - NSArray *items = [_selectionMap allValues]; - for (id item in items) - enumerationBlock(item); + for (NSArray *identifier in _selectedIdentifiers) + { + NSObject *item = _selectionMap[identifier]; + if (item != nil) { + enumerationBlock(item); + } + } } - (NSOrderedSet *)selectedItemsIdentifiers @@ -213,6 +215,31 @@ return _selectedIdentifiers.count; } +- (void)saveState { + if (_savedSelectedIdentifiers == nil) { + _savedSelectedIdentifiers = [_selectedIdentifiers mutableCopy]; + } +} + +- (void)restoreState { + _selectedIdentifiers = _savedSelectedIdentifiers; + _savedSelectedIdentifiers = nil; + + _pipe.sink([TGMediaSelectionChange changeWithItem:nil selected:false animated:false sender:nil]); +} + +- (void)clearSavedState { + _savedSelectedIdentifiers = nil; +} + +- (NSUInteger)savedStateDifference { + if (_savedSelectedIdentifiers != nil) { + return _savedSelectedIdentifiers.count - _selectedIdentifiers.count; + } else { + return 0; + } +} + #pragma mark - - (void)setItemSourceUpdatedSignal:(SSignal *)signal diff --git a/submodules/MediaPickerUI/BUILD b/submodules/MediaPickerUI/BUILD index f2da9f36a8..958040d91d 100644 --- a/submodules/MediaPickerUI/BUILD +++ b/submodules/MediaPickerUI/BUILD @@ -38,6 +38,8 @@ swift_library( "//submodules/WallpaperBackgroundNode:WallpaperBackgroundNode", "//submodules/WebSearchUI:WebSearchUI", "//submodules/ChatMessageBackground:ChatMessageBackground", + "//submodules/SparseItemGrid:SparseItemGrid", + "//submodules/UndoUI:UndoUI", ], visibility = [ "//visibility:public", diff --git a/submodules/MediaPickerUI/Sources/MediaPickerMoreButton.swift b/submodules/MediaPickerUI/Sources/MediaPickerMoreButton.swift index 5064818bff..e5b71fcd2b 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerMoreButton.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerMoreButton.swift @@ -119,7 +119,7 @@ final class MediaPickerMoreButtonNode: ASDisplayNode { override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let animationSize = CGSize(width: 30.0, height: 30.0) let inset: CGFloat = 0.0 - self.iconNode.frame = CGRect(origin: CGPoint(x: inset + 4.0, y: floor((constrainedSize.height - animationSize.height) / 2.0)), size: animationSize) + self.iconNode.frame = CGRect(origin: CGPoint(x: inset + 6.0, y: floor((constrainedSize.height - animationSize.height) / 2.0) + 1.0), size: animationSize) let size = CGSize(width: animationSize.width + inset * 2.0, height: constrainedSize.height) let bounds = CGRect(origin: CGPoint(), size: size) diff --git a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift index 34d78b9b3d..f81328765c 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift @@ -753,6 +753,8 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { let textYear = Child(RollingText.self) return { context in + context.view.clipsToBounds = true + let date = context.component.date let components = date.0.components(separatedBy: " ") @@ -828,7 +830,7 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent { context.add(textYear .position(CGPoint(x: yearTextFrame.midX, y: yearTextFrame.midY)) ) - + return rect.size } } @@ -1099,7 +1101,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode { self.updateLineIndicator(transition: transition) } - func feedbackTap() { + public func feedbackTap() { self.hapticFeedback.tap() } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ab5b10fad2..f0f63ad185 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10353,7 +10353,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText - let currentMediaController = Atomic(value: nil) + let currentMediaController = Atomic(value: nil) let currentFilesController = Atomic(value: nil) let currentLocationController = Atomic(value: nil) @@ -10367,7 +10367,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.controllerNavigationDisposable.set(nil) let existingController = currentMediaController.with { $0 } if let controller = existingController { - completion(controller, nil) + completion(controller, controller.mediaPickerContext) controller.prepareForReuse() return } @@ -11023,7 +11023,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) { + private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?) -> Void) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 4ecfd52eec..94350e8f06 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -49,7 +49,11 @@ public enum UndoOverlayAction { public final class UndoOverlayController: ViewController { private let presentationData: PresentationData - public let content: UndoOverlayContent + public var content: UndoOverlayContent { + didSet { + (self.displayNode as! UndoOverlayControllerNode).updateContent(self.content) + } + } private let elevatedLayout: Bool private let animateInAsReplacement: Bool private var action: (UndoOverlayAction) -> Bool diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 696404e3fc..b6a61b2ee6 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -24,6 +24,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private let timerTextNode: ImmediateTextNode private let avatarNode: AvatarNode? private let iconNode: ASImageNode? + private var iconImageSize: CGSize? private let iconCheckNode: RadialStatusNode? private let animationNode: AnimationNode? private var animatedStickerNode: AnimatedStickerNode? @@ -41,7 +42,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { private let action: (UndoOverlayAction) -> Bool private let dismiss: () -> Void - private let content: UndoOverlayContent + private var content: UndoOverlayContent private let effectView: UIView @@ -752,7 +753,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { case let .image(image, text): self.avatarNode = nil self.iconNode = ASImageNode() + self.iconNode?.clipsToBounds = true + self.iconNode?.contentMode = .scaleAspectFill self.iconNode?.image = image + self.iconNode?.cornerRadius = 4.0 + self.iconImageSize = CGSize(width: 32.0, height: 32.0) self.iconCheckNode = nil self.animationNode = nil self.animatedStickerNode = nil @@ -892,6 +897,24 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.checkTimer() } + func updateContent(_ content: UndoOverlayContent) { + self.content = content + + switch content { + case let .image(image, text): + self.iconNode?.image = image + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) + default: + break + } + + self.renewWithCurrentContent() + + if let validLayout = self.validLayout { + self.containerLayoutUpdated(layout: validLayout, transition: .immediate) + } + } + func containerLayoutUpdated(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let firstLayout = self.validLayout == nil self.validLayout = layout @@ -979,7 +1002,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin), size: titleSize)) transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: leftInset, y: textContentOrigin + textOffset), size: textSize)) - if let iconNode = self.iconNode, let iconSize = iconNode.image?.size { + if let iconNode = self.iconNode { + let iconSize: CGSize + if let size = self.iconImageSize { + iconSize = size + } else if let size = iconNode.image?.size { + iconSize = size + } else { + iconSize = CGSize() + } + let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0) + verticalOffset), size: iconSize) transition.updateFrame(node: iconNode, frame: iconFrame)