diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8e129fba47..20679c4941 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7284,6 +7284,7 @@ Sorry for the inconvenience."; "Attachment.Ungrouped" = "Ungrouped"; "Attachment.MessagePreview" = "Message Preview"; +"Attachment.MessagesPreview" = "Messages Preview"; "Attachment.DragToReorder" = "Drag media to reorder"; "Attachment.SearchWeb" = "Search Web"; @@ -7352,6 +7353,10 @@ Sorry for the inconvenience."; "Attachment.LocationAccessTitle" = "Access Your Location"; "Attachment.LocationAccessText" = "Share places or your live location."; +"Attachment.CancelSelectionAlertText" = "Cancel selection?"; +"Attachment.CancelSelectionAlertYes" = "Yes"; +"Attachment.CancelSelectionAlertNo" = "No"; + "ChannelInfo.CreateExternalStream" = "Stream With..."; "CreateExternalStream.Title" = "Stream With..."; diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index e3baf38849..655396d0dd 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -207,18 +207,24 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { if currentOffset > 0.0, let scrollView = scrollView { scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) } - - var bounds = self.bounds - bounds.origin.y = -translation - bounds.origin.y = min(0.0, bounds.origin.y) - self.bounds = bounds } + var bounds = self.bounds + if self.isExpanded { + bounds.origin.y = -max(0.0, translation - edgeTopInset) + } else { + bounds.origin.y = -translation + } + bounds.origin.y = min(0.0, bounds.origin.y) + self.bounds = bounds + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .immediate) case .ended: guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else { return } + self.panGestureArguments = nil + let visibleContentOffset = listNode?.visibleContentOffset() let contentOffset = scrollView?.contentOffset.y ?? 0.0 @@ -236,15 +242,23 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { } var bounds = self.bounds - bounds.origin.y = -translation + if self.isExpanded { + bounds.origin.y = -max(0.0, translation - edgeTopInset) + } else { + bounds.origin.y = -translation + } bounds.origin.y = min(0.0, bounds.origin.y) scrollView?.bounces = true let offset = currentTopInset + panOffset let topInset: CGFloat = edgeTopInset - if self.isExpanded { - self.panGestureArguments = nil + + var dismissing = false + if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 600.0) { + self.interactivelyDismissed?() + dismissing = true + } else if self.isExpanded { if velocity.y > 300.0 || offset > topInset / 2.0 { self.isExpanded = false if let listNode = listNode { @@ -262,42 +276,34 @@ final class AttachmentContainer: ASDisplayNode, UIGestureRecognizerDelegate { self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .animated(duration: 0.3, curve: .easeInOut)) } + } else if (velocity.y < -300.0 || offset < topInset / 2.0) { + if velocity.y > -2200.0 && velocity.y < -300.0, let listNode = listNode { + DispatchQueue.main.async { + listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + } + + let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset) + let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) + self.isExpanded = true + + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: transition) } else { - self.panGestureArguments = nil - - var dismissing = false - if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) { - self.interactivelyDismissed?() - dismissing = true - } else if (velocity.y < -300.0 || offset < topInset / 2.0) { - if velocity.y > -2200.0 && velocity.y < -300.0, let listNode = listNode { - DispatchQueue.main.async { - listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - } - } - - let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset) - let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) - self.isExpanded = true - - self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: transition) - } else { - if let listNode = listNode { - listNode.scroller.setContentOffset(CGPoint(), animated: false) - } else if let scrollView = scrollView { - scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) - } - - self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .animated(duration: 0.3, curve: .easeInOut)) + if let listNode = listNode { + listNode.scroller.setContentOffset(CGPoint(), animated: false) + } else if let scrollView = scrollView { + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) } - if !dismissing { - var bounds = self.bounds - let previousBounds = bounds - bounds.origin.y = 0.0 - self.bounds = bounds - self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) - } + self.update(layout: layout, controllers: controllers, coveredByModalTransition: coveredByModalTransition, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + + if !dismissing { + var bounds = self.bounds + let previousBounds = bounds + bounds.origin.y = 0.0 + self.bounds = bounds + self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) } case .cancelled: self.panGestureArguments = nil diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index defd5cf08c..660b99f8bc 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -28,6 +28,8 @@ public protocol AttachmentContainable: ViewController { func resetForReuse() func prepareForReuse() + + func requestDismiss(completion: @escaping () -> Void) } public extension AttachmentContainable { @@ -38,6 +40,10 @@ public extension AttachmentContainable { func prepareForReuse() { } + + func requestDismiss(completion: @escaping () -> Void) { + completion() + } } public enum AttachmentMediaPickerSendMode { @@ -274,7 +280,13 @@ public class AttachmentController: ViewController { @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.controller?.dismiss(animated: true) + if let controller = self.currentControllers.last { + controller.requestDismiss(completion: { [weak self] in + self?.controller?.dismiss(animated: true) + }) + } else { + self.controller?.dismiss(animated: true) + } } } diff --git a/submodules/AvatarNode/Sources/AvatarNode.swift b/submodules/AvatarNode/Sources/AvatarNode.swift index 0719339198..1a0fa0c279 100644 --- a/submodules/AvatarNode/Sources/AvatarNode.swift +++ b/submodules/AvatarNode/Sources/AvatarNode.swift @@ -184,7 +184,7 @@ public final class AvatarNode: ASDisplayNode { public var font: UIFont { didSet { - if oldValue !== font { + if oldValue.pointSize != font.pointSize { if let parameters = self.parameters { self.parameters = AvatarNodeParameters(theme: parameters.theme, accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, letters: parameters.letters, font: self.font, icon: parameters.icon, explicitColorIndex: parameters.explicitColorIndex, hasImage: parameters.hasImage, clipStyle: parameters.clipStyle) } diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index a11dc5f509..43eec6e9fb 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -519,7 +519,7 @@ public final class ComposedPoll { private class CreatePollControllerImpl: ItemListController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } - public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } + public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } } diff --git a/submodules/ContactListUI/Sources/ContactListNode.swift b/submodules/ContactListUI/Sources/ContactListNode.swift index 9ed4b3282a..0778bf6088 100644 --- a/submodules/ContactListUI/Sources/ContactListNode.swift +++ b/submodules/ContactListUI/Sources/ContactListNode.swift @@ -1499,10 +1499,14 @@ public final class ContactListNode: ASDisplayNode { self.indexNode.update(size: indexNodeFrame.size, color: self.presentationData.theme.list.itemAccentColor, sections: indexSections, transition: transition) } - let permissionSize = CGSize(width: layout.size.width, height: layout.size.height - 160.0) - var permissionInsets = insets - permissionInsets.bottom += 100.0 - self.authorizationNode.updateLayout(size: permissionSize, insets: permissionInsets, transition: transition) + if self.multipleSelection { + let permissionSize = CGSize(width: layout.size.width, height: layout.size.height - 160.0) + var permissionInsets = insets + permissionInsets.bottom += 100.0 + self.authorizationNode.updateLayout(size: permissionSize, insets: permissionInsets, transition: transition) + } else { + self.authorizationNode.updateLayout(size: layout.size, insets: insets, transition: transition) + } transition.updateFrame(node: self.authorizationNode, frame: self.bounds) if !hadValidLayout { diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index d5b76e5ce9..ccb824c3d3 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -439,7 +439,6 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem { } } -private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0)) private let badgeFont = Font.regular(15.0) public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNode { @@ -503,7 +502,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo self.containerNode = ContextControllerSourceNode() - self.avatarNode = AvatarNode(font: avatarFont) + self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))) //self.avatarNode.isLayerBacked = !smartInvertColorsEnabled() self.titleNode = TextNode() @@ -763,6 +762,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo let verticalInset: CGFloat let verticalOffset: CGFloat let avatarSize: CGFloat + let avatarFontSize: CGFloat switch item.height { case .generic: if case .none = item.text { @@ -773,6 +773,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo verticalOffset = 0.0 avatarSize = 31.0 leftInset = 59.0 + params.leftInset + avatarFontSize = floor(31.0 * 16.0 / 37.0) case .peerList: if case .none = item.text { verticalInset = 14.0 @@ -782,6 +783,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo verticalOffset = 0.0 avatarSize = 40.0 leftInset = 65.0 + params.leftInset + avatarFontSize = floor(40.0 * 16.0 / 37.0) } var editableControlSizeAndApply: (CGFloat, (CGFloat) -> ItemListEditableControlNode)? @@ -887,6 +889,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize) strongSelf.containerNode.isGestureEnabled = item.contextAction != nil + strongSelf.avatarNode.font = avatarPlaceholderFont(size: avatarFontSize) + strongSelf.accessibilityLabel = titleAttributedString?.string var combinedValueString = "" if let statusString = statusAttributedString?.string, !statusString.isEmpty { diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h index 7b9c10199c..5770bf78b9 100644 --- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h +++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaSelectionContext.h @@ -41,6 +41,7 @@ - (SSignal *)selectionChangedSignal; - (void)enumerateSelectedItems:(void (^)(id))enumerationBlock; +- (void)enumerateDeselectedItems:(void (^)(id))enumerationBlock; - (NSOrderedSet *)selectedItemsIdentifiers; - (NSArray *)selectedItems; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 70f099b628..dbc2caa921 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -140,6 +140,10 @@ if (strongSelf == nil) return; + strongSelf->_ignoreSelectionUpdates = true; + [strongSelf->_selectionChangedDisposable dispose]; + [strongSelf->_itemSelectedDisposable dispose]; + [strongSelf.window endEditing:true]; strongSelf->_portraitToolbarView.doneButton.userInteractionEnabled = false; strongSelf->_landscapeToolbarView.doneButton.userInteractionEnabled = false; diff --git a/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m b/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m index e5ba124a3b..dcb967ce39 100644 --- a/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m +++ b/submodules/LegacyComponents/Sources/TGMediaSelectionContext.m @@ -188,7 +188,7 @@ if (enumerationBlock == nil) return; - for (NSArray *identifier in _selectedIdentifiers) + for (NSString *identifier in _selectedIdentifiers) { NSObject *item = _selectionMap[identifier]; if (item != nil) { @@ -197,6 +197,22 @@ } } +- (void)enumerateDeselectedItems:(void (^)(id))enumerationBlock +{ + if (enumerationBlock == nil || _savedSelectedIdentifiers == nil) + return; + + for (NSString *identifier in _savedSelectedIdentifiers) + { + if (![_selectedIdentifiers containsObject:identifier]) { + NSObject *item = _selectionMap[identifier]; + if (item != nil) { + enumerationBlock(item); + } + } + } +} + - (NSOrderedSet *)selectedItemsIdentifiers { return [[NSOrderedSet alloc] initWithArray:_selectedIdentifiers]; diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryController.m b/submodules/LegacyComponents/Sources/TGModernGalleryController.m index e594529dd5..ac29fefa76 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryController.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryController.m @@ -191,7 +191,6 @@ 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; } } diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryView.m b/submodules/LegacyComponents/Sources/TGModernGalleryView.m index 4c6d3e8dad..370258a176 100644 --- a/submodules/LegacyComponents/Sources/TGModernGalleryView.m +++ b/submodules/LegacyComponents/Sources/TGModernGalleryView.m @@ -379,12 +379,14 @@ static const CGFloat swipeDistanceThreshold = 128.0f; completion(); }]; - [UIView animateWithDuration:ABS(distance / velocity) animations:^ - { + [UIView animateWithDuration:0.15 animations:^{ _interfaceView.alpha = 0.0f; _overlayContainerView.alpha = 0.0f; + }]; + + [UIView animateWithDuration:0.35 animations:^{ self.backgroundColor = UIColorRGBA(0x000000, 0.0f); - } completion:nil]; + }]; } - (void)transitionInWithDuration:(NSTimeInterval)duration diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 5dace1b7f1..bb46c8f029 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -88,8 +88,7 @@ public final class LocationPickerController: ViewController, AttachmentContainab self.title = self.presentationData.strings.Map_ChooseLocationTitle self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) - self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.searchPressed)) - self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.Common_Search + self.updateBarButtons() self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) |> deliverOnMainQueue).start(next: { [weak self] presentationData in @@ -100,7 +99,8 @@ public final class LocationPickerController: ViewController, AttachmentContainab strongSelf.navigationBar?.updatePresentationData(NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: strongSelf.presentationData.theme).withUpdatedSeparatorColor(.clear), strings: NavigationBarStrings(presentationStrings: strongSelf.presentationData.strings))) strongSelf.searchNavigationContentNode?.updatePresentationData(strongSelf.presentationData) - strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(strongSelf.presentationData.theme), style: .plain, target: strongSelf, action: #selector(strongSelf.searchPressed)) + + strongSelf.updateBarButtons() if strongSelf.isNodeLoaded { strongSelf.controllerNode.updatePresentationData(presentationData) @@ -288,6 +288,16 @@ public final class LocationPickerController: ViewController, AttachmentContainab self.isSearchingDisposable.dispose() } + private var locationAccessDenied = false + private func updateBarButtons() { + if self.locationAccessDenied { + self.navigationItem.rightBarButtonItem = nil + } else { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: PresentationResourcesRootController.navigationCompactSearchIcon(self.presentationData.theme), style: .plain, target: self, action: #selector(self.searchPressed)) + self.navigationItem.rightBarButtonItem?.accessibilityLabel = self.presentationData.strings.Common_Search + } + } + override public func loadDisplayNode() { super.loadDisplayNode() guard let interaction = self.interaction else { @@ -299,6 +309,10 @@ public final class LocationPickerController: ViewController, AttachmentContainab self.controllerNode.beganInteractiveDragging = { [weak self] in self?.requestAttachmentMenuExpansion() } + self.controllerNode.locationAccessDeniedUpdated = { [weak self] denied in + self?.locationAccessDenied = denied + self?.updateBarButtons() + } self.permissionDisposable = (DeviceAccess.authorizationStatus(subject: .location(.send)) |> deliverOnMainQueue).start(next: { [weak self] next in diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index 65b39f4240..fbcb0c307f 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -313,6 +313,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM private var listOffset: CGFloat? var beganInteractiveDragging: () -> Void = {} + var locationAccessDeniedUpdated: (Bool) -> Void = { _ in } init(controller: LocationPickerController, context: AccountContext, presentationData: PresentationData, mode: LocationPickerMode, interaction: LocationPickerInteraction, locationManager: LocationManager) { self.controller = controller @@ -639,11 +640,13 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM if [.denied, .restricted].contains(access) { if !strongSelf.locationAccessDenied { strongSelf.locationAccessDenied = true + strongSelf.locationAccessDeniedUpdated(true) updateLayout = true } } else { if strongSelf.locationAccessDenied { strongSelf.locationAccessDenied = false + strongSelf.locationAccessDeniedUpdated(false) updateLayout = true } } @@ -998,6 +1001,8 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM self.placeholderBackgroundNode = nil placeholderBackgroundNode.removeFromSupernode() } + + self.controller?.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate) self.controller?.updateTabBarAlpha(1.0, .immediate) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 8ab13e91fa..381c666358 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -19,6 +19,7 @@ import ContextUI import WebSearchUI import SparseItemGrid import UndoUI +import PresentationDataUtils let overflowInset: CGFloat = 0.0 @@ -687,7 +688,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return } self.controller?.legacyCompletion(signals, silently, scheduleTime, { [weak self] identifier in - return self?.getItemSnapshot(identifier) + return !asFile ? self?.getItemSnapshot(identifier) : nil }, { [weak self] in completion() self?.controller?.dismiss(animated: animated) @@ -1155,7 +1156,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { selectionState.setItem(item, selected: value) if showUndo { - strongSelf.showSelectionUndo(item: item, count: Int32(selectionState.savedStateDifference())) + strongSelf.showSelectionUndo(item: item) } } }, sendSelected: { [weak self] currentItem, silently, scheduleTime, animated, completion in @@ -1198,7 +1199,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } private weak var undoOverlayController: UndoOverlayController? - private func showSelectionUndo(item: TGMediaSelectableItem, count: Int32) { + private func showSelectionUndo(item: TGMediaSelectableItem) { var asset: PHAsset? if let item = item as? TGMediaAsset { asset = item.backingAsset @@ -1218,12 +1219,36 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { return } + var photosCount = 0 + var videosCount = 0 + strongSelf.interaction?.selectionState?.enumerateDeselectedItems({ item in + if let item = item as? TGMediaAsset { + if item.isVideo { + videosCount += 1 + } else { + photosCount += 1 + } + } else if let _ = item as? TGCameraCapturedVideo { + videosCount += 1 + } + }) + let totalCount = Int32(photosCount + videosCount) + let presentationData = strongSelf.presentationData + let text: String + if photosCount > 0 && videosCount > 0 { + text = presentationData.strings.Attachment_DeselectedItems(totalCount) + } else if photosCount > 0 { + text = presentationData.strings.Attachment_DeselectedPhotos(totalCount) + } else if videosCount > 0 { + text = presentationData.strings.Attachment_DeselectedVideos(totalCount) + } else { + text = presentationData.strings.Attachment_DeselectedItems(totalCount) + } + if let undoOverlayController = strongSelf.undoOverlayController { - let text = presentationData.strings.Attachment_DeselectedItems(count) undoOverlayController.content = .image(image: image ?? UIImage(), text: text) } else { - let text = presentationData.strings.Attachment_DeselectedItems(count) let undoOverlayController = UndoOverlayController(presentationData: presentationData, content: .image(image: image ?? UIImage(), text: text), elevatedLayout: true, action: { [weak self] action in guard let strongSelf = self else { return true @@ -1282,8 +1307,23 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.undoOverlayController?.dismissWithCommitAction() } + public func requestDismiss(completion: @escaping () -> Void) { + if let selectionState = self.interaction?.selectionState, selectionState.count() > 0 { + let controller = textAlertController(context: self.context, title: nil, text: self.presentationData.strings.Attachment_CancelSelectionAlertText, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertNo, action: { + + }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertYes, action: { [weak self] in + self?.dismissAllTooltips() + completion() + })]) + self.present(controller, in: .window(.root)) + } else { + completion() + } + } + @objc private func cancelPressed() { self.dismissAllTooltips() + self.dismiss() } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index d623ff5881..8bcf19e5f0 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -6,6 +6,7 @@ import SwiftSignalKit import Postbox import TelegramCore import TelegramPresentationData +import TelegramStringFormatting import LegacyComponents import CheckNode import MosaicLayout @@ -13,7 +14,6 @@ import WallpaperBackgroundNode import AccountContext import ChatMessageBackground - private class MediaPickerSelectedItemNode: ASDisplayNode { let asset: TGMediaAsset private let interaction: MediaPickerInteraction? @@ -23,6 +23,8 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { private var durationBackgroundNode: ASDisplayNode? private var durationTextNode: ImmediateTextNode? + private var adjustmentsDisposable: Disposable? + private var theme: PresentationTheme? private var validLayout: CGSize? @@ -49,6 +51,8 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { return self.readyPromise.get() } + private var videoDuration: Double? + init(asset: TGMediaAsset, interaction: MediaPickerInteraction?) { self.imageNode = ImageNode() self.imageNode.contentMode = .scaleAspectFill @@ -63,6 +67,44 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { self.clipsToBounds = true self.addSubnode(self.imageNode) + + if asset.isVideo, let editingState = interaction?.editingState { + func adjustmentsChangedSignal(editingState: TGMediaEditingContext) -> Signal { + return Signal { subscriber in + let disposable = editingState.adjustmentsSignal(for: asset).start(next: { next in + if let next = next as? TGMediaEditAdjustments { + subscriber.putNext(next) + } else if next == nil { + subscriber.putNext(nil) + } + }, error: nil, completed: {}) + return ActionDisposable { + disposable?.dispose() + } + } + } + + self.adjustmentsDisposable = (adjustmentsChangedSignal(editingState: editingState) + |> deliverOnMainQueue).start(next: { [weak self] adjustments in + if let strongSelf = self { + let duration: Double + if let adjustments = adjustments as? TGVideoEditAdjustments, adjustments.trimApplied() { + duration = adjustments.trimEndValue - adjustments.trimStartValue + } else { + duration = asset.videoDuration + } + strongSelf.videoDuration = duration + + if let size = strongSelf.validLayout { + strongSelf.updateLayout(size: size, transition: .immediate) + } + } + }) + } + } + + deinit { + self.adjustmentsDisposable?.dispose() } override func didLoad() { @@ -158,6 +200,13 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { if let checkNode = self.checkNode, checkNode.alpha > 0.0 { checkNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } + + if let durationTextNode = self.durationTextNode, durationTextNode.alpha > 0.0 { + durationTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + if let durationBackgroundNode = self.durationBackgroundNode, durationBackgroundNode.alpha > 0.0 { + durationBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } } } @@ -182,6 +231,42 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { if let checkNode = self.checkNode { transition.updateFrame(node: checkNode, frame: CGRect(origin: CGPoint(x: size.width - checkSize.width - 3.0, y: 3.0), size: checkSize)) } + + if let duration = self.videoDuration { + let textNode: ImmediateTextNode + let backgroundNode: ASDisplayNode + if let currentTextNode = self.durationTextNode, let currentBackgroundNode = self.durationBackgroundNode { + textNode = currentTextNode + backgroundNode = currentBackgroundNode + } else { + backgroundNode = ASDisplayNode() + backgroundNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.5) + backgroundNode.cornerRadius = 9.0 + self.addSubnode(backgroundNode) + self.durationBackgroundNode = backgroundNode + + textNode = ImmediateTextNode() + textNode.displaysAsynchronously = false + self.addSubnode(textNode) + self.durationTextNode = textNode + } + + textNode.attributedText = NSAttributedString(string: stringForDuration(Int32(duration)), font: Font.with(size: 11.0, design: .regular, weight: .regular, traits: .monospacedNumbers), textColor: .white) + + let textSize = textNode.updateLayout(size) + let backgroundFrame = CGRect(x: 6.0, y: 6.0, width: ceil(textSize.width) + 14.0, height: 18.0) + backgroundNode.frame = backgroundFrame + textNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floorToScreenPixels((backgroundFrame.size.width - textSize.width) / 2.0), y: backgroundFrame.minY + floorToScreenPixels((backgroundFrame.size.height - textSize.height) / 2.0)), size: textSize) + } else { + if let durationTextNode = self.durationTextNode { + self.durationTextNode = nil + durationTextNode.removeFromSupernode() + } + if let durationBackgroundNode = self.durationBackgroundNode { + self.durationBackgroundNode = nil + durationBackgroundNode.removeFromSupernode() + } + } } func transitionView() -> UIView { @@ -203,6 +288,9 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { let frame = view.convert(view.bounds, to: self.supernode?.view) let targetFrame = self.frame + self.durationTextNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.durationBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.updateLayout(size: frame.size, transition: .immediate) self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring)) self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak view] _ in @@ -218,11 +306,17 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { let corners = self.corners + self.durationTextNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.durationBackgroundNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.corners = [] self.updateLayout(size: targetFrame.size, transition: .animated(duration: 0.25, curve: .spring)) self.layer.animateFrame(from: frame, to: targetFrame, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak view, weak self] _ in view?.alpha = 1.0 + self?.durationTextNode?.layer.removeAllAnimations() + self?.durationBackgroundNode?.layer.removeAllAnimations() + var animateCheckNode = false if let strongSelf = self, let checkNode = strongSelf.checkNode, checkNode.alpha.isZero { animateCheckNode = true @@ -541,53 +635,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) - var peers = SimpleDictionary() - peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) - - let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_MessagePreview, entities: []))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, isCentered: true) - - let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: []))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) - let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, isCentered: true) - - let headerItems: [ListViewItem] = [previewItem, dragItem] - - let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height) - if let messageNodes = self.messageNodes { - for i in 0 ..< headerItems.count { - let itemNode = messageNodes[i] - headerItems[i].updateNode(async: { $0() }, node: { - return itemNode - }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in - let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height)) - - itemNode.contentSize = layout.contentSize - itemNode.insets = layout.insets - itemNode.frame = nodeFrame - itemNode.isUserInteractionEnabled = false - - apply(ListViewItemApply(isOnScreen: true)) - }) - } - } else { - var messageNodes: [ListViewItemNode] = [] - for i in 0 ..< headerItems.count { - var itemNode: ListViewItemNode? - headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in - itemNode = node - apply().1(ListViewItemApply(isOnScreen: true)) - }) - itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) - itemNode!.isUserInteractionEnabled = false - messageNodes.append(itemNode!) - self.scrollNode.addSubnode(itemNode!) - } - self.messageNodes = messageNodes - } - var itemSizes: [CGSize] = [] - let sideInset: CGFloat = 34.0 let boundingWidth = min(320.0, size.width - insets.left - insets.right - sideInset * 2.0) @@ -672,6 +720,53 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI } } + let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)) + var peers = SimpleDictionary() + peers[peerId] = TelegramUser(id: peerId, accessHash: nil, firstName: "", lastName: "", username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: []) + + let previewText = groupLayouts.count > 1 ? presentationData.strings.Attachment_MessagesPreview : presentationData.strings.Attachment_MessagePreview + + let previewMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: previewText, entities: []))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let previewItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [previewMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, isCentered: true) + + let dragMessage = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: presentationData.strings.Attachment_DragToReorder, entities: []))], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) + let dragItem = self.context.sharedContext.makeChatMessagePreviewItem(context: context, messages: [dragMessage], theme: theme, strings: presentationData.strings, wallpaper: wallpaper, fontSize: presentationData.chatFontSize, chatBubbleCorners: bubbleCorners, dateTimeFormat: presentationData.dateTimeFormat, nameOrder: presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.wallpaperBackgroundNode, availableReactions: nil, isCentered: true) + + let headerItems: [ListViewItem] = [previewItem, dragItem] + + let params = ListViewItemLayoutParams(width: size.width, leftInset: insets.left, rightInset: insets.right, availableHeight: size.height) + if let messageNodes = self.messageNodes { + for i in 0 ..< headerItems.count { + let itemNode = messageNodes[i] + headerItems[i].updateNode(async: { $0() }, node: { + return itemNode + }, params: params, previousItem: nil, nextItem: nil, animation: .None, completion: { (layout, apply) in + let nodeFrame = CGRect(origin: itemNode.frame.origin, size: CGSize(width: size.width, height: layout.size.height)) + + itemNode.contentSize = layout.contentSize + itemNode.insets = layout.insets + itemNode.frame = nodeFrame + itemNode.isUserInteractionEnabled = false + + apply(ListViewItemApply(isOnScreen: true)) + }) + } + } else { + var messageNodes: [ListViewItemNode] = [] + for i in 0 ..< headerItems.count { + var itemNode: ListViewItemNode? + headerItems[i].nodeConfiguredForParams(async: { $0() }, params: params, synchronousLoads: false, previousItem: nil, nextItem: nil, completion: { node, apply in + itemNode = node + apply().1(ListViewItemApply(isOnScreen: true)) + }) + itemNode!.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) + itemNode!.isUserInteractionEnabled = false + messageNodes.append(itemNode!) + self.scrollNode.addSubnode(itemNode!) + } + self.messageNodes = messageNodes + } + let spacing: CGFloat = 8.0 var contentHeight: CGFloat = 60.0 diff --git a/submodules/RadialStatusNode/BUILD b/submodules/RadialStatusNode/BUILD index c759d1bc5a..264f6f4f6d 100644 --- a/submodules/RadialStatusNode/BUILD +++ b/submodules/RadialStatusNode/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Display:Display", "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/LegacyComponents:LegacyComponents", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", ], visibility = [ "//visibility:public", diff --git a/submodules/RadialStatusNode/Sources/RadialProgressContentNode.swift b/submodules/RadialStatusNode/Sources/RadialProgressContentNode.swift index cf57dddbe2..79cd25485a 100644 --- a/submodules/RadialStatusNode/Sources/RadialProgressContentNode.swift +++ b/submodules/RadialStatusNode/Sources/RadialProgressContentNode.swift @@ -3,6 +3,7 @@ import UIKit import Display import AsyncDisplayKit import LegacyComponents +import SwiftSignalKit private final class RadialProgressContentCancelNodeParameters: NSObject { let color: UIColor @@ -159,23 +160,28 @@ private final class RadialProgressContentSpinnerNode: ASDisplayNode { } } + private var hierarchyVersion: Int = 0 override func willEnterHierarchy() { super.willEnterHierarchy() if self.animateRotation { - let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") - basicAnimation.duration = 1.5 - var fromValue = Float.pi + 0.58 - if let presentation = self.layer.presentation(), let value = (presentation.value(forKeyPath: "transform.rotation.z") as? NSNumber)?.floatValue { - fromValue = value - } - basicAnimation.fromValue = NSNumber(value: fromValue) - basicAnimation.toValue = NSNumber(value: fromValue + Float.pi * 2.0) - basicAnimation.repeatCount = Float.infinity - basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - basicAnimation.beginTime = 0.0 + self.hierarchyVersion += 1 - self.layer.add(basicAnimation, forKey: "progressRotation") + if self.layer.animation(forKey: "progressRotation") == nil { + let basicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") + basicAnimation.duration = 1.5 + var fromValue = Float.pi + 0.58 + if let presentation = self.layer.presentation(), let value = (presentation.value(forKeyPath: "transform.rotation.z") as? NSNumber)?.floatValue { + fromValue = value + } + basicAnimation.fromValue = NSNumber(value: fromValue) + basicAnimation.toValue = NSNumber(value: fromValue + Float.pi * 2.0) + basicAnimation.repeatCount = Float.infinity + basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) + basicAnimation.beginTime = 0.0 + + self.layer.add(basicAnimation, forKey: "progressRotation") + } } } @@ -183,7 +189,12 @@ private final class RadialProgressContentSpinnerNode: ASDisplayNode { super.didExitHierarchy() if self.animateRotation { - self.layer.removeAnimation(forKey: "progressRotation") + let version = self.hierarchyVersion + Queue.mainQueue().after(0.1, { + if self.hierarchyVersion == version { + self.layer.removeAnimation(forKey: "progressRotation") + } + }) } } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift index 825a4e7b3d..8bce683aec 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ForwardPrivacyChatPreviewItem.swift @@ -181,7 +181,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode { var authorNameCenter: CGFloat? - let forwardedString = item.strings.Message_ForwardedMessage("").string + let forwardedString = item.strings.Message_ForwardedMessageShort("").string var fromString: String? if let newlineRange = forwardedString.range(of: "\n") { let from = forwardedString[newlineRange.upperBound...] diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Hidden.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Hidden.imageset/Contents.json new file mode 100644 index 0000000000..933a25f161 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Hidden.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "attach_hidden.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Hidden.imageset/attach_hidden.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Hidden.imageset/attach_hidden.pdf new file mode 100644 index 0000000000..950d6a0382 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Attach Menu/Hidden.imageset/attach_hidden.pdf @@ -0,0 +1,105 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 3.315430 8.007324 cm +0.000000 0.000000 0.000000 scn +23.041992 0.435059 m +23.364258 0.102051 23.879883 0.080566 24.212891 0.435059 c +24.567383 0.789551 24.535156 1.272949 24.212891 1.605957 c +6.251953 19.556152 l +5.929688 19.878418 5.403320 19.878418 5.070312 19.556152 c +4.758789 19.244629 4.758789 18.696777 5.070312 18.385254 c +23.041992 0.435059 l +h +14.684570 18.943848 m +12.815430 18.943848 11.118164 18.621582 9.539062 18.095215 c +10.957031 16.687988 l +12.138672 17.042480 13.352539 17.246582 14.684570 17.246582 c +21.752930 17.246582 27.478516 11.016113 27.478516 9.737793 c +27.478516 8.867676 25.598633 6.525879 22.623047 4.699707 c +23.944336 3.378418 l +27.338867 5.580566 29.358398 8.470215 29.358398 9.737793 c +29.358398 11.939941 23.364258 18.943848 14.684570 18.943848 c +h +14.684570 0.531738 m +16.682617 0.531738 18.508789 0.886230 20.173828 1.455566 c +18.766602 2.862793 l +17.488281 2.465332 16.134766 2.229004 14.684570 2.229004 c +7.605469 2.229004 1.879883 8.223145 1.879883 9.737793 c +1.879883 10.489746 3.867188 12.992676 7.036133 14.894043 c +5.704102 16.226074 l +2.126953 14.013184 0.000000 11.037598 0.000000 9.737793 c +0.000000 7.546387 6.112305 0.531738 14.684570 0.531738 c +h +20.195312 7.385254 m +20.528320 8.104980 20.710938 8.910645 20.710938 9.737793 c +20.710938 13.100098 18.025391 15.753418 14.684570 15.753418 c +13.835938 15.753418 13.041016 15.570801 12.321289 15.259277 c +20.195312 7.385254 l +h +14.684570 3.722168 m +15.629883 3.722168 16.521484 3.958496 17.316406 4.345215 c +9.259766 12.401855 l +8.862305 11.606934 8.636719 10.704590 8.636719 9.737793 c +8.647461 6.461426 11.322266 3.722168 14.684570 3.722168 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 1716 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 36.000000 36.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001806 00000 n +0000001829 00000 n +0000002002 00000 n +0000002076 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2135 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 35f9e7f951..796bffaece 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -10269,7 +10269,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let storeEditedPhotos = settings.storeEditedPhotos let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeEditedPhotos, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in if let strongSelf = self { strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) if !inputText.string.isEmpty { @@ -10755,7 +10755,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G photoOnly = true } - presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, photoOnly: photoOnly, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in if let strongSelf = self { if editMediaOptions != nil { strongSelf.editMessageMediaWithLegacySignals(signals!) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index b85904187c..c4cce70b20 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -191,14 +191,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var telegramDice: TelegramMediaDice? private let disposable = MetaDisposable() private let disposables = DisposableSet() - - private var forwardInfoNode: ChatMessageForwardInfoNode? - private var forwardBackgroundNode: NavigationBackgroundNode? - + private var viaBotNode: TextNode? private let dateAndStatusNode: ChatMessageDateAndStatusNode private var replyInfoNode: ChatMessageReplyInfoNode? private var replyBackgroundNode: NavigationBackgroundNode? + private var forwardInfoNode: ChatMessageForwardInfoNode? private var actionButtonsNode: ChatMessageActionButtonsNode? private var reactionButtonsNode: ChatMessageReactionButtonsNode? @@ -991,11 +989,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } } - - if replyInfoApply != nil || viaBotApply != nil { - needsReplyBackground = true - } - + var updatedShareButtonNode: ChatMessageShareButton? if needsShareButton { if let currentShareButtonNode = currentShareButtonNode { @@ -1013,7 +1007,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var forwardPsaType: String? var forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode)? - var needsForwardBackground = false if !ignoreForward, let forwardInfo = item.message.forwardInfo { forwardPsaType = forwardInfo.psaType @@ -1038,8 +1031,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } let availableWidth = max(60.0, availableContentWidth + 6.0) forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) - - needsForwardBackground = true + } + + if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { + needsReplyBackground = true } var maxContentWidth = imageSize.width @@ -1194,22 +1189,59 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { replyBackgroundNode.removeFromSupernode() } - if let (viaBotLayout, viaBotApply) = viaBotApply { + var messageInfoSize = CGSize() + if let (viaBotLayout, _) = viaBotApply, forwardInfoSizeApply == nil { + messageInfoSize = CGSize(width: viaBotLayout.size.width + 1.0, height: 0.0) + } + if let (forwardInfoSize, _) = forwardInfoSizeApply { + messageInfoSize = CGSize(width: max(messageInfoSize.width, forwardInfoSize.width + 2.0), height: 0.0) + } + if let (replyInfoSize, _) = replyInfoApply { + messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: 0.0) + } + + if let (viaBotLayout, viaBotApply) = viaBotApply, forwardInfoSizeApply == nil { let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } - let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 15.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 14.0)), y: 8.0), size: viaBotLayout.size) + let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size) viaBotNode.frame = viaBotFrame - if let replyBackgroundNode = strongSelf.replyBackgroundNode { - replyBackgroundNode.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 6.0, y: viaBotFrame.minY - 2.0 - UIScreenPixel), size: CGSize(width: viaBotFrame.size.width + 11.0, height: viaBotFrame.size.height + 5.0)) - replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: 8.0, transition: .immediate) - } + + messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) } else if let viaBotNode = strongSelf.viaBotNode { viaBotNode.removeFromSupernode() strongSelf.viaBotNode = nil } + + if let (forwardInfoSize, forwardInfoApply) = forwardInfoSizeApply { + let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) + if strongSelf.forwardInfoNode == nil { + strongSelf.forwardInfoNode = forwardInfoNode + strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) + + if animation.isAnimated { + forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize) + forwardInfoNode.frame = forwardInfoFrame + + messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) + } else if let forwardInfoNode = strongSelf.forwardInfoNode { + if animation.isAnimated { + if let forwardInfoNode = strongSelf.forwardInfoNode { + strongSelf.forwardInfoNode = nil + forwardInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardInfoNode] _ in + forwardInfoNode?.removeFromSupernode() + }) + } + } else { + forwardInfoNode.removeFromSupernode() + strongSelf.forwardInfoNode = nil + } + } if let (replyInfoSize, replyInfoApply) = replyInfoApply { let replyInfoNode = replyInfoApply() @@ -1217,31 +1249,29 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.replyInfoNode = replyInfoNode strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) } - var viaBotSize = CGSize() - if let viaBotNode = strongSelf.viaBotNode { - viaBotSize = viaBotNode.frame.size - } - let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize) - if let viaBotNode = strongSelf.viaBotNode { - if replyInfoFrame.minX < viaBotNode.frame.minX { - viaBotNode.frame = viaBotNode.frame.offsetBy(dx: replyInfoFrame.minX - viaBotNode.frame.minX, dy: 0.0) - } - } + let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize) replyInfoNode.frame = replyInfoFrame - if let replyBackgroundNode = strongSelf.replyBackgroundNode { - replyBackgroundNode.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) - replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: 8.0, transition: .immediate) - } - if let _ = item.controllerInteraction.selectionState, isEmoji { - replyInfoNode.alpha = 0.0 - strongSelf.replyBackgroundNode?.alpha = 0.0 - } + messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height) } else if let replyInfoNode = strongSelf.replyInfoNode { replyInfoNode.removeFromSupernode() strongSelf.replyInfoNode = nil } + + if let replyBackgroundNode = strongSelf.replyBackgroundNode { + replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0)) + + let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0 + replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) + } + + let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 + strongSelf.replyInfoNode?.alpha = panelsAlpha + strongSelf.viaBotNode?.alpha = panelsAlpha + strongSelf.forwardInfoNode?.alpha = panelsAlpha + strongSelf.replyBackgroundNode?.alpha = panelsAlpha + if isFailed { let deliveryFailedNode: ChatMessageDeliveryFailedNode var isAppearing = false @@ -1272,61 +1302,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { deliveryFailedNode?.removeFromSupernode() }) } - - if needsForwardBackground { - if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - forwardBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) - } else { - let forwardBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) - strongSelf.forwardBackgroundNode = forwardBackgroundNode - strongSelf.contextSourceNode.contentNode.addSubnode(forwardBackgroundNode) - - if animation.isAnimated { - forwardBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - } else if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - if animation.isAnimated { - strongSelf.forwardBackgroundNode = nil - forwardBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardBackgroundNode] _ in - forwardBackgroundNode?.removeFromSupernode() - }) - } else { - forwardBackgroundNode.removeFromSupernode() - strongSelf.forwardBackgroundNode = nil - } - } - - if let (forwardInfoSize, forwardInfoApply) = forwardInfoSizeApply { - let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) - if strongSelf.forwardInfoNode == nil { - strongSelf.forwardInfoNode = forwardInfoNode - strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) - - if animation.isAnimated { - forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize) - forwardInfoNode.frame = forwardInfoFrame - if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - forwardBackgroundNode.frame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0)) - forwardBackgroundNode.update(size: forwardBackgroundNode.bounds.size, cornerRadius: 8.0, transition: .immediate) - } - } else if let forwardInfoNode = strongSelf.forwardInfoNode { - if animation.isAnimated { - if let forwardInfoNode = strongSelf.forwardInfoNode { - strongSelf.forwardInfoNode = nil - forwardInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardInfoNode] _ in - forwardInfoNode?.removeFromSupernode() - }) - } - } else { - forwardInfoNode.removeFromSupernode() - strongSelf.forwardInfoNode = nil - } - } - + if let actionButtonsSizeAndApply = actionButtonsSizeAndApply { let actionButtonsNode = actionButtonsSizeAndApply.1(animation) let previousFrame = actionButtonsNode.frame @@ -2057,12 +2033,18 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate - let replyAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 + let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 if let replyInfoNode = self.replyInfoNode { - transition.updateAlpha(node: replyInfoNode, alpha: replyAlpha) + transition.updateAlpha(node: replyInfoNode, alpha: panelsAlpha) + } + if let viaBotNode = self.viaBotNode { + transition.updateAlpha(node: viaBotNode, alpha: panelsAlpha) + } + if let forwardInfoNode = self.forwardInfoNode { + transition.updateAlpha(node: forwardInfoNode, alpha: panelsAlpha) } if let replyBackgroundNode = self.replyBackgroundNode { - transition.updateAlpha(node: replyBackgroundNode, alpha: replyAlpha) + transition.updateAlpha(node: replyBackgroundNode, alpha: panelsAlpha) } if let selectionState = item.controllerInteraction.selectionState { diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index efa60a6a20..f2c4d0b9ca 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -805,7 +805,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode 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)) + rects.append(contentNode.frame.offsetBy(dx: -self.clippingNode.frame.minX, dy: 0.0)) } } return rects diff --git a/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift index fc6bc9c4fb..880b7a3d38 100644 --- a/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageForwardInfoNode.swift @@ -148,7 +148,7 @@ class ChatMessageForwardInfoNode: ASDisplayNode { } } else { titleColor = incoming ? presentationData.theme.theme.chat.message.incoming.accentTextColor : presentationData.theme.theme.chat.message.outgoing.accentTextColor - completeSourceString = strings.Message_ForwardedMessage(peerString) + completeSourceString = strings.Message_ForwardedMessageShort(peerString) } case .standalone: let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 73e28679f5..c4a7aac36d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -42,12 +42,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD return self.apparentHeightTransition != nil } - private var forwardInfoNode: ChatMessageForwardInfoNode? - private var forwardBackgroundNode: NavigationBackgroundNode? - private var viaBotNode: TextNode? private var replyInfoNode: ChatMessageReplyInfoNode? private var replyBackgroundNode: NavigationBackgroundNode? + private var forwardInfoNode: ChatMessageForwardInfoNode? private var actionButtonsNode: ChatMessageActionButtonsNode? private var reactionButtonsNode: ChatMessageReactionButtonsNode? @@ -259,7 +257,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD let currentShareButtonNode = self.shareButtonNode let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode) - let currentForwardBackgroundNode = self.forwardBackgroundNode let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode) let reactionButtonsLayout = ChatMessageReactionButtonsNode.asyncLayout(self.reactionButtonsNode) @@ -472,17 +469,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD replyMarkup = attribute } } - - if replyInfoApply != nil || viaBotApply != nil { - if let currentReplyBackgroundNode = currentReplyBackgroundNode { - updatedReplyBackgroundNode = currentReplyBackgroundNode - } else { - updatedReplyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) - } - - updatedReplyBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) - } - + var updatedShareButtonNode: ChatMessageShareButton? if needsShareButton { if currentShareButtonNode != nil { @@ -499,7 +486,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD var forwardAuthorSignature: String? var forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode)? - var updatedForwardBackgroundNode: NavigationBackgroundNode? if !ignoreForward, let forwardInfo = item.message.forwardInfo { let forwardPsaType = forwardInfo.psaType @@ -524,14 +510,16 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } let availableWidth = max(60.0, availableContentWidth - normalDisplaySize.width + 6.0) forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude)) - - if let currentForwardBackgroundNode = currentForwardBackgroundNode { - updatedForwardBackgroundNode = currentForwardBackgroundNode + } + + if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { + if let currentReplyBackgroundNode = currentReplyBackgroundNode { + updatedReplyBackgroundNode = currentReplyBackgroundNode } else { - updatedForwardBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) + updatedReplyBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) } - updatedForwardBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) + updatedReplyBackgroundNode?.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) } var maxContentWidth = normalDisplaySize.width @@ -657,21 +645,59 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD strongSelf.replyBackgroundNode = nil } - if let (viaBotLayout, viaBotApply) = viaBotApply { + var messageInfoSize = CGSize() + if let (viaBotLayout, _) = viaBotApply, forwardInfoSizeApply == nil { + messageInfoSize = CGSize(width: viaBotLayout.size.width + 1.0, height: 0.0) + } + if let (forwardInfoSize, _) = forwardInfoSizeApply { + messageInfoSize = CGSize(width: max(messageInfoSize.width, forwardInfoSize.width + 2.0), height: 0.0) + } + if let (replyInfoSize, _) = replyInfoApply { + messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: 0.0) + } + + if let (viaBotLayout, viaBotApply) = viaBotApply, forwardInfoSizeApply == nil { let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } - let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0), size: viaBotLayout.size) + let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size) viaBotNode.frame = viaBotFrame - let replyBackgroundFrame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 4.0, y: viaBotFrame.minY - 2.0), size: CGSize(width: viaBotFrame.size.width + 8.0, height: viaBotFrame.size.height + 5.0)) - strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame - strongSelf.replyBackgroundNode?.update(size: replyBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate) + + messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) } else if let viaBotNode = strongSelf.viaBotNode { viaBotNode.removeFromSupernode() strongSelf.viaBotNode = nil } + + if let (forwardInfoSize, forwardInfoApply) = forwardInfoSizeApply { + let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) + if strongSelf.forwardInfoNode == nil { + strongSelf.forwardInfoNode = forwardInfoNode + strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) + + if animation.isAnimated { + forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize) + forwardInfoNode.frame = forwardInfoFrame + + messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) + } else if let forwardInfoNode = strongSelf.forwardInfoNode { + if animation.isAnimated { + if let forwardInfoNode = strongSelf.forwardInfoNode { + strongSelf.forwardInfoNode = nil + forwardInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardInfoNode] _ in + forwardInfoNode?.removeFromSupernode() + }) + } + } else { + forwardInfoNode.removeFromSupernode() + strongSelf.forwardInfoNode = nil + } + } if let (replyInfoSize, replyInfoApply) = replyInfoApply { let replyInfoNode = replyInfoApply() @@ -679,25 +705,23 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD strongSelf.replyInfoNode = replyInfoNode strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) } - var viaBotSize = CGSize() - if let viaBotNode = strongSelf.viaBotNode { - viaBotSize = viaBotNode.frame.size - } - let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize) - if let viaBotNode = strongSelf.viaBotNode { - if replyInfoFrame.minX < viaBotNode.frame.minX { - viaBotNode.frame = viaBotNode.frame.offsetBy(dx: replyInfoFrame.minX - viaBotNode.frame.minX, dy: 0.0) - } - } + let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize) replyInfoNode.frame = replyInfoFrame - let replyBackgroundFrame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) - strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame - strongSelf.replyBackgroundNode?.update(size: replyBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate) + + messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height) } else if let replyInfoNode = strongSelf.replyInfoNode { replyInfoNode.removeFromSupernode() strongSelf.replyInfoNode = nil } + + if let replyBackgroundNode = strongSelf.replyBackgroundNode { + replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0)) + + let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0 + replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) + } + if isFailed { let deliveryFailedNode: ChatMessageDeliveryFailedNode var isAppearing = false @@ -728,63 +752,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD deliveryFailedNode?.removeFromSupernode() }) } - - if let updatedForwardBackgroundNode = updatedForwardBackgroundNode { - if strongSelf.forwardBackgroundNode == nil { - strongSelf.forwardBackgroundNode = updatedForwardBackgroundNode - strongSelf.contextSourceNode.contentNode.addSubnode(updatedForwardBackgroundNode) - - if animation.isAnimated { - updatedForwardBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - } else if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - if animation.isAnimated { - strongSelf.forwardBackgroundNode = nil - forwardBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardBackgroundNode] _ in - forwardBackgroundNode?.removeFromSupernode() - }) - } else { - forwardBackgroundNode.removeFromSupernode() - strongSelf.forwardBackgroundNode = nil - } - } - - if let (forwardInfoSize, forwardInfoApply) = forwardInfoSizeApply { - let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) - if strongSelf.forwardInfoNode == nil { - strongSelf.forwardInfoNode = forwardInfoNode - strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) - forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in - guard let strongSelf = strongSelf, let item = strongSelf.item else { - return - } - item.controllerInteraction.displayPsa(type, sourceNode) - } - - if animation.isAnimated { - forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize) - forwardInfoNode.frame = forwardInfoFrame - let forwardBackgroundFrame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0)) - strongSelf.forwardBackgroundNode?.frame = forwardBackgroundFrame - strongSelf.forwardBackgroundNode?.update(size: forwardBackgroundFrame.size, cornerRadius: 8.0, transition: .immediate) - } else if let forwardInfoNode = strongSelf.forwardInfoNode { - if animation.isAnimated { - if let forwardInfoNode = strongSelf.forwardInfoNode { - strongSelf.forwardInfoNode = nil - forwardInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardInfoNode] _ in - forwardInfoNode?.removeFromSupernode() - }) - } - } else { - forwardInfoNode.removeFromSupernode() - strongSelf.forwardInfoNode = nil - } - } - + if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply { let reactionButtonsNode = reactionButtonsSizeAndApply.1(animation) var reactionButtonsFrame = CGRect(origin: CGPoint(x: videoFrame.minX, y: videoFrame.maxY + 6.0), size: reactionButtonsSizeAndApply.0) @@ -869,9 +837,6 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD } let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) - if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - transition.updateAlpha(node: forwardBackgroundNode, alpha: isPlaying ? 0.0 : 1.0) - } if let replyBackgroundNode = strongSelf.replyBackgroundNode { transition.updateAlpha(node: replyBackgroundNode, alpha: isPlaying ? 0.0 : 1.0) } @@ -1312,28 +1277,28 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD shareButtonNode.frame = CGRect(origin: CGPoint(x: min(params.width - buttonSize.width - 8.0, videoFrame.maxX - 7.0), y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize) } - if let viaBotNode = self.viaBotNode { - let viaBotLayout = viaBotNode.frame - let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0), size: viaBotLayout.size) - viaBotNode.frame = viaBotFrame - self.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 4.0, y: viaBotFrame.minY - 2.0), size: CGSize(width: viaBotFrame.size.width + 8.0, height: viaBotFrame.size.height + 5.0)) - } - - if let replyInfoNode = self.replyInfoNode { - var viaBotSize = CGSize() - if let viaBotNode = self.viaBotNode { - viaBotSize = viaBotNode.frame.size - } - let replyInfoSize = replyInfoNode.frame.size - let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize) - if let viaBotNode = self.viaBotNode { - if replyInfoFrame.minX < viaBotNode.frame.minX { - viaBotNode.frame = viaBotNode.frame.offsetBy(dx: replyInfoFrame.minX - viaBotNode.frame.minX, dy: 0.0) - } - } - replyInfoNode.frame = replyInfoFrame - self.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) - } +// if let viaBotNode = self.viaBotNode { +// let viaBotLayout = viaBotNode.frame +// let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - viaBotLayout.size.width - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0), size: viaBotLayout.size) +// viaBotNode.frame = viaBotFrame +// self.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 4.0, y: viaBotFrame.minY - 2.0), size: CGSize(width: viaBotFrame.size.width + 8.0, height: viaBotFrame.size.height + 5.0)) +// } +// +// if let replyInfoNode = self.replyInfoNode { +// var viaBotSize = CGSize() +// if let viaBotNode = self.viaBotNode { +// viaBotSize = viaBotNode.frame.size +// } +// let replyInfoSize = replyInfoNode.frame.size +// let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - max(replyInfoSize.width, viaBotSize.width) - layoutConstants.bubble.edgeInset - 10.0)), y: 8.0 + viaBotSize.height), size: replyInfoSize) +// if let viaBotNode = self.viaBotNode { +// if replyInfoFrame.minX < viaBotNode.frame.minX { +// viaBotNode.frame = viaBotNode.frame.offsetBy(dx: replyInfoFrame.minX - viaBotNode.frame.minX, dy: 0.0) +// } +// } +// replyInfoNode.frame = replyInfoFrame +// self.replyBackgroundNode?.frame = CGRect(origin: CGPoint(x: replyInfoFrame.minX - 4.0, y: replyInfoFrame.minY - viaBotSize.height - 2.0), size: CGSize(width: max(replyInfoFrame.size.width, viaBotSize.width) + 8.0, height: replyInfoFrame.size.height + viaBotSize.height + 5.0)) +// } if let deliveryFailedNode = self.deliveryFailedNode { let deliveryFailedSize = deliveryFailedNode.frame.size @@ -1341,12 +1306,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD deliveryFailedNode.frame = deliveryFailedFrame } - if let forwardInfoNode = self.forwardInfoNode { - let forwardInfoSize = forwardInfoNode.frame.size - let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize) - forwardInfoNode.frame = forwardInfoFrame - self.forwardBackgroundNode?.frame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0)) - } +// if let forwardInfoNode = self.forwardInfoNode { +// let forwardInfoSize = forwardInfoNode.frame.size +// let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize) +// forwardInfoNode.frame = forwardInfoFrame +// } if let actionButtonsNode = self.actionButtonsNode { let actionButtonsSize = actionButtonsNode.frame.size diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 54af0e788f..d3488b4957 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -46,7 +46,6 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } class func asyncLayout(_ maybeNode: ChatMessageReplyInfoNode?) -> (_ theme: ChatPresentationData, _ strings: PresentationStrings, _ context: AccountContext, _ type: ChatMessageReplyInfoType, _ message: Message, _ constrainedSize: CGSize) -> (CGSize, () -> ChatMessageReplyInfoNode) { - let titleNodeLayout = TextNode.asyncLayout(maybeNode?.titleNode) let textNodeLayout = TextNode.asyncLayout(maybeNode?.textNode) let imageNodeLayout = TransformImageNode.asyncLayout(maybeNode?.imageNode) @@ -57,7 +56,13 @@ class ChatMessageReplyInfoNode: ASDisplayNode { let titleFont = Font.medium(fontSize) let textFont = Font.regular(fontSize) - var titleString = message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? strings.User_DeletedAccount + var author: Peer? + if let forwardAuthor = message.forwardInfo?.author { + author = forwardAuthor + } else { + author = message.effectiveAuthor + } + var titleString = author.flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder) ?? strings.User_DeletedAccount if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) { if let author = forwardInfo.author { @@ -242,9 +247,9 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } node.imageNode?.captureProtected = message.isCopyProtected() - titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: spacing - textInsets.top), size: titleLayout.size) + titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size) - let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top), size: textLayout.size) + let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size) textNode.frame = textFrame if !textLayout.spoilers.isEmpty { @@ -265,7 +270,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { } node.lineNode.image = lineImage - node.lineNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 3.0), size: CGSize(width: 2.0, height: max(0.0, size.height - 5.0))) + node.lineNode.frame = CGRect(origin: CGPoint(x: 1.0, y: 3.0), size: CGSize(width: 2.0, height: max(0.0, size.height - 4.0))) node.contentNode.frame = CGRect(origin: CGPoint(), size: size) diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 7585b1dbe1..66cae8f595 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -36,13 +36,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView { var telegramFile: TelegramMediaFile? private let fetchDisposable = MetaDisposable() - private var forwardInfoNode: ChatMessageForwardInfoNode? - private var forwardBackgroundNode: NavigationBackgroundNode? - private var viaBotNode: TextNode? private let dateAndStatusNode: ChatMessageDateAndStatusNode private var replyInfoNode: ChatMessageReplyInfoNode? private var replyBackgroundNode: NavigationBackgroundNode? + private var forwardInfoNode: ChatMessageForwardInfoNode? private var actionButtonsNode: ChatMessageActionButtonsNode? private var reactionButtonsNode: ChatMessageReactionButtonsNode? @@ -588,12 +586,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } } - var needsReplyBackground = false - - if replyInfoApply != nil || viaBotApply != nil { - needsReplyBackground = true - } - var updatedShareButtonNode: ChatMessageShareButton? if needsShareButton { if let currentShareButtonNode = currentShareButtonNode { @@ -611,7 +603,6 @@ class ChatMessageStickerItemNode: ChatMessageItemView { var forwardPsaType: String? var forwardInfoSizeApply: (CGSize, (CGFloat) -> ChatMessageForwardInfoNode)? - var needsForwardBackground = false if !ignoreForward, let forwardInfo = item.message.forwardInfo { forwardPsaType = forwardInfo.psaType @@ -636,8 +627,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } let availableForwardWidth = max(60.0, availableWidth + 6.0) forwardInfoSizeApply = makeForwardInfoLayout(item.presentationData, item.presentationData.strings, .standalone, forwardSource, forwardAuthorSignature, forwardPsaType, CGSize(width: availableForwardWidth, height: CGFloat.greatestFiniteMagnitude)) - - needsForwardBackground = true + } + + var needsReplyBackground = false + if replyInfoApply != nil || viaBotApply != nil || forwardInfoSizeApply != nil { + needsReplyBackground = true } var maxContentWidth = imageSize.width @@ -829,48 +823,89 @@ class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.replyBackgroundNode = nil } - if let (_, viaBotApply) = viaBotApply, let viaBotFrame = viaBotFrame { + var messageInfoSize = CGSize() + if let (viaBotLayout, _) = viaBotApply, forwardInfoSizeApply == nil { + messageInfoSize = CGSize(width: viaBotLayout.size.width + 1.0, height: 0.0) + } + if let (forwardInfoSize, _) = forwardInfoSizeApply { + messageInfoSize = CGSize(width: max(messageInfoSize.width, forwardInfoSize.width + 2.0), height: 0.0) + } + if let (replyInfoSize, _) = replyInfoApply { + messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: 0.0) + } + + if let (viaBotLayout, viaBotApply) = viaBotApply, forwardInfoSizeApply == nil { let viaBotNode = viaBotApply() if strongSelf.viaBotNode == nil { strongSelf.viaBotNode = viaBotNode strongSelf.contextSourceNode.contentNode.addSubnode(viaBotNode) } + let viaBotFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0), size: viaBotLayout.size) viaBotNode.frame = viaBotFrame - if let replyBackgroundNode = strongSelf.replyBackgroundNode { - replyBackgroundNode.frame = CGRect(origin: CGPoint(x: viaBotFrame.minX - 6.0, y: viaBotFrame.minY - 2.0 - UIScreenPixel), size: CGSize(width: viaBotFrame.size.width + 11.0, height: viaBotFrame.size.height + 5.0)) - replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: 8.0, transition: .immediate) - } + + messageInfoSize = CGSize(width: messageInfoSize.width, height: viaBotLayout.size.height) } else if let viaBotNode = strongSelf.viaBotNode { viaBotNode.removeFromSupernode() strongSelf.viaBotNode = nil } + + if let (forwardInfoSize, forwardInfoApply) = forwardInfoSizeApply { + let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) + if strongSelf.forwardInfoNode == nil { + strongSelf.forwardInfoNode = forwardInfoNode + strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) + + if animation.isAnimated { + forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 8.0)), y: 8.0 + messageInfoSize.height), size: forwardInfoSize) + forwardInfoNode.frame = forwardInfoFrame + + messageInfoSize = CGSize(width: messageInfoSize.width, height: messageInfoSize.height + forwardInfoSize.height - 1.0) + } else if let forwardInfoNode = strongSelf.forwardInfoNode { + if animation.isAnimated { + if let forwardInfoNode = strongSelf.forwardInfoNode { + strongSelf.forwardInfoNode = nil + forwardInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardInfoNode] _ in + forwardInfoNode?.removeFromSupernode() + }) + } + } else { + forwardInfoNode.removeFromSupernode() + strongSelf.forwardInfoNode = nil + } + } - if let (_, replyInfoApply) = replyInfoApply, let replyInfoFrame = replyInfoFrame { + if let (replyInfoSize, replyInfoApply) = replyInfoApply { let replyInfoNode = replyInfoApply() if strongSelf.replyInfoNode == nil { strongSelf.replyInfoNode = replyInfoNode strongSelf.contextSourceNode.contentNode.addSubnode(replyInfoNode) } + let replyInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 11.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 9.0)), y: 8.0 + messageInfoSize.height), size: replyInfoSize) replyInfoNode.frame = replyInfoFrame - if let replyBackgroundNode = strongSelf.replyBackgroundNode, let replyBackgroundFrame = replyBackgroundFrame { - replyBackgroundNode.frame = replyBackgroundFrame - replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: 8.0, transition: .immediate) - } - if isEmoji && !incoming { - if let _ = item.controllerInteraction.selectionState { - replyInfoNode.alpha = 0.0 - strongSelf.replyBackgroundNode?.alpha = 0.0 - } else { - replyInfoNode.alpha = 1.0 - strongSelf.replyBackgroundNode?.alpha = 1.0 - } - } + messageInfoSize = CGSize(width: max(messageInfoSize.width, replyInfoSize.width), height: messageInfoSize.height + replyInfoSize.height) } else if let replyInfoNode = strongSelf.replyInfoNode { replyInfoNode.removeFromSupernode() strongSelf.replyInfoNode = nil } + + if let replyBackgroundNode = strongSelf.replyBackgroundNode { + replyBackgroundNode.frame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 10.0) : (params.width - params.rightInset - messageInfoSize.width - layoutConstants.bubble.edgeInset - 10.0)) - 4.0, y: 6.0), size: CGSize(width: messageInfoSize.width + 8.0, height: messageInfoSize.height + 5.0)) + + let cornerRadius = replyBackgroundNode.frame.height <= 22.0 ? replyBackgroundNode.frame.height / 2.0 : 8.0 + replyBackgroundNode.update(size: replyBackgroundNode.bounds.size, cornerRadius: cornerRadius, transition: .immediate) + } + + let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 + strongSelf.replyInfoNode?.alpha = panelsAlpha + strongSelf.viaBotNode?.alpha = panelsAlpha + strongSelf.forwardInfoNode?.alpha = panelsAlpha + strongSelf.replyBackgroundNode?.alpha = panelsAlpha + if isFailed { let deliveryFailedNode: ChatMessageDeliveryFailedNode var isAppearing = false @@ -901,61 +936,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { deliveryFailedNode?.removeFromSupernode() }) } - - if needsForwardBackground { - if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - forwardBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) - } else { - let forwardBackgroundNode = NavigationBackgroundNode(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)) - strongSelf.forwardBackgroundNode = forwardBackgroundNode - strongSelf.contextSourceNode.contentNode.addSubnode(forwardBackgroundNode) - - if animation.isAnimated { - forwardBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - } else if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - if animation.isAnimated { - strongSelf.forwardBackgroundNode = nil - forwardBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardBackgroundNode] _ in - forwardBackgroundNode?.removeFromSupernode() - }) - } else { - forwardBackgroundNode.removeFromSupernode() - strongSelf.forwardBackgroundNode = nil - } - } - - if let (forwardInfoSize, forwardInfoApply) = forwardInfoSizeApply { - let forwardInfoNode = forwardInfoApply(forwardInfoSize.width) - if strongSelf.forwardInfoNode == nil { - strongSelf.forwardInfoNode = forwardInfoNode - strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode) - - if animation.isAnimated { - forwardInfoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - } - } - let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize) - forwardInfoNode.frame = forwardInfoFrame - if let forwardBackgroundNode = strongSelf.forwardBackgroundNode { - forwardBackgroundNode.frame = CGRect(origin: CGPoint(x: forwardInfoFrame.minX - 6.0, y: forwardInfoFrame.minY - 2.0), size: CGSize(width: forwardInfoFrame.size.width + 10.0, height: forwardInfoFrame.size.height + 4.0)) - forwardBackgroundNode.update(size: forwardBackgroundNode.bounds.size, cornerRadius: 8.0, transition: .immediate) - } - } else if let forwardInfoNode = strongSelf.forwardInfoNode { - if animation.isAnimated { - if let forwardInfoNode = strongSelf.forwardInfoNode { - strongSelf.forwardInfoNode = nil - forwardInfoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, removeOnCompletion: false, completion: { [weak forwardInfoNode] _ in - forwardInfoNode?.removeFromSupernode() - }) - } - } else { - forwardInfoNode.removeFromSupernode() - strongSelf.forwardInfoNode = nil - } - } - + if let actionButtonsSizeAndApply = actionButtonsSizeAndApply { let actionButtonsNode = actionButtonsSizeAndApply.1(animation) let previousFrame = actionButtonsNode.frame @@ -1333,12 +1314,18 @@ class ChatMessageStickerItemNode: ChatMessageItemView { } let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate - let replyAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 + let panelsAlpha: CGFloat = item.controllerInteraction.selectionState == nil ? 1.0 : 0.0 if let replyInfoNode = self.replyInfoNode { - transition.updateAlpha(node: replyInfoNode, alpha: replyAlpha) + transition.updateAlpha(node: replyInfoNode, alpha: panelsAlpha) + } + if let viaBotNode = self.viaBotNode { + transition.updateAlpha(node: viaBotNode, alpha: panelsAlpha) + } + if let forwardInfoNode = self.forwardInfoNode { + transition.updateAlpha(node: forwardInfoNode, alpha: panelsAlpha) } if let replyBackgroundNode = self.replyBackgroundNode { - transition.updateAlpha(node: replyBackgroundNode, alpha: replyAlpha) + transition.updateAlpha(node: replyBackgroundNode, alpha: panelsAlpha) } if let selectionState = item.controllerInteraction.selectionState { diff --git a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift index 1b8c571cbe..d178bcd109 100644 --- a/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageTransitionNode.swift @@ -521,6 +521,62 @@ public final class ChatMessageTransitionNode: ASDisplayNode { } case let .mediaInput(mediaInput): if let snapshotView = mediaInput.extractSnapshot() { + Queue.mainQueue().justDispatch { + if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { + itemNode.cancelInsertionAnimations() + + self.contextSourceNode.isExtractedToContextPreview = true + self.contextSourceNode.isExtractedToContextPreviewUpdated?(true) + + self.containerNode.addSubnode(self.contextSourceNode.contentNode) + + let targetAbsoluteRect = self.contextSourceNode.view.convert(self.contextSourceNode.contentRect, to: self.view) + let sourceBackgroundAbsoluteRect = snapshotView.frame + 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) + + let combinedTransition = CombinedTransition(horizontal: .animated(duration: horizontalDuration, curve: ChatMessageTransitionNode.horizontalAnimationCurve), vertical: .animated(duration: verticalDuration, curve: ChatMessageTransitionNode.verticalAnimationCurve)) + + if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { + itemNode.animateContentFromMediaInput(snapshotView: snapshotView, transition: combinedTransition) + } + + self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.minY) + + snapshotView.center = targetAbsoluteRect.center.offsetBy(dx: -self.containerNode.frame.minX, dy: -self.containerNode.frame.minY) + self.containerNode.view.addSubview(snapshotView) + + 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)) + + combinedTransition.horizontal.updateTransformScale(layer: snapshotView.layer, scale: CGPoint(x: 1.0 / (sourceBackgroundAbsoluteRect.width / targetAbsoluteRect.width), y: 1.0 / (sourceBackgroundAbsoluteRect.height / targetAbsoluteRect.height))) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + + 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 + } + Queue.mainQueue().justDispatch { if let itemNode = self.itemNode as? ChatMessageBubbleItemNode { itemNode.cancelInsertionAnimations() @@ -529,20 +585,43 @@ public final class ChatMessageTransitionNode: ASDisplayNode { self.containerNode.addSubnode(self.contextSourceNode.contentNode) - let targetAbsoluteRect = self.contextSourceNode.view.convert(self.contextSourceNode.contentRect, to: self.view) - let sourceBackgroundAbsoluteRect = snapshotView.frame - 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) - 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 { - itemNode.animateContentFromMediaInput(snapshotView: snapshotView, transition: combinedTransition) + 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)) } - self.containerNode.frame = targetAbsoluteRect.offsetBy(dx: -self.contextSourceNode.contentRect.minX, dy: -self.contextSourceNode.contentRect.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) - snapshotView.center = targetAbsoluteRect.center.offsetBy(dx: -self.containerNode.frame.minX, dy: -self.containerNode.frame.minY) - self.containerNode.view.addSubview(snapshotView) + 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) @@ -556,101 +635,26 @@ public final class ChatMessageTransitionNode: ASDisplayNode { combinedTransition.horizontal.animateTransformScale(node: self.contextSourceNode.contentNode, from: CGPoint(x: sourceBackgroundAbsoluteRect.width / targetAbsoluteRect.width, y: sourceBackgroundAbsoluteRect.height / targetAbsoluteRect.height)) - combinedTransition.horizontal.updateTransformScale(layer: snapshotView.layer, scale: CGPoint(x: 1.0 / (sourceBackgroundAbsoluteRect.width / targetAbsoluteRect.width), y: 1.0 / (sourceBackgroundAbsoluteRect.height / targetAbsoluteRect.height))) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - + 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) } - } 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) } } } diff --git a/submodules/TelegramUI/Sources/LegacyCamera.swift b/submodules/TelegramUI/Sources/LegacyCamera.swift index 4b85a465c3..fd23afa47a 100644 --- a/submodules/TelegramUI/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Sources/LegacyCamera.swift @@ -10,7 +10,7 @@ import ShareController import LegacyUI import LegacyMediaPickerUI -func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, hasSchedule: Bool, photoOnly: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) { +func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, photoOnly: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, presentStickers: @escaping (@escaping (TelegramMediaFile, Bool, UIView, CGRect) -> Void) -> TGPhotoPaintStickersScreen?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) @@ -28,6 +28,10 @@ func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: Ch } controller.inhibitMultipleCapture = editingMedia + if !initialCaption.string.isEmpty { + controller.forcedCaption = initialCaption + } + controller.presentScheduleController = { _, done in presentSchedulePicker(true, { time in done?(time) diff --git a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift index b1a9870d83..afde2592a1 100644 --- a/submodules/TextFormat/Sources/StringWithAppliedEntities.swift +++ b/submodules/TextFormat/Sources/StringWithAppliedEntities.swift @@ -251,20 +251,56 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti break } + var addedAttributes: [(NSRange, ChatTextFontAttributes)] = [] + func addFont(ranges: [NSRange], fontAttributes: ChatTextFontAttributes) { + for range in ranges { + var font: UIFont? + if fontAttributes == [.bold, .italic] { + font = boldItalicFont + } else if fontAttributes == [.bold] { + font = boldFont + addedAttributes.append((range, fontAttributes)) + } else if fontAttributes == [.italic] { + font = italicFont + addedAttributes.append((range, fontAttributes)) + } + if let font = font { + string.addAttribute(NSAttributedString.Key.font, value: font, range: range) + } + } + } + for (range, fontAttributes) in fontAttributes { - var font: UIFont? - if fontAttributes.contains(.blockQuote) { - font = blockQuoteFont - } else if fontAttributes == [.bold, .italic] { - font = boldItalicFont - } else if fontAttributes == [.bold] { - font = boldFont - } else if fontAttributes == [.italic] { - font = italicFont - } - if let font = font { - string.addAttribute(NSAttributedString.Key.font, value: font, range: range) + var ranges = [range] + var fontAttributes = fontAttributes + if fontAttributes != [.bold, .italic] { + for (existingRange, existingAttributes) in addedAttributes { + if let intersection = existingRange.intersection(range) { + if intersection.length == range.length { + if existingAttributes == .bold || existingAttributes == .italic { + fontAttributes.insert(existingAttributes) + } + } else { + var fontAttributes = fontAttributes + if existingAttributes == .bold || existingAttributes == .italic { + fontAttributes.insert(existingAttributes) + } + addFont(ranges: [intersection], fontAttributes: fontAttributes) + + ranges = [] + if range.upperBound > existingRange.lowerBound { + ranges.append(NSRange(location: range.lowerBound, length: existingRange.lowerBound - range.lowerBound)) + } + if range.upperBound > existingRange.upperBound { + ranges.append(NSRange(location: existingRange.upperBound, length: range.upperBound - existingRange.upperBound)) + } + } + break + } + } } + + addFont(ranges: ranges, fontAttributes: fontAttributes) } } return string