mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Attachment menu improvements
This commit is contained in:
parent
8f560ba10b
commit
8f44a8fee6
@ -156,7 +156,10 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||
let point = recognizer.location(in: self.view)
|
||||
let currentHitView = self.hitTest(point, with: nil)
|
||||
|
||||
let scrollViewAndListNode = self.findScrollView(view: currentHitView)
|
||||
var scrollViewAndListNode = self.findScrollView(view: currentHitView)
|
||||
if scrollViewAndListNode?.0.frame.height == self.frame.width {
|
||||
scrollViewAndListNode = nil
|
||||
}
|
||||
let scrollView = scrollViewAndListNode?.0
|
||||
let listNode = scrollViewAndListNode?.1
|
||||
|
||||
|
@ -90,6 +90,7 @@ private func generateMaskImage() -> UIImage? {
|
||||
public class AttachmentController: ViewController {
|
||||
private let context: AccountContext
|
||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
private let chatLocation: ChatLocation
|
||||
private let buttons: [AttachmentButtonType]
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
@ -164,7 +165,7 @@ public class AttachmentController: ViewController {
|
||||
|
||||
self.container = AttachmentContainer()
|
||||
self.container.canHaveKeyboardFocus = true
|
||||
self.panel = AttachmentPanel(context: controller.context, updatedPresentationData: controller.updatedPresentationData)
|
||||
self.panel = AttachmentPanel(context: controller.context, chatLocation: controller.chatLocation, updatedPresentationData: controller.updatedPresentationData)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -360,9 +361,9 @@ public class AttachmentController: ViewController {
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
})
|
||||
@ -544,10 +545,11 @@ public class AttachmentController: ViewController {
|
||||
completion(nil, nil)
|
||||
}
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, buttons: [AttachmentButtonType]) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType]) {
|
||||
self.context = context
|
||||
self.buttons = buttons
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.chatLocation = chatLocation
|
||||
self.buttons = buttons
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
|
@ -182,11 +182,11 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
var present: (ViewController) -> Void = { _ in }
|
||||
var presentInGlobalOverlay: (ViewController) -> Void = { _ in }
|
||||
|
||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?) {
|
||||
init(context: AccountContext, chatLocation: ChatLocation, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?) {
|
||||
self.context = context
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(PeerId(0)), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil)
|
||||
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil)
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.clipsToBounds = true
|
||||
|
@ -40,6 +40,8 @@ typedef NS_ENUM(NSUInteger, TGModernGalleryScrollAnimationDirection) {
|
||||
- (TGModernGalleryItemView *)itemViewForItem:(id<TGModernGalleryItem>)item;
|
||||
- (id<TGModernGalleryItem>)currentItem;
|
||||
|
||||
- (UIView *)transitionView;
|
||||
|
||||
- (void)setCurrentItemIndex:(NSUInteger)index animated:(bool)animated;
|
||||
- (void)setCurrentItemIndex:(NSUInteger)index direction:(TGModernGalleryScrollAnimationDirection)direction animated:(bool)animated;
|
||||
|
||||
|
@ -54,6 +54,7 @@
|
||||
- (UIView *)footerView;
|
||||
|
||||
- (UIView *)transitionView;
|
||||
- (UIView *)transitionContentView;
|
||||
- (CGRect)transitionViewContentRect;
|
||||
|
||||
- (bool)dismissControllerNowOrSchedule;
|
||||
|
@ -9,7 +9,6 @@
|
||||
|
||||
- (CGSize)contentSize;
|
||||
- (UIView *)contentView;
|
||||
- (UIView *)transitionContentView;
|
||||
|
||||
- (void)reset;
|
||||
|
||||
|
@ -876,6 +876,14 @@
|
||||
return [_imageView convertRect:_imageView.bounds toView:[self transitionView]];
|
||||
}
|
||||
|
||||
- (UIView *)transitionContentView {
|
||||
if (_videoView != nil) {
|
||||
return _videoView;
|
||||
} else {
|
||||
return _imageView;
|
||||
}
|
||||
}
|
||||
|
||||
- (UIImage *)screenImage
|
||||
{
|
||||
if (_videoView != nil)
|
||||
|
@ -178,6 +178,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (UIView *)transitionView {
|
||||
id<TGModernGalleryItem> focusItem = nil;
|
||||
if ([self currentItemIndex] < self.model.items.count)
|
||||
focusItem = self.model.items[[self currentItemIndex]];
|
||||
|
||||
for (TGModernGalleryItemView *itemView in self->_visibleItemViews)
|
||||
{
|
||||
if ([itemView.item isEqual:focusItem])
|
||||
{
|
||||
itemView.alpha = 0.01;
|
||||
UIView *contentView = [itemView transitionContentView];
|
||||
UIView *snapshotView = [contentView snapshotViewAfterScreenUpdates:true];
|
||||
snapshotView.frame = [contentView convertRect:contentView.bounds toView:nil];
|
||||
// snapshotView.frame = CGRectOffset([contentView convertRect:contentView.bounds toView:nil], 0.0, -self.view.frame.size.height);
|
||||
return snapshotView;
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (bool)isFullyOpaque
|
||||
{
|
||||
CGFloat alpha = 0.0f;
|
||||
|
@ -224,7 +224,7 @@
|
||||
CGFloat panelHeight = [_inputPanel updateLayoutSize:frame.size sideInset:0.0];
|
||||
|
||||
CGFloat y = 0.0;
|
||||
if (frame.size.width > frame.size.height) {
|
||||
if (frame.size.width > frame.size.height && !TGIsPad()) {
|
||||
y = edgeInsets.top + frame.size.height;
|
||||
} else {
|
||||
y = edgeInsets.top + frame.size.height - panelHeight - MAX(edgeInsets.bottom, _keyboardHeight);
|
||||
|
@ -74,8 +74,9 @@ enum LegacyMediaPickerGallerySource {
|
||||
case selection(item: TGMediaSelectableItem)
|
||||
}
|
||||
|
||||
func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation?, presentationData: PresentationData, source: LegacyMediaPickerGallerySource, immediateThumbnail: UIImage?, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, hasSilentPosting: Bool, hasSchedule: Bool, hasTimer: Bool, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?) -> Void, presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)?, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void, finishedTransitionIn: @escaping () -> Void, willTransitionOut: @escaping () -> Void, dismissAll: @escaping () -> Void) {
|
||||
func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation?, presentationData: PresentationData, source: LegacyMediaPickerGallerySource, immediateThumbnail: UIImage?, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, hasSilentPosting: Bool, hasSchedule: Bool, hasTimer: Bool, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (String) -> UIView?, completed: @escaping (TGMediaSelectableItem & TGMediaEditableItem, Bool, Int32?, @escaping () -> Void) -> Void, presentStickers: ((@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?)?, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void, finishedTransitionIn: @escaping () -> Void, willTransitionOut: @escaping () -> Void, dismissAll: @escaping () -> Void) -> TGModernGalleryController {
|
||||
let reminder = peer?.id == context.account.peerId
|
||||
let hasSilentPosting = hasSilentPosting && peer?.id != context.account.peerId
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
|
||||
legacyController.statusBar.statusBarStyle = presentationData.theme.rootController.statusBarStyle.style
|
||||
@ -191,9 +192,10 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
|
||||
|
||||
model.interfaceView.donePressed = { [weak controller] item in
|
||||
if let item = item as? TGMediaPickerGalleryItem {
|
||||
controller?.dismissWhenReady(animated: true)
|
||||
completed(item.asset, false, nil)
|
||||
dismissAll()
|
||||
completed(item.asset, false, nil, {
|
||||
controller?.dismissWhenReady(animated: true)
|
||||
dismissAll()
|
||||
})
|
||||
}
|
||||
}
|
||||
model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model] item in
|
||||
@ -215,23 +217,24 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
|
||||
model?.dismiss(true, false)
|
||||
}
|
||||
controller.send = {
|
||||
dismissImpl()
|
||||
completed(item.asset, false, nil)
|
||||
completed(item.asset, false, nil, {
|
||||
dismissImpl()
|
||||
})
|
||||
}
|
||||
controller.sendSilently = {
|
||||
dismissImpl()
|
||||
completed(item.asset, true, nil)
|
||||
completed(item.asset, true, nil, {
|
||||
dismissImpl()
|
||||
})
|
||||
}
|
||||
controller.schedule = {
|
||||
presentSchedulePicker(true, { time in
|
||||
dismissImpl()
|
||||
completed(item.asset, false, time)
|
||||
completed(item.asset, false, time, {
|
||||
dismissImpl()
|
||||
})
|
||||
})
|
||||
}
|
||||
controller.sendWithTimer = {
|
||||
presentTimerPicker { time in
|
||||
dismissImpl()
|
||||
|
||||
var items = selectionContext.selectedItems() ?? []
|
||||
items.append(item.asset as Any)
|
||||
|
||||
@ -239,7 +242,9 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
|
||||
editingContext?.setTimer(time as NSNumber, for: item)
|
||||
}
|
||||
|
||||
completed(item.asset, false, nil)
|
||||
completed(item.asset, false, nil, {
|
||||
dismissImpl()
|
||||
})
|
||||
}
|
||||
}
|
||||
controller.customDismissBlock = { [weak legacySheetController] in
|
||||
@ -288,4 +293,6 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
|
||||
}
|
||||
}
|
||||
present(legacyController, nil)
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@ -159,13 +159,19 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
let wasHidden = self.isHidden
|
||||
self.isHidden = self.interaction?.hiddenMediaId == asset.localIdentifier
|
||||
if !self.isHidden && wasHidden {
|
||||
self.checkNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.typeIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.animateFadeIn(animateCheckNode: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateFadeIn(animateCheckNode: Bool) {
|
||||
if animateCheckNode {
|
||||
self.checkNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
self.gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.typeIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
@ -105,7 +105,9 @@ final class MediaPickerMoreButtonNode: ASDisplayNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.action?(strongSelf.contextSourceNode, gesture)
|
||||
if case .more = strongSelf.iconNode.iconState {
|
||||
strongSelf.action?(strongSelf.contextSourceNode, gesture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,14 +26,14 @@ final class MediaPickerInteraction {
|
||||
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
|
||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||
let toggleSelection: (TGMediaSelectableItem, Bool, Bool) -> Void
|
||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void
|
||||
let sendSelected: (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void
|
||||
let schedule: () -> Void
|
||||
let dismissInput: () -> Void
|
||||
let selectionState: TGMediaSelectionContext?
|
||||
let editingState: TGMediaEditingContext
|
||||
var hiddenMediaId: String?
|
||||
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Void, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Void, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
self.openMedia = openMedia
|
||||
self.openSelectedMedia = openSelectedMedia
|
||||
self.toggleSelection = toggleSelection
|
||||
@ -127,7 +127,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
public var presentWebSearch: (MediaGroupsScreen) -> Void = { _ in }
|
||||
public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil }
|
||||
|
||||
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?) -> Void = { _, _, _ in }
|
||||
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _ in }
|
||||
|
||||
public var requestAttachmentMenuExpansion: () -> Void = { }
|
||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||
@ -532,21 +532,29 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
let updated = self.currentDisplayMode != displayMode
|
||||
self.currentDisplayMode = displayMode
|
||||
|
||||
self.dismissInput()
|
||||
|
||||
if case .selected = displayMode, self.selectionNode == nil, let controller = self.controller {
|
||||
let selectionNode = MediaPickerSelectedListNode(context: controller.context)
|
||||
selectionNode.layer.allowsGroupOpacity = true
|
||||
selectionNode.alpha = 0.0
|
||||
selectionNode.layer.allowsGroupOpacity = true
|
||||
selectionNode.isUserInteractionEnabled = false
|
||||
selectionNode.interaction = self.controller?.interaction
|
||||
selectionNode.getTransitionView = { [weak self] identifier in
|
||||
if let strongSelf = self {
|
||||
var view: UIView?
|
||||
var node: MediaPickerGridItemNode?
|
||||
strongSelf.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? MediaPickerGridItemNode, itemNode.asset?.localIdentifier == identifier {
|
||||
view = itemNode.view
|
||||
node = itemNode
|
||||
}
|
||||
}
|
||||
return view
|
||||
if let node = node {
|
||||
return (node.view, { [weak node] animateCheckNode in
|
||||
node?.animateFadeIn(animateCheckNode: animateCheckNode)
|
||||
})
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -573,16 +581,18 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
if updated {
|
||||
switch displayMode {
|
||||
case .selected:
|
||||
self.selectionNode?.alpha = 1.0
|
||||
self.selectionNode?.animateIn(completion: completion)
|
||||
self.updateNavigation(transition: .immediate)
|
||||
self.selectionNode?.animateIn(initiated: { [weak self] in
|
||||
self?.updateNavigation(transition: .immediate)
|
||||
}, completion: completion)
|
||||
case .all:
|
||||
self.selectionNode?.animateOut(completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var openingMedia = false
|
||||
private weak var currentGalleryController: TGModernGalleryController?
|
||||
|
||||
private var openingMedia = false
|
||||
fileprivate func openMedia(fetchResult: PHFetchResult<PHAsset>, index: Int, immediateThumbnail: UIImage?) {
|
||||
guard let controller = self.controller, let interaction = controller.interaction, let (layout, _) = self.validLayout, !self.openingMedia else {
|
||||
return
|
||||
@ -600,15 +610,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
let reversed = controller.collection == nil
|
||||
let index = reversed ? fetchResult.count - index - 1 : index
|
||||
presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
|
||||
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
|
||||
self?.hiddenMediaId.set(.single(id))
|
||||
}, initialLayout: layout, transitionHostView: { [weak self] in
|
||||
return self?.gridNode.view
|
||||
}, transitionView: { [weak self] identifier in
|
||||
return self?.transitionView(for: identifier)
|
||||
}, completed: { [weak self] result, silently, scheduleTime in
|
||||
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
|
||||
}
|
||||
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
@ -636,15 +646,15 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
self.openingMedia = true
|
||||
presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
|
||||
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
|
||||
self?.hiddenMediaId.set(.single(id))
|
||||
}, initialLayout: layout, transitionHostView: { [weak self] in
|
||||
return self?.selectionNode?.view
|
||||
}, transitionView: { [weak self] identifier in
|
||||
return self?.transitionView(for: identifier)
|
||||
}, completed: { [weak self] result, silently, scheduleTime in
|
||||
}, completed: { [weak self] result, silently, scheduleTime, completion in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false)
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
|
||||
}
|
||||
}, presentStickers: controller.presentStickers, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||
self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true)
|
||||
@ -658,7 +668,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool) {
|
||||
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, completion: @escaping () -> Void) {
|
||||
var hasHeic = false
|
||||
let allItems = self.controller?.interaction?.selectionState?.selectedItems() ?? []
|
||||
for item in allItems {
|
||||
@ -673,8 +683,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
guard let signals = TGMediaAssetsController.resultSignals(for: self.controller?.interaction?.selectionState, editingContext: self.controller?.interaction?.editingState, intent: asFile ? TGMediaAssetsControllerSendFileIntent : TGMediaAssetsControllerSendMediaIntent, currentItem: nil, storeAssets: true, convertToJpeg: convertToJpeg, descriptionGenerator: legacyAssetPickerItemGenerator(), saveEditedPhotos: true) else {
|
||||
return
|
||||
}
|
||||
self.controller?.legacyCompletion(signals, silently, scheduleTime)
|
||||
self.controller?.dismiss(animated: animated)
|
||||
self.controller?.legacyCompletion(signals, silently, scheduleTime, { [weak self] identifier in
|
||||
return self?.getItemSnapshot(identifier)
|
||||
}, { [weak self] in
|
||||
completion()
|
||||
self?.controller?.dismiss(animated: animated)
|
||||
})
|
||||
}
|
||||
|
||||
if asFile && hasHeic {
|
||||
@ -687,7 +701,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
proceed(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func openLimitedMediaOptions() {
|
||||
let presentationData = self.presentationData
|
||||
let controller = ActionSheetController(presentationData: self.presentationData)
|
||||
@ -712,9 +726,33 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.controller?.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
private func transitionView(for identifier: String) -> UIView? {
|
||||
private func getItemSnapshot(_ identifier: String) -> UIView? {
|
||||
guard let selectionState = self.controller?.interaction?.selectionState else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let galleryController = self.currentGalleryController {
|
||||
if selectionState.count() > 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return galleryController.transitionView()
|
||||
}
|
||||
|
||||
if selectionState.grouping && selectionState.count() > 1 && (self.selectionNode?.alpha ?? 0.0).isZero {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let view = self.transitionView(for: identifier, hideSource: true) {
|
||||
return view
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func transitionView(for identifier: String, hideSource: Bool = false) -> UIView? {
|
||||
if let selectionNode = self.selectionNode, selectionNode.alpha > 0.0 {
|
||||
return selectionNode.transitionView(for: identifier)
|
||||
return selectionNode.transitionView(for: identifier, hideSource: hideSource)
|
||||
} else {
|
||||
var transitionNode: MediaPickerGridItemNode?
|
||||
self.gridNode.forEachItemNode { itemNode in
|
||||
@ -722,7 +760,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
transitionNode = itemNode
|
||||
}
|
||||
}
|
||||
return transitionNode?.transitionView()
|
||||
let transitionView = transitionNode?.transitionView()
|
||||
if hideSource {
|
||||
transitionNode?.isHidden = true
|
||||
}
|
||||
return transitionView
|
||||
}
|
||||
}
|
||||
|
||||
@ -1113,17 +1155,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
strongSelf.showSelectionUndo(item: item, count: Int32(selectionState.savedStateDifference()))
|
||||
}
|
||||
}
|
||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated in
|
||||
}, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in
|
||||
if let strongSelf = self, let selectionState = strongSelf.interaction?.selectionState {
|
||||
if let currentItem = currentItem {
|
||||
selectionState.setItem(currentItem, selected: true)
|
||||
}
|
||||
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated)
|
||||
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, completion: completion)
|
||||
}
|
||||
}, schedule: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentSchedulePicker(false, { [weak self] time in
|
||||
self?.interaction?.sendSelected(nil, false, time, true)
|
||||
self?.interaction?.sendSelected(nil, false, time, true, {})
|
||||
})
|
||||
}
|
||||
}, dismissInput: { [weak self] in
|
||||
@ -1292,7 +1334,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true)
|
||||
self?.controllerNode.send(asFile: true, silently: false, scheduleTime: nil, animated: true, completion: {})
|
||||
})))
|
||||
|
||||
if selectionCount > 1 {
|
||||
@ -1382,7 +1424,7 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
||||
}
|
||||
|
||||
func send(silently: Bool, mode: AttachmentMediaPickerSendMode) {
|
||||
self.interaction?.sendSelected(nil, silently, nil, true)
|
||||
self.interaction?.sendSelected(nil, silently, nil, true, {})
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
@ -1457,7 +1499,7 @@ private class MediaPickerGridSelectionGesture: UIPanGestureRecognizer {
|
||||
if !self.processing {
|
||||
if abs(translation.y) > 5.0 {
|
||||
self.state = .failed
|
||||
} else if abs(translation.x) > 4.0 {
|
||||
} else if abs(translation.x) > 8.0 {
|
||||
self.processing = true
|
||||
self.gridNode?.scrollView.isScrollEnabled = false
|
||||
self.began()
|
||||
|
@ -44,10 +44,16 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
private var readyPromise = Promise<Bool>()
|
||||
fileprivate var ready: Signal<Bool, NoError> {
|
||||
return self.readyPromise.get()
|
||||
}
|
||||
|
||||
init(asset: TGMediaAsset, interaction: MediaPickerInteraction?) {
|
||||
self.imageNode = ImageNode()
|
||||
self.imageNode.contentMode = .scaleAspectFill
|
||||
self.imageNode.clipsToBounds = true
|
||||
self.imageNode.animateFirstTransition = false
|
||||
|
||||
self.asset = asset
|
||||
self.interaction = interaction
|
||||
@ -111,6 +117,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
self.imageNode.setSignal(imageSignal)
|
||||
self.readyPromise.set(self.imageNode.contentReady)
|
||||
}
|
||||
|
||||
func updateSelectionState() {
|
||||
@ -203,7 +210,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
func animateTo(_ view: UIView) {
|
||||
func animateTo(_ view: UIView, completion: @escaping (Bool) -> Void) {
|
||||
view.alpha = 0.0
|
||||
|
||||
let frame = self.frame
|
||||
@ -216,6 +223,12 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
||||
self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in
|
||||
view?.alpha = 1.0
|
||||
|
||||
var animateCheckNode = false
|
||||
if let strongSelf = self, let checkNode = strongSelf.checkNode, checkNode.alpha.isZero {
|
||||
animateCheckNode = true
|
||||
}
|
||||
completion(animateCheckNode)
|
||||
|
||||
self?.corners = corners
|
||||
self?.updateLayout(size: frame.size, transition: .immediate)
|
||||
|
||||
@ -288,6 +301,9 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
|
||||
private var validLayout: (size: CGSize, insets: UIEdgeInsets, items: [TGMediaSelectableItem], grouped: Bool, theme: PresentationTheme, wallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners)?
|
||||
|
||||
private var didSetReady = false
|
||||
private var ready = Promise<Bool>()
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground)
|
||||
@ -344,35 +360,46 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
})
|
||||
}
|
||||
|
||||
var getTransitionView: (String) -> UIView? = { _ in return nil }
|
||||
var getTransitionView: (String ) -> (UIView, (Bool) -> Void)? = { _ in return nil }
|
||||
|
||||
func animateIn(completion: @escaping () -> Void = {}) {
|
||||
self.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
for (_, backgroundNode) in self.backgroundNodes {
|
||||
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
}
|
||||
|
||||
for (identifier, itemNode) in self.itemNodes {
|
||||
if let transitionView = self.getTransitionView(identifier) {
|
||||
itemNode.animateFrom(transitionView)
|
||||
} else {
|
||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
func animateIn(initiated: @escaping () -> Void, completion: @escaping () -> Void = {}) {
|
||||
let _ = (self.ready.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let topNode = self.messageNodes?.first, !topNode.alpha.isZero {
|
||||
topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
if let bottomNode = self.messageNodes?.last, !bottomNode.alpha.isZero {
|
||||
bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
strongSelf.alpha = 1.0
|
||||
initiated()
|
||||
|
||||
strongSelf.wallpaperBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
strongSelf.wallpaperBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
for (_, backgroundNode) in strongSelf.backgroundNodes {
|
||||
backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
}
|
||||
|
||||
for (identifier, itemNode) in strongSelf.itemNodes {
|
||||
if let (transitionView, _) = strongSelf.getTransitionView(identifier) {
|
||||
itemNode.animateFrom(transitionView)
|
||||
} else {
|
||||
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
if let topNode = strongSelf.messageNodes?.first, !topNode.alpha.isZero {
|
||||
topNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
topNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
if let bottomNode = strongSelf.messageNodes?.last, !bottomNode.alpha.isZero {
|
||||
bottomNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: 0.1)
|
||||
bottomNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 30.0), to: CGPoint(), duration: 0.4, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void = {}) {
|
||||
@ -400,8 +427,8 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
}
|
||||
|
||||
for (identifier, itemNode) in self.itemNodes {
|
||||
if let transitionView = self.getTransitionView(identifier) {
|
||||
itemNode.animateTo(transitionView)
|
||||
if let (transitionView, completion) = self.getTransitionView(identifier) {
|
||||
itemNode.animateTo(transitionView, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -593,6 +620,19 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
itemSizes.append(asset.dimensions)
|
||||
}
|
||||
}
|
||||
|
||||
if !self.didSetReady {
|
||||
self.didSetReady = true
|
||||
|
||||
var signals: [Signal<Bool, NoError>] = []
|
||||
for (_, itemNode) in self.itemNodes {
|
||||
signals.append(itemNode.ready)
|
||||
}
|
||||
self.ready.set(combineLatest(queue: Queue.mainQueue(), signals)
|
||||
|> map { _ in
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
let boundingSize = CGSize(width: boundingWidth, height: boundingWidth)
|
||||
var groupLayouts: [([(TGMediaSelectableItem, CGRect, MosaicItemPosition)], CGSize)] = []
|
||||
@ -812,9 +852,17 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
|
||||
transition.updateFrame(node: self.scrollNode, frame: bounds)
|
||||
}
|
||||
|
||||
func transitionView(for identifier: String) -> UIView? {
|
||||
func transitionView(for identifier: String, hideSource: Bool) -> UIView? {
|
||||
if hideSource {
|
||||
for (_, node) in self.backgroundNodes {
|
||||
node.alpha = 0.01
|
||||
}
|
||||
}
|
||||
for (_, itemNode) in self.itemNodes {
|
||||
if itemNode.asset.uniqueIdentifier == identifier {
|
||||
if hideSource {
|
||||
itemNode.alpha = 0.01
|
||||
}
|
||||
return itemNode.transitionView()
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +47,14 @@ public enum EnqueueMessage {
|
||||
return .forward(source: source, grouping: grouping, attributes: attributes, correlationId: value)
|
||||
}
|
||||
}
|
||||
|
||||
public var groupingKey: Int64? {
|
||||
if case let .message(_, _, _, _, localGroupingKey, _) = self {
|
||||
return localGroupingKey
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension EnqueueMessage {
|
||||
|
@ -10357,7 +10357,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let currentFilesController = Atomic<AttachmentContainable?>(value: nil)
|
||||
let currentLocationController = Atomic<AttachmentContainable?>(value: nil)
|
||||
|
||||
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, buttons: availableTabs)
|
||||
let attachmentController = AttachmentController(context: self.context, updatedPresentationData: self.updatedPresentationData, chatLocation: self.chatLocation, buttons: availableTabs)
|
||||
attachmentController.requestController = { [weak self, weak attachmentController] type, completion in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -10379,11 +10379,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
completion(controller, mediaPickerContext)
|
||||
}, updateMediaPickerContext: { [weak attachmentController] mediaPickerContext in
|
||||
attachmentController?.mediaPickerContext = mediaPickerContext
|
||||
}, completion: { [weak self] signals, silentPosting, scheduleTime in
|
||||
}, completion: { [weak self] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
|
||||
if !inputText.string.isEmpty {
|
||||
self?.clearInputText()
|
||||
}
|
||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime)
|
||||
self?.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
|
||||
})
|
||||
case .file:
|
||||
strongSelf.controllerNavigationDisposable.set(nil)
|
||||
@ -10619,8 +10619,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
}
|
||||
self.present(attachmentController, in: .window(.root))
|
||||
self.attachmentController = attachmentController
|
||||
Queue.mainQueue().justDispatch {
|
||||
self.present(attachmentController, in: .window(.root))
|
||||
self.attachmentController = attachmentController
|
||||
}
|
||||
}
|
||||
|
||||
private func oldPresentAttachmentMenu(editMediaOptions: MessageMediaEditingOptions?, editMediaReference: AnyMediaReference?) {
|
||||
@ -11025,7 +11027,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
|
||||
private func presentMediaPicker(bannedSendMedia: (Int32, Bool)?, present: @escaping (MediaPickerScreen, 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?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
@ -11088,8 +11090,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
controller.getCaptionPanelView = { [weak self] in
|
||||
return self?.getCaptionPanelView()
|
||||
}
|
||||
controller.legacyCompletion = { signals, silently, scheduleTime in
|
||||
completion(signals, silently, scheduleTime)
|
||||
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
|
||||
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
|
||||
}
|
||||
present(controller, mediaPickerContext)
|
||||
}
|
||||
@ -11927,25 +11929,80 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var usedCorrelationId: Int64?
|
||||
|
||||
var mappedMessages: [EnqueueMessage] = []
|
||||
var addedTransitions: [(Int64, [String], () -> Void)] = []
|
||||
|
||||
var groupedCorrelationIds: [Int64: Int64] = [:]
|
||||
|
||||
for item in items {
|
||||
var message = item.message
|
||||
if let uniqueId = item.uniqueId {
|
||||
let correlationId = Int64.random(in: 0 ..< Int64.max)
|
||||
let correlationId: Int64
|
||||
var addTransition = true
|
||||
if let groupingKey = message.groupingKey {
|
||||
if let existing = groupedCorrelationIds[groupingKey] {
|
||||
correlationId = existing
|
||||
addTransition = false
|
||||
} else {
|
||||
correlationId = Int64.random(in: 0 ..< Int64.max)
|
||||
groupedCorrelationIds[groupingKey] = correlationId
|
||||
}
|
||||
} else {
|
||||
correlationId = Int64.random(in: 0 ..< Int64.max)
|
||||
}
|
||||
message = message.withUpdatedCorrelationId(correlationId)
|
||||
|
||||
if items.count == 1, let getAnimatedTransitionSource = getAnimatedTransitionSource {
|
||||
usedCorrelationId = correlationId
|
||||
completionImpl = nil
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .mediaInput(ChatMessageTransitionNode.Source.MediaInput(extractSnapshot: {
|
||||
return getAnimatedTransitionSource(uniqueId)
|
||||
})), initiated: {
|
||||
completion()
|
||||
})
|
||||
if addTransition {
|
||||
addedTransitions.append((correlationId, [uniqueId], addedTransitions.isEmpty ? completion : {}))
|
||||
} else {
|
||||
if let index = addedTransitions.firstIndex(where: { $0.0 == correlationId }) {
|
||||
var (correlationId, uniqueIds, completion) = addedTransitions[index]
|
||||
uniqueIds.append(uniqueId)
|
||||
addedTransitions[index] = (correlationId, uniqueIds, completion)
|
||||
}
|
||||
}
|
||||
|
||||
usedCorrelationId = correlationId
|
||||
completionImpl = nil
|
||||
}
|
||||
mappedMessages.append(message)
|
||||
}
|
||||
|
||||
|
||||
if addedTransitions.count > 1 {
|
||||
var transitions: [(Int64, ChatMessageTransitionNode.Source, () -> Void)] = []
|
||||
for (correlationId, uniqueIds, initiated) in addedTransitions {
|
||||
var source: ChatMessageTransitionNode.Source?
|
||||
if uniqueIds.count > 1 {
|
||||
source = .groupedMediaInput(ChatMessageTransitionNode.Source.GroupedMediaInput(extractSnapshots: {
|
||||
return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) })
|
||||
}))
|
||||
} else if let uniqueId = uniqueIds.first {
|
||||
source = .mediaInput(ChatMessageTransitionNode.Source.MediaInput(extractSnapshot: {
|
||||
return getAnimatedTransitionSource?(uniqueId)
|
||||
}))
|
||||
}
|
||||
if let source = source {
|
||||
transitions.append((correlationId, source, initiated))
|
||||
}
|
||||
}
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.add(grouped: transitions)
|
||||
} else if let (correlationId, uniqueIds, initiated) = addedTransitions.first {
|
||||
var source: ChatMessageTransitionNode.Source?
|
||||
if uniqueIds.count > 1 {
|
||||
source = .groupedMediaInput(ChatMessageTransitionNode.Source.GroupedMediaInput(extractSnapshots: {
|
||||
return uniqueIds.compactMap({ getAnimatedTransitionSource?($0) })
|
||||
}))
|
||||
} else if let uniqueId = uniqueIds.first {
|
||||
source = .mediaInput(ChatMessageTransitionNode.Source.MediaInput(extractSnapshot: {
|
||||
return getAnimatedTransitionSource?(uniqueId)
|
||||
}))
|
||||
}
|
||||
if let source = source {
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: source, initiated: {
|
||||
initiated()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let messages = strongSelf.transformEnqueueMessages(mappedMessages, silentPosting: silentPosting, scheduleTime: scheduleTime)
|
||||
let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId
|
||||
strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
|
||||
|
@ -2595,15 +2595,15 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self?.scrolledToSomeIndex?()
|
||||
}
|
||||
|
||||
if let currentSendAnimationCorrelationId = strongSelf.currentSendAnimationCorrelationId {
|
||||
var foundItemNode: ChatMessageItemView?
|
||||
if let currentSendAnimationCorrelationIds = strongSelf.currentSendAnimationCorrelationIds {
|
||||
var foundItemNodes: [Int64: ChatMessageItemView] = [:]
|
||||
strongSelf.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
|
||||
for (message, _) in item.content {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? OutgoingMessageInfoAttribute {
|
||||
if attribute.correlationId == currentSendAnimationCorrelationId {
|
||||
foundItemNode = itemNode
|
||||
if let attribute = attribute as? OutgoingMessageInfoAttribute, let correlationId = attribute.correlationId {
|
||||
if currentSendAnimationCorrelationIds.contains(correlationId) {
|
||||
foundItemNodes[correlationId] = itemNode
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2611,9 +2611,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
}
|
||||
}
|
||||
|
||||
if let foundItemNode = foundItemNode {
|
||||
strongSelf.currentSendAnimationCorrelationId = nil
|
||||
strongSelf.animationCorrelationMessageFound?(foundItemNode, currentSendAnimationCorrelationId)
|
||||
if !foundItemNodes.isEmpty {
|
||||
strongSelf.currentSendAnimationCorrelationIds = nil
|
||||
strongSelf.animationCorrelationMessagesFound?(foundItemNodes)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3225,12 +3225,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
}
|
||||
}
|
||||
|
||||
private var currentSendAnimationCorrelationId: Int64?
|
||||
func setCurrentSendAnimationCorrelationId(_ value: Int64?) {
|
||||
self.currentSendAnimationCorrelationId = value
|
||||
private var currentSendAnimationCorrelationIds: Set<Int64>?
|
||||
func setCurrentSendAnimationCorrelationIds(_ value: Set<Int64>?) {
|
||||
self.currentSendAnimationCorrelationIds = value
|
||||
}
|
||||
|
||||
var animationCorrelationMessageFound: ((ChatMessageItemView, Int64?) -> Void)?
|
||||
var animationCorrelationMessagesFound: (([Int64: ChatMessageItemView]) -> Void)?
|
||||
|
||||
final class SnapshotState {
|
||||
fileprivate let snapshotTopInset: CGFloat
|
||||
|
@ -799,6 +799,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
self.mainContextSourceNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
}
|
||||
|
||||
func animateContentFromGroupedMediaInput(transition: CombinedTransition) -> [CGRect] {
|
||||
self.mainContextSourceNode.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1)
|
||||
|
||||
var rects: [CGRect] = []
|
||||
for contentNode in self.contentNodes {
|
||||
if let contentNode = contentNode as? ChatMessageMediaBubbleContentNode {
|
||||
rects.append(contentNode.frame.offsetBy(dx: -76.0, dy: 0.0))
|
||||
}
|
||||
}
|
||||
return rects
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
|
@ -169,12 +169,21 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
self.extractSnapshot = extractSnapshot
|
||||
}
|
||||
}
|
||||
|
||||
final class GroupedMediaInput {
|
||||
let extractSnapshots: () -> [UIView]
|
||||
|
||||
init(extractSnapshots: @escaping () -> [UIView]) {
|
||||
self.extractSnapshots = extractSnapshots
|
||||
}
|
||||
}
|
||||
|
||||
case textInput(textInput: TextInput, replyPanel: ReplyAccessoryPanelNode?)
|
||||
case stickerMediaInput(input: StickerInput, replyPanel: ReplyAccessoryPanelNode?)
|
||||
case audioMicInput(AudioMicInput)
|
||||
case videoMessage(VideoMessage)
|
||||
case mediaInput(MediaInput)
|
||||
case groupedMediaInput(GroupedMediaInput)
|
||||
}
|
||||
|
||||
final class DecorationItemNode: ASDisplayNode {
|
||||
@ -556,6 +565,92 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), ChatMessageTransitionNode.horizontalAnimationCurve, horizontalDuration)
|
||||
self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), ChatMessageTransitionNode.verticalAnimationCurve, verticalDuration)
|
||||
}
|
||||
} else {
|
||||
self.endAnimation()
|
||||
}
|
||||
case let .groupedMediaInput(groupedMediaInput):
|
||||
let snapshotViews = groupedMediaInput.extractSnapshots()
|
||||
if snapshotViews.isEmpty {
|
||||
self.endAnimation()
|
||||
return
|
||||
}
|
||||
if let itemNode = self.itemNode as? ChatMessageBubbleItemNode {
|
||||
itemNode.cancelInsertionAnimations()
|
||||
|
||||
self.contextSourceNode.isExtractedToContextPreview = true
|
||||
self.contextSourceNode.isExtractedToContextPreviewUpdated?(true)
|
||||
|
||||
self.containerNode.addSubnode(self.contextSourceNode.contentNode)
|
||||
|
||||
let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNode.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve))
|
||||
|
||||
var targetContentRects: [CGRect] = []
|
||||
if let itemNode = self.itemNode as? ChatMessageBubbleItemNode {
|
||||
targetContentRects = itemNode.animateContentFromGroupedMediaInput(transition: combinedTransition)
|
||||
}
|
||||
|
||||
let targetAbsoluteRect = self.contextSourceNode.view.convert(self.contextSourceNode.contentRect, to: self.view)
|
||||
|
||||
func boundingRect(for views: [UIView]) -> CGRect {
|
||||
var minX: CGFloat = .greatestFiniteMagnitude
|
||||
var minY: CGFloat = .greatestFiniteMagnitude
|
||||
var maxX: CGFloat = .leastNonzeroMagnitude
|
||||
var maxY: CGFloat = .leastNonzeroMagnitude
|
||||
|
||||
for view in views {
|
||||
let rect = view.frame
|
||||
if rect.minX < minX {
|
||||
minX = rect.minX
|
||||
}
|
||||
if rect.minY < minY {
|
||||
minY = rect.minY
|
||||
}
|
||||
if rect.maxX > maxX {
|
||||
maxX = rect.maxX
|
||||
}
|
||||
if rect.maxY > maxY {
|
||||
maxY = rect.maxY
|
||||
}
|
||||
}
|
||||
return CGRect(origin: CGPoint(x: minX, y: minY), size: CGSize(width: maxX - minX, height: maxY - minY))
|
||||
}
|
||||
|
||||
let sourceBackgroundAbsoluteRect = boundingRect(for: snapshotViews)
|
||||
let sourceAbsoluteRect = CGRect(origin: CGPoint(x: sourceBackgroundAbsoluteRect.midX - self.contextSourceNode.contentRect.size.width / 2.0, y: sourceBackgroundAbsoluteRect.midY - self.contextSourceNode.contentRect.size.height / 2.0), size: self.contextSourceNode.contentRect.size)
|
||||
|
||||
self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY)
|
||||
|
||||
self.contextSourceNode.updateAbsoluteRect?(self.containerNode.frame, UIScreen.main.bounds.size)
|
||||
|
||||
self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: sourceAbsoluteRect.midY - targetAbsoluteRect.midY), to: CGPoint(), duration: horizontalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.horizontalAnimationCurve.mediaTimingFunction, additive: true, force: true)
|
||||
self.containerNode.layer.animatePosition(from: CGPoint(x: sourceAbsoluteRect.midX - targetAbsoluteRect.midX, y: 0.0), to: CGPoint(), duration: verticalDuration, delay: delay, mediaTimingFunction: ChatMessageTransitionNode.verticalAnimationCurve.mediaTimingFunction, additive: true, force: true, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.endAnimation()
|
||||
})
|
||||
|
||||
combinedTransition.horizontal.animateTransformScale(node: self.contextSourceNode.contentNode, from: CGPoint(x: sourceBackgroundAbsoluteRect.width / targetAbsoluteRect.width, y: sourceBackgroundAbsoluteRect.height / targetAbsoluteRect.height))
|
||||
|
||||
var index = 0
|
||||
for snapshotView in snapshotViews {
|
||||
let targetContentRect = targetContentRects[index]
|
||||
let targetAbsoluteContentRect = targetContentRect.offsetBy(dx: targetAbsoluteRect.minX, dy: targetAbsoluteRect.minY)
|
||||
|
||||
snapshotView.center = targetAbsoluteContentRect.center.offsetBy(dx: -self.containerNode.frame.minX, dy: -self.containerNode.frame.minY)
|
||||
self.containerNode.view.addSubview(snapshotView)
|
||||
|
||||
combinedTransition.horizontal.updateTransformScale(layer: snapshotView.layer, scale: CGPoint(x: 1.0 / (snapshotView.frame.width / targetContentRect.width), y: 1.0 / (snapshotView.frame.height / targetContentRect.height)))
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
|
||||
index += 1
|
||||
}
|
||||
|
||||
self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: sourceAbsoluteRect.minX - targetAbsoluteRect.minX, y: 0.0), ChatMessageTransitionNode.horizontalAnimationCurve, horizontalDuration)
|
||||
self.contextSourceNode.applyAbsoluteOffset?(CGPoint(x: 0.0, y: sourceAbsoluteRect.maxY - targetAbsoluteRect.maxY), ChatMessageTransitionNode.verticalAnimationCurve, verticalDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -652,14 +747,14 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
private let getContentAreaInScreenSpace: () -> CGRect
|
||||
private let onTransitionEvent: (ContainedViewLayoutTransition) -> Void
|
||||
|
||||
private var currentPendingItem: (Int64, Source, () -> Void)?
|
||||
private var currentPendingItems: [Int64: (Source, () -> Void)] = [:]
|
||||
|
||||
private var animatingItemNodes: [AnimatingItemNode] = []
|
||||
private var decorationItemNodes: [DecorationItemNode] = []
|
||||
private var messageReactionContexts: [MessageReactionContext] = []
|
||||
|
||||
var hasScheduledTransitions: Bool {
|
||||
return self.currentPendingItem != nil
|
||||
return !self.currentPendingItems.isEmpty
|
||||
}
|
||||
|
||||
var hasOngoingTransitions: Bool {
|
||||
@ -673,21 +768,39 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.listNode.animationCorrelationMessageFound = { [weak self] itemNode, correlationId in
|
||||
guard let strongSelf = self, let (currentId, currentSource, initiated) = strongSelf.currentPendingItem else {
|
||||
self.listNode.animationCorrelationMessagesFound = { [weak self] itemNodeAndCorrelationIds in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if currentId == correlationId {
|
||||
strongSelf.currentPendingItem = nil
|
||||
strongSelf.beginAnimation(itemNode: itemNode, source: currentSource)
|
||||
initiated()
|
||||
|
||||
for (correlationId, itemNode) in itemNodeAndCorrelationIds {
|
||||
if let (currentSource, initiated) = strongSelf.currentPendingItems[correlationId] {
|
||||
strongSelf.beginAnimation(itemNode: itemNode, source: currentSource)
|
||||
initiated()
|
||||
}
|
||||
}
|
||||
|
||||
if itemNodeAndCorrelationIds.count == strongSelf.currentPendingItems.count {
|
||||
strongSelf.currentPendingItems = [:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func add(correlationId: Int64, source: Source, initiated: @escaping () -> Void) {
|
||||
self.currentPendingItem = (correlationId, source, initiated)
|
||||
self.listNode.setCurrentSendAnimationCorrelationId(correlationId)
|
||||
self.currentPendingItems = [correlationId: (source, initiated)]
|
||||
self.listNode.setCurrentSendAnimationCorrelationIds(Set([correlationId]))
|
||||
}
|
||||
|
||||
func add(grouped: [(correlationId: Int64, source: Source, initiated: () -> Void)]) {
|
||||
var currentPendingItems: [Int64: (Source, () -> Void)] = [:]
|
||||
var correlationIds = Set<Int64>()
|
||||
for (correlationId, source, initiated) in grouped {
|
||||
currentPendingItems[correlationId] = (source, initiated)
|
||||
correlationIds.insert(correlationId)
|
||||
}
|
||||
|
||||
self.currentPendingItems = currentPendingItems
|
||||
self.listNode.setCurrentSendAnimationCorrelationIds(correlationIds)
|
||||
}
|
||||
|
||||
func add(decorationView: UIView, itemNode: ChatMessageItemView) -> DecorationItemNode {
|
||||
@ -730,7 +843,7 @@ public final class ChatMessageTransitionNode: ASDisplayNode {
|
||||
|
||||
self.animatingItemNodes.append(animatingItemNode)
|
||||
switch source {
|
||||
case .audioMicInput, .videoMessage, .mediaInput:
|
||||
case .audioMicInput, .videoMessage, .mediaInput, .groupedMediaInput:
|
||||
let overlayController = OverlayTransitionContainerController()
|
||||
overlayController.displayNode.addSubnode(animatingItemNode)
|
||||
animatingItemNode.overlayController = overlayController
|
||||
|
Loading…
x
Reference in New Issue
Block a user