diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 10a12454ca..823cc4e5ab 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -3563,10 +3563,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture headerNode = current switch transition.0 { case .immediate: - headerNode.frame = headerFrame + headerNode.updateFrame(headerFrame, within: self.visibleSize) case let .animated(duration, curve): let previousFrame = headerNode.frame - headerNode.frame = headerFrame + headerNode.updateFrame(headerFrame, within: self.visibleSize) var offset = headerFrame.minY - previousFrame.minY + transition.2 if headerNode.isRotated { offset = -offset diff --git a/submodules/Display/Source/ListViewItemHeader.swift b/submodules/Display/Source/ListViewItemHeader.swift index 6356694670..01f0f96e8c 100644 --- a/submodules/Display/Source/ListViewItemHeader.swift +++ b/submodules/Display/Source/ListViewItemHeader.swift @@ -141,4 +141,17 @@ open class ListViewItemHeaderNode: ASDisplayNode { open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { } + + open func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + } + + public func updateFrame(_ frame: CGRect, within containerSize: CGSize, updateFrame: Bool = true) { + if updateFrame { + self.frame = frame + } + if frame.maxY < 0.0 || frame.minY > containerSize.height { + } else { + self.updateAbsoluteRect(frame, within: containerSize) + } + } } diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 3d73f0c733..af90c5875a 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -378,7 +378,7 @@ open class NavigationController: UINavigationController, ContainableController, let belowKeyboardOverlayLayout = layout var globalOverlayLayout = layout - globalOverlayLayout.inputHeight = nil +// globalOverlayLayout.inputHeight = nil if let globalOverlayBelowKeyboardContainerParent = self.globalOverlayBelowKeyboardContainerParent { if globalOverlayBelowKeyboardContainerParent.view.superview != self.displayNode.view { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index 4e933489c2..37c850d737 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1014,11 +1014,15 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { func setupItem(_ item: UniversalVideoGalleryItem) { if self.item?.content.id != item.content.id { func parseChapters(_ string: NSAttributedString) -> [MediaPlayerScrubbingChapter] { + var existingTimecodes = Set() var timecodeRanges: [(NSRange, TelegramTimecode)] = [] var lineRanges: [NSRange] = [] string.enumerateAttributes(in: NSMakeRange(0, string.length), options: [], using: { attributes, range, _ in if let timecode = attributes[NSAttributedString.Key(TelegramTextAttributes.Timecode)] as? TelegramTimecode { - timecodeRanges.append((range, timecode)) + if !existingTimecodes.contains(timecode.time) { + timecodeRanges.append((range, timecode)) + existingTimecodes.insert(timecode.time) + } } }) (string.string as NSString).enumerateSubstrings(in: NSMakeRange(0, string.length), options: .byLines, using: { _, range, _, _ in @@ -1029,7 +1033,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { for (timecodeRange, timecode) in timecodeRanges { inner: for lineRange in lineRanges { if lineRange.contains(timecodeRange.location) { - if lineRange.length > timecodeRange.length { + if lineRange.length > timecodeRange.length && timecodeRange.location < lineRange.location + 4 { var title = ((string.string as NSString).substring(with: lineRange) as NSString).replacingCharacters(in: NSMakeRange(timecodeRange.location - lineRange.location, timecodeRange.length), with: "") title = title.trimmingCharacters(in: .whitespacesAndNewlines).trimmingCharacters(in: .punctuationCharacters) chapters.append(MediaPlayerScrubbingChapter(title: title, start: timecode.time)) @@ -1038,6 +1042,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } } } + return chapters } diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index 7df4d9995d..8448d70ddb 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -877,7 +877,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { } let endPosition: CGFloat = max(startPosition, floor(backgroundFrame.width * CGFloat(chapter.start / duration)) - lineWidth / 2.0) let width = endPosition - startPosition - if width < lineWidth * 2.0 { + if width < lineWidth * 0.5 { previousChapterNode.frame = CGRect() continue } diff --git a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift index 752aa33aa9..854e2ea5e2 100644 --- a/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/DefaultDarkPresentationTheme.swift @@ -602,7 +602,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati ) let serviceMessage = PresentationThemeServiceMessage( - components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), scam: UIColor(rgb: 0xeb5545), dateFillStatic: UIColor(rgb: 0x000000, alpha: 0.6), dateFillFloating: UIColor(rgb: 0x000000, alpha: 0.2)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), scam: UIColor(rgb: 0xeb5545), dateFillStatic: UIColor(rgb: 0x000000, alpha: 0.6), dateFillFloating: UIColor(rgb: 0x000000, alpha: 0.2))), + components: PresentationThemeServiceMessageColor(withDefaultWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), scam: UIColor(rgb: 0xeb5545), dateFillStatic: UIColor(rgb: 0x000000, alpha: 0.2), dateFillFloating: UIColor(rgb: 0x000000, alpha: 0.2)), withCustomWallpaper: PresentationThemeServiceMessageColorComponents(fill: UIColor(rgb: 0x1f1f1f, alpha: 1.0), primaryText: UIColor(rgb: 0xffffff), linkHighlight: UIColor(rgb: 0xffffff, alpha: 0.12), scam: UIColor(rgb: 0xeb5545), dateFillStatic: UIColor(rgb: 0x000000, alpha: 0.2), dateFillFloating: UIColor(rgb: 0x000000, alpha: 0.2))), unreadBarFillColor: UIColor(rgb: 0x1b1b1b), unreadBarStrokeColor: UIColor(rgb: 0x1b1b1b), unreadBarTextColor: UIColor(rgb: 0xffffff), diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 29ea387cfd..c67774c73c 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -740,7 +740,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { self.emptyNode = emptyNode self.historyNodeContainer.supernode?.insertSubnode(emptyNode, aboveSubnode: self.historyNodeContainer) if let (size, insets) = self.validEmptyNodeLayout { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, size: size, insets: insets, transition: .immediate) + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: wasLoading && self.loadingNode.supernode != nil ? self.loadingNode : nil, backgroundNode: self.backgroundNode, size: size, insets: insets, transition: .immediate) } if animated { emptyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -1513,8 +1513,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { emptyNodeInsets.bottom += inputPanelsHeight self.validEmptyNodeLayout = (contentBounds.size, emptyNodeInsets) if let emptyNode = self.emptyNode, let emptyType = self.emptyType { - emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: nil, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) + emptyNode.updateLayout(interfaceState: self.chatPresentationInterfaceState, emptyType: emptyType, loadingNode: nil, backgroundNode: self.backgroundNode, size: contentBounds.size, insets: emptyNodeInsets, transition: transition) transition.updateFrame(node: emptyNode, frame: contentBounds) + emptyNode.update(rect: contentBounds, within: contentBounds.size) } var contentBottomInset: CGFloat = inputPanelsHeight + 4.0 diff --git a/submodules/TelegramUI/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Sources/ChatEmptyNode.swift index 3a81a68828..dbae9369c6 100644 --- a/submodules/TelegramUI/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Sources/ChatEmptyNode.swift @@ -11,6 +11,7 @@ import LocalizedPeerData import TelegramStringFormatting import AccountContext import ChatPresentationInterfaceState +import WallpaperBackgroundNode private protocol ChatEmptyNodeContent { func updateLayout(interfaceState: ChatPresentationInterfaceState, size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize @@ -789,6 +790,11 @@ final class ChatEmptyNode: ASDisplayNode { private let backgroundNode: NavigationBackgroundNode + private var wallpaperBackgroundNode: WallpaperBackgroundNode? + private var backgroundContent: WallpaperBubbleBackgroundNode? + + private var absolutePosition: (CGRect, CGSize)? + private var currentTheme: PresentationTheme? private var currentStrings: PresentationStrings? @@ -820,12 +826,21 @@ final class ChatEmptyNode: ASDisplayNode { let targetFrame = self.backgroundNode.frame let initialFrame = loadingNode.convert(loadingNode.progressFrame, to: self) + let transition = ContainedViewLayoutTransition.animated(duration: duration, curve: .easeInOut) self.backgroundNode.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration) self.backgroundNode.update(size: initialFrame.size, cornerRadius: initialFrame.size.width / 2.0, transition: .immediate) - self.backgroundNode.update(size: targetFrame.size, cornerRadius: targetCornerRadius, transition: .animated(duration: duration, curve: .easeInOut)) + self.backgroundNode.update(size: targetFrame.size, cornerRadius: targetCornerRadius, transition: transition) + + if let backgroundContent = self.backgroundContent { + backgroundContent.layer.animateFrame(from: initialFrame, to: targetFrame, duration: duration) + backgroundContent.cornerRadius = initialFrame.size.width / 2.0 + transition.updateCornerRadius(layer: backgroundContent.layer, cornerRadius: targetCornerRadius) + } } - func updateLayout(interfaceState: ChatPresentationInterfaceState, emptyType: ChatHistoryNodeLoadState.EmptyType, loadingNode: ChatLoadingNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + func updateLayout(interfaceState: ChatPresentationInterfaceState, emptyType: ChatHistoryNodeLoadState.EmptyType, loadingNode: ChatLoadingNode?, backgroundNode: WallpaperBackgroundNode?, size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) { + self.wallpaperBackgroundNode = backgroundNode + if self.currentTheme !== interfaceState.theme || self.currentStrings !== interfaceState.strings { self.currentTheme = interfaceState.theme self.currentStrings = interfaceState.strings @@ -833,6 +848,8 @@ final class ChatEmptyNode: ASDisplayNode { self.backgroundNode.updateColor(color: selectDateFillStaticColor(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), enableBlur: dateFillNeedsBlur(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper), transition: .immediate) } + + var isScheduledMessages = false if case .scheduledMessages = interfaceState.subject { isScheduledMessages = true @@ -923,8 +940,45 @@ final class ChatEmptyNode: ASDisplayNode { transition.updateFrame(node: self.backgroundNode, frame: contentFrame) self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: min(20.0, self.backgroundNode.bounds.height / 2.0), transition: transition) + if backgroundNode?.hasExtraBubbleBackground() == true { + if self.backgroundContent == nil, let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + + self.backgroundContent = backgroundContent + self.insertSubnode(backgroundContent, at: 0) + } + } else { + self.backgroundContent?.removeFromSupernode() + self.backgroundContent = nil + } + + if let backgroundContent = self.backgroundContent { + self.backgroundNode.isHidden = true + backgroundContent.cornerRadius = min(20.0, self.backgroundNode.bounds.height / 2.0) + backgroundContent.frame = contentFrame + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } else { + self.backgroundNode.isHidden = false + } + if let loadingNode = loadingNode { self.animateFromLoadingNode(loadingNode) } } + + + public func update(rect: CGRect, within containerSize: CGSize, transition: ContainedViewLayoutTransition = .immediate) { + self.absolutePosition = (rect, containerSize) + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: transition) + } + } } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index d6c9a94fb3..a690473be2 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -244,7 +244,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca } return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): - return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint) + return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context), directionHint: entry.directionHint) case let .ReplyCountEntry(_, isComments, count, presentationData): return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context, controllerInteraction: controllerInteraction), directionHint: entry.directionHint) case let .ChatInfoEntry(title, text, photo, video, presentationData): @@ -289,7 +289,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca } return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint) case let .UnreadEntry(_, presentationData): - return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, context: context), directionHint: entry.directionHint) + return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatUnreadItem(index: entry.entry.index, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context), directionHint: entry.directionHint) case let .ReplyCountEntry(_, isComments, count, presentationData): return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatReplyCountItem(index: entry.entry.index, isComments: isComments, count: count, presentationData: presentationData, context: context, controllerInteraction: controllerInteraction), directionHint: entry.directionHint) case let .ChatInfoEntry(title, text, photo, video, presentationData): diff --git a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift index 3a35ccd26d..c4f2c48a7e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionButtonsNode.swift @@ -6,6 +6,7 @@ import Postbox import Display import TelegramPresentationData import AccountContext +import WallpaperBackgroundNode private let titleFont = Font.medium(16.0) @@ -16,6 +17,12 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { private var iconNode: ASImageNode? private var buttonView: HighlightTrackingButton? + private var wallpaperBackgroundNode: WallpaperBackgroundNode? + private var backgroundContent: WallpaperBubbleBackgroundNode? + private var backgroundColorNode: ASDisplayNode? + + private var absolutePosition: (CGRect, CGSize)? + private var button: ReplyMarkupButton? var pressed: ((ReplyMarkupButton) -> Void)? var longTapped: ((ReplyMarkupButton) -> Void)? @@ -58,9 +65,15 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { if highlighted { strongSelf.backgroundBlurNode.layer.removeAnimation(forKey: "opacity") strongSelf.backgroundBlurNode.alpha = 0.55 + + strongSelf.backgroundContent?.layer.removeAnimation(forKey: "opacity") + strongSelf.backgroundContent?.alpha = 0.55 } else { strongSelf.backgroundBlurNode.alpha = 1.0 strongSelf.backgroundBlurNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) + + strongSelf.backgroundContent?.alpha = 1.0 + strongSelf.backgroundContent?.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) } } } @@ -83,10 +96,21 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ message: Message, _ button: ReplyMarkupButton, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absolutePosition = (rect, containerSize) + + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + + class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode) - return { context, theme, bubbleCorners, strings, message, button, constrainedWidth, position in + return { context, theme, bubbleCorners, strings, backgroundNode, message, button, constrainedWidth, position in let incoming = message.effectivelyIncoming(context.account.peerId) let graphics = PresentationResourcesChat.additionalGraphics(theme.theme, wallpaper: theme.wallpaper, bubbleCorners: bubbleCorners) @@ -159,6 +183,8 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { animation = .None } + node.wallpaperBackgroundNode = backgroundNode + node.button = button switch button.action { @@ -175,6 +201,44 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { node.backgroundBlurNode.update(size: node.backgroundBlurNode.bounds.size, cornerRadius: bubbleCorners.auxiliaryRadius, animator: animation.animator) node.backgroundBlurNode.updateColor(color: selectDateFillStaticColor(theme: theme.theme, wallpaper: theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: theme.theme, wallpaper: theme.wallpaper), transition: .immediate) + if backgroundNode?.hasExtraBubbleBackground() == true { + if node.backgroundContent == nil, let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + backgroundContent.allowsGroupOpacity = true + + node.backgroundContent = backgroundContent + node.insertSubnode(backgroundContent, at: 0) + + let backgroundColorNode = ASDisplayNode() + backgroundColorNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4) + backgroundContent.addSubnode(backgroundColorNode) + node.backgroundColorNode = backgroundColorNode + } + } else { + node.backgroundContent?.removeFromSupernode() + node.backgroundContent = nil + + node.backgroundColorNode?.removeFromSupernode() + node.backgroundColorNode = nil + } + + if let backgroundContent = node.backgroundContent { + node.backgroundBlurNode.isHidden = true + backgroundContent.frame = node.backgroundBlurNode.frame + backgroundContent.cornerRadius = bubbleCorners.auxiliaryRadius + + node.backgroundColorNode?.frame = backgroundContent.bounds + + if let (rect, containerSize) = node.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } else { + node.backgroundBlurNode.isHidden = false + } + if iconImage != nil { if node.iconNode == nil { let iconNode = ASImageNode() @@ -206,6 +270,10 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { animation.animator.updateFrame(layer: iconNode.layer, frame: CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0), completion: nil) } + if let (rect, size) = node.absolutePosition { + node.updateAbsoluteRect(rect, within: size) + } + node.accessibilityArea.accessibilityLabel = title node.accessibilityArea.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0)) @@ -224,6 +292,8 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { var buttonPressed: ((ReplyMarkupButton) -> Void)? var buttonLongTapped: ((ReplyMarkupButton) -> Void)? + private var absolutePosition: (CGRect, CGSize)? + override init() { super.init() @@ -240,10 +310,21 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absolutePosition = (rect, containerSize) + + for button in buttonNodes { + var buttonFrame = button.frame + buttonFrame.origin.x += rect.minX + buttonFrame.origin.y += rect.minY + button.updateAbsoluteRect(buttonFrame, within: containerSize) + } + } + + class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? [] - return { context, theme, chatBubbleCorners, strings, replyMarkup, message, constrainedWidth in + return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, message, constrainedWidth in let buttonHeight: CGFloat = 42.0 let buttonSpacing: CGFloat = 4.0 @@ -276,9 +357,9 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { let prepareButtonLayout: (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) if buttonIndex < currentButtonLayouts.count { - prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, message, button, maximumButtonWidth, buttonPosition) + prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, backgroundNode, message, button, maximumButtonWidth, buttonPosition) } else { - prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, message, button, maximumButtonWidth, buttonPosition) + prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, backgroundNode, message, button, maximumButtonWidth, buttonPosition) } maximumRowButtonWidth = max(maximumRowButtonWidth, prepareButtonLayout.minimumWidth) @@ -363,6 +444,10 @@ final class ChatMessageActionButtonsNode: ASDisplayNode { } node.buttonNodes = updatedButtons + if let (rect, size) = node.absolutePosition { + node.updateAbsoluteRect(rect, within: size) + } + return node }) }) diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index dddc4117c6..ee6292924d 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -57,6 +57,8 @@ extension SlotMachineAnimationNode: GenericAnimatedStickerNode { } class ChatMessageShareButton: HighlightableButtonNode { + private var backgroundContent: WallpaperBubbleBackgroundNode? + private let backgroundNode: NavigationBackgroundNode private let iconNode: ASImageNode private var iconOffset = CGPoint() @@ -66,6 +68,8 @@ class ChatMessageShareButton: HighlightableButtonNode { private var textNode: ImmediateTextNode? + private var absolutePosition: (CGRect, CGSize)? + init() { self.backgroundNode = NavigationBackgroundNode(color: .clear) self.iconNode = ASImageNode() @@ -80,7 +84,7 @@ class ChatMessageShareButton: HighlightableButtonNode { fatalError("init(coder:) has not been implemented") } - func update(presentationData: ChatPresentationData, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { + func update(presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false) -> CGSize { var isReplies = false var replyCount = 0 if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info { @@ -160,8 +164,46 @@ class ChatMessageShareButton: HighlightableButtonNode { if let image = self.iconNode.image { self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.iconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.iconOffset.y), size: image.size) } + + + if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { + if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + backgroundContent.allowsGroupOpacity = true + self.backgroundContent = backgroundContent + self.insertSubnode(backgroundContent, at: 0) + } + } else { + self.backgroundContent?.removeFromSupernode() + self.backgroundContent = nil + } + + if let backgroundContent = self.backgroundContent { + self.backgroundNode.isHidden = true + backgroundContent.cornerRadius = min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0 + backgroundContent.frame = self.backgroundNode.frame + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } else { + self.backgroundNode.isHidden = false + } + return size } + + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absolutePosition = (rect, containerSize) + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } } class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { @@ -1206,7 +1248,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, replyMarkup, item.message, maxContentWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, item.message, maxContentWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -1359,7 +1401,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - buttonSize.height - 4.0 + imageBottomPadding), size: buttonSize) } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 803d8b2794..6673df3966 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1073,7 +1073,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode adminBadgeLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode), forwardInfoLayout: (ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageReplyInfoNode), - actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), + actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)), mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)), layoutConstants: ChatMessageItemLayoutConstants, @@ -1937,7 +1937,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, replyMarkup, item.message, maximumNodeWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, item.message, maximumNodeWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -2959,7 +2959,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } if let shareButtonNode = strongSelf.shareButtonNode { let currentBackgroundFrame = strongSelf.backgroundNode.frame - let buttonSize = shareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true) + let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true) animation.animator.updateFrame(layer: shareButtonNode.layer, frame: CGRect(origin: CGPoint(x: currentBackgroundFrame.maxX + 8.0, y: currentBackgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize), completion: nil) } } else { @@ -2969,7 +2969,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode }*/ strongSelf.messageAccessibilityArea.frame = backgroundFrame if let shareButtonNode = strongSelf.shareButtonNode { - let buttonSize = shareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true) + let buttonSize = shareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account, disableComments: true) shareButtonNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.maxX + 8.0, y: backgroundFrame.maxY - buttonSize.width - 1.0), size: buttonSize) } @@ -3984,6 +3984,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode contentNode.updateAbsoluteRect(CGRect(origin: CGPoint(x: rect.minX + contentNode.frame.minX, y: rect.minY + contentNode.frame.minY), size: rect.size), within: containerSize) } + if let shareButtonNode = self.shareButtonNode { + var shareButtonNodeFrame = shareButtonNode.frame + shareButtonNodeFrame.origin.x += rect.minX + shareButtonNodeFrame.origin.y += rect.minY + + shareButtonNode.updateAbsoluteRect(shareButtonNodeFrame, within: containerSize) + } + + if let actionButtonsNode = self.actionButtonsNode { + var actionButtonsNodeFrame = actionButtonsNode.frame + actionButtonsNodeFrame.origin.x += rect.minX + actionButtonsNodeFrame.origin.y += rect.minY + + actionButtonsNode.updateAbsoluteRect(actionButtonsNodeFrame, within: containerSize) + } + if let reactionButtonsNode = self.reactionButtonsNode { var reactionButtonsNodeFrame = reactionButtonsNode.frame reactionButtonsNodeFrame.origin.x += rect.minX diff --git a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift index df77623653..89d9363475 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateHeader.swift @@ -12,6 +12,7 @@ import TelegramUniversalVideoContent import UniversalMediaPlayer import GalleryUI import HierarchyTrackingLayer +import WallpaperBackgroundNode private let timezoneOffset: Int32 = { let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) @@ -30,13 +31,15 @@ final class ChatMessageDateHeader: ListViewItemHeader { let id: ListViewItemNode.HeaderId let presentationData: ChatPresentationData + let controllerInteraction: ChatControllerInteraction? let context: AccountContext let action: ((Int32, Bool) -> Void)? - init(timestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { + init(timestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { self.timestamp = timestamp self.scheduled = scheduled self.presentationData = presentationData + self.controllerInteraction = controllerInteraction self.context = context self.action = action self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp) @@ -57,7 +60,7 @@ final class ChatMessageDateHeader: ListViewItemHeader { } func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { - return ChatMessageDateHeaderNode(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, presentationData: self.presentationData, context: self.context, action: self.action) + return ChatMessageDateHeaderNode(localTimestamp: self.roundedTimestamp, scheduled: self.scheduled, presentationData: self.presentationData, controllerInteraction: self.controllerInteraction, context: self.context, action: self.action) } func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) { @@ -115,8 +118,11 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { let stickBackgroundNode: ASImageNode let activateArea: AccessibilityAreaNode + private var backgroundContent: WallpaperBubbleBackgroundNode? + private let localTimestamp: Int32 private var presentationData: ChatPresentationData + private let controllerInteraction: ChatControllerInteraction? private let context: AccountContext private let text: String @@ -124,8 +130,11 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { private var stickDistanceFactor: CGFloat = 0.0 private var action: ((Int32, Bool) -> Void)? = nil - init(localTimestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { + private var absolutePosition: (CGRect, CGSize)? + + init(localTimestamp: Int32, scheduled: Bool, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction?, context: AccountContext, action: ((Int32, Bool) -> Void)? = nil) { self.presentationData = presentationData + self.controllerInteraction = controllerInteraction self.context = context self.localTimestamp = localTimestamp @@ -135,6 +144,11 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.labelNode.isUserInteractionEnabled = false self.labelNode.displaysAsynchronously = !presentationData.isPreview + if controllerInteraction?.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true, let backgroundContent = controllerInteraction?.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + self.backgroundContent = backgroundContent + } + self.backgroundNode = NavigationBackgroundNode(color: .clear) self.backgroundNode.isUserInteractionEnabled = false @@ -188,7 +202,11 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.stickBackgroundNode.image = graphics.dateFloatingBackground self.stickBackgroundNode.alpha = 0.0 - self.addSubnode(self.backgroundNode) + if let backgroundContent = self.backgroundContent { + self.addSubnode(backgroundContent) + } else { + self.addSubnode(self.backgroundNode) + } self.addSubnode(self.labelNode) self.addSubnode(self.activateArea) @@ -239,6 +257,16 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.backgroundNode.updateColor(color: color, enableBlur: enableBlur, transition: .immediate) } + override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absolutePosition = (rect, containerSize) + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += containerSize.height - rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { let chatDateSize: CGFloat = 20.0 let chatDateInset: CGFloat = 6.0 @@ -253,6 +281,20 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { self.labelNode.frame = CGRect(origin: CGPoint(x: backgroundFrame.origin.x + chatDateInset, y: backgroundFrame.origin.y + floorToScreenPixels((backgroundSize.height - labelSize.height) / 2.0)), size: labelSize) self.activateArea.frame = backgroundFrame + + if let backgroundContent = self.backgroundContent { + backgroundContent.allowsGroupOpacity = true + self.backgroundNode.isHidden = true + backgroundContent.frame = self.backgroundNode.frame + backgroundContent.cornerRadius = backgroundFrame.size.height / 2.0 + + if let (rect, containerSize) = self.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += containerSize.height - rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } } override func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { @@ -285,10 +327,12 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { let previousAlpha = self.backgroundNode.alpha if !previousAlpha.isEqual(to: alpha) { + self.backgroundContent?.alpha = alpha self.backgroundNode.alpha = alpha self.labelNode.alpha = alpha if animated { let duration: Double = flashing ? 0.3 : 0.4 + self.backgroundContent?.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration) self.backgroundNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration) self.labelNode.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration) } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 74f360a2a0..2bb526a3d7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -532,7 +532,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD var maxContentWidth = normalDisplaySize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, replyMarkup, item.message, maxContentWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, item.message, maxContentWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -636,7 +636,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) updatedShareButtonNode.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) } else if let shareButtonNode = strongSelf.shareButtonNode { shareButtonNode.removeFromSupernode() diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index 4fae899d44..547874fe48 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -328,7 +328,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { isScheduledMessages = true } - self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, scheduled: isScheduledMessages, presentationData: presentationData, context: context, action: { timestamp, alreadyThere in + self.dateHeader = ChatMessageDateHeader(timestamp: content.index.timestamp, scheduled: isScheduledMessages, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context, action: { timestamp, alreadyThere in var calendar = NSCalendar.current calendar.timeZone = TimeZone(abbreviation: "UTC")! let date = Date(timeIntervalSince1970: TimeInterval(timestamp)) diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift index 0afbd03d9b..7e4ff74531 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift @@ -84,7 +84,7 @@ struct ChatMessageItemLayoutConstants { } fileprivate static var compact: ChatMessageItemLayoutConstants { - let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)) + let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 0.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.85), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)) let text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0)) let image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 16.0, mergedCornerRadius: 8.0, contentMergedCornerRadius: 0.0, maxDimensions: CGSize(width: 300.0, height: 380.0), minDimensions: CGSize(width: 170.0, height: 74.0)) let video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0) @@ -96,7 +96,7 @@ struct ChatMessageItemLayoutConstants { } fileprivate static var regular: ChatMessageItemLayoutConstants { - let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 1.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.65), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)) + let bubble = ChatMessageItemBubbleLayoutConstants(edgeInset: 4.0, defaultSpacing: 2.0 + UIScreenPixel, mergedSpacing: 0.0, maximumWidthFill: ChatMessageItemWidthFill(compactInset: 36.0, compactWidthBoundary: 500.0, freeMaximumFillFactor: 0.65), minimumSize: CGSize(width: 40.0, height: 35.0), contentInsets: UIEdgeInsets(top: 0.0, left: 6.0, bottom: 0.0, right: 0.0), borderInset: UIScreenPixel, strokeInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)) let text = ChatMessageItemTextLayoutConstants(bubbleInsets: UIEdgeInsets(top: 6.0 + UIScreenPixel, left: 12.0, bottom: 6.0 - UIScreenPixel, right: 12.0)) let image = ChatMessageItemImageLayoutConstants(bubbleInsets: UIEdgeInsets(top: 2.0, left: 2.0, bottom: 2.0, right: 2.0), statusInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 6.0, right: 6.0), defaultCornerRadius: 16.0, mergedCornerRadius: 8.0, contentMergedCornerRadius: 5.0, maxDimensions: CGSize(width: 440.0, height: 440.0), minDimensions: CGSize(width: 170.0, height: 74.0)) let video = ChatMessageItemVideoLayoutConstants(maxHorizontalHeight: 250.0, maxVerticalHeight: 360.0) diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index 6d055960f2..a8f3362b55 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -673,7 +673,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { var maxContentWidth = imageSize.width var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))? if let replyMarkup = replyMarkup { - let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, replyMarkup, item.message, maxContentWidth) + let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, item.message, maxContentWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } @@ -839,7 +839,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView { strongSelf.addSubnode(updatedShareButtonNode) updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside) } - let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) + let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, controllerInteraction: item.controllerInteraction, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account) let shareButtonFrame = CGRect(origin: CGPoint(x: baseShareButtonFrame.minX, y: baseShareButtonFrame.maxY - buttonSize.height), size: buttonSize) transition.updateFrame(node: updatedShareButtonNode, frame: shareButtonFrame) } else if let shareButtonNode = strongSelf.shareButtonNode { diff --git a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift b/submodules/TelegramUI/Sources/ChatReplyCountItem.swift index 90eba140c6..2bf23adc86 100644 --- a/submodules/TelegramUI/Sources/ChatReplyCountItem.swift +++ b/submodules/TelegramUI/Sources/ChatReplyCountItem.swift @@ -23,7 +23,7 @@ class ChatReplyCountItem: ListViewItem { self.isComments = isComments self.count = count self.presentationData = presentationData - self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, context: context) + self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context) self.controllerInteraction = controllerInteraction } diff --git a/submodules/TelegramUI/Sources/ChatUnreadItem.swift b/submodules/TelegramUI/Sources/ChatUnreadItem.swift index 23a2f37304..67bf4357a7 100644 --- a/submodules/TelegramUI/Sources/ChatUnreadItem.swift +++ b/submodules/TelegramUI/Sources/ChatUnreadItem.swift @@ -6,18 +6,21 @@ import Display import SwiftSignalKit import TelegramPresentationData import AccountContext +import WallpaperBackgroundNode private let titleFont = UIFont.systemFont(ofSize: 13.0) class ChatUnreadItem: ListViewItem { let index: MessageIndex let presentationData: ChatPresentationData + let controllerInteraction: ChatControllerInteraction let header: ChatMessageDateHeader - init(index: MessageIndex, presentationData: ChatPresentationData, context: AccountContext) { + init(index: MessageIndex, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, context: AccountContext) { self.index = index self.presentationData = presentationData - self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, context: context) + self.controllerInteraction = controllerInteraction + self.header = ChatMessageDateHeader(timestamp: index.timestamp, scheduled: false, presentationData: presentationData, controllerInteraction: controllerInteraction, context: context) } func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { @@ -61,6 +64,11 @@ class ChatUnreadItemNode: ListViewItemNode { let activateArea: AccessibilityAreaNode + private var wallpaperBackgroundNode: WallpaperBackgroundNode? + private var backgroundContent: WallpaperBubbleBackgroundNode? + + private var absolutePosition: (CGRect, CGSize)? + private var theme: ChatPresentationThemeData? private let layoutConstants = ChatMessageItemLayoutConstants.default @@ -101,7 +109,7 @@ class ChatUnreadItemNode: ListViewItemNode { } override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { - if let item = item as? ChatUnreadItem { + if let item = item as? ChatUnreadItem { let dateAtBottom = !chatItemsHaveCommonDateHeader(item, nextItem) let (layout, apply) = self.asyncLayout()(item, params, dateAtBottom) apply() @@ -142,11 +150,48 @@ class ChatUnreadItemNode: ListViewItemNode { strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize) strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - size.size.width) / 2.0), y: floorToScreenPixels((backgroundSize.height - size.size.height) / 2.0)), size: size.size) + + if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { + if strongSelf.backgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + backgroundContent.clipsToBounds = true + + strongSelf.backgroundContent = backgroundContent + strongSelf.insertSubnode(backgroundContent, at: 0) + } + } else { + strongSelf.backgroundContent?.removeFromSupernode() + strongSelf.backgroundContent = nil + } + + if let backgroundContent = strongSelf.backgroundContent { + strongSelf.backgroundNode.isHidden = true + backgroundContent.frame = strongSelf.backgroundNode.frame + if let (rect, containerSize) = strongSelf.absolutePosition { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } else { + strongSelf.backgroundNode.isHidden = false + } } }) } } + override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + super.updateAbsoluteRect(rect, within: containerSize) + + self.absolutePosition = (rect, containerSize) + if let backgroundContent = self.backgroundContent { + var backgroundFrame = backgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + override public func headers() -> [ListViewItemHeader]? { if let item = self.item { return [item.header] diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 963616b25c..3e5c19e099 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1355,7 +1355,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader { - return ChatMessageDateHeader(timestamp: timestamp, scheduled: false, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), context: context) + return ChatMessageDateHeader(timestamp: timestamp, scheduled: false, presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: theme, wallpaper: wallpaper), fontSize: fontSize, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameOrder, disableAnimations: false, largeEmoji: false, chatBubbleCorners: chatBubbleCorners, animatedEmojiScale: 1.0, isPreview: true), controllerInteraction: nil, context: context) } #if ENABLE_WALLET diff --git a/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift index 63d125ec96..adf92f3707 100644 --- a/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/MetalWallpaperBackgroundNode.swift @@ -291,6 +291,10 @@ final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode return false } + func hasExtraBubbleBackground() -> Bool { + return false + } + func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? { return nil } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 88a415a332..5f4b0af304 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -61,6 +61,8 @@ public protocol WallpaperBackgroundNode: ASDisplayNode { func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool func makeBubbleBackground(for type: WallpaperBubbleType) -> WallpaperBubbleBackgroundNode? + func hasExtraBubbleBackground() -> Bool + func makeDimmedNode() -> ASDisplayNode? } @@ -393,7 +395,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode if isInvertedGradient { switch self.bubbleType { case .free: - needsCleanBackground = false + self.contentNode.backgroundColor = bubbleTheme.chat.message.incoming.bubble.withWallpaper.fill[0] +// needsCleanBackground = false case .incoming, .outgoing: break } @@ -591,6 +594,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode private struct ValidPatternImage { let wallpaper: TelegramWallpaper + let invertPattern: Bool let generate: (TransformImageArguments) -> DrawingContext? } private var validPatternImage: ValidPatternImage? @@ -893,10 +897,14 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode var updated = true let brightness = UIColor.average(of: file.settings.colors.map(UIColor.init(rgb:))).hsb.b patternIsLight = brightness > 0.3 + + let intensity = CGFloat(file.settings.intensity ?? 50) / 100.0 + invertPattern = intensity < 0 + if let previousWallpaper = self.validPatternImage?.wallpaper { switch previousWallpaper { case let .file(previousFile): - if file.file.id == previousFile.file.id { + if file.file.id == previousFile.file.id && self.validPatternImage?.invertPattern == invertPattern { updated = false } default: @@ -908,8 +916,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode self.validPatternGeneratedImage = nil self.validPatternImage = nil - if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper { - self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, generate: cachedValidPatternImage.generate) + if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated.wallpaper == wallpaper && cachedValidPatternImage.generated.invertPattern == invertPattern { + self.validPatternImage = ValidPatternImage(wallpaper: cachedValidPatternImage.generated.wallpaper, invertPattern: invertPattern, generate: cachedValidPatternImage.generate) } else { func reference(for resource: EngineMediaResource, media: EngineMedia) -> MediaResourceReference { return .wallpaper(wallpaper: .slug(file.slug), resource: resource._asResource()) @@ -945,7 +953,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode return context }*/ - strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, generate: generator) + strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, invertPattern: invertPattern, generate: generator) strongSelf.validPatternGeneratedImage = nil if let size = strongSelf.validLayout { strongSelf.loadPatternForSizeIfNeeded(size: size, transition: .immediate) @@ -958,8 +966,6 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode })) } } - let intensity = CGFloat(file.settings.intensity ?? 50) / 100.0 - invertPattern = intensity < 0 default: self.updatePatternPresentation() } @@ -1165,6 +1171,19 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode return node } + func hasExtraBubbleBackground() -> Bool { + var isInvertedGradient = false + switch self.wallpaper { + case let .file(file): + if let intensity = file.settings.intensity, intensity < 0 { + isInvertedGradient = true + } + default: + break + } + return isInvertedGradient + } + func makeDimmedNode() -> ASDisplayNode? { if let gradientBackgroundNode = self.gradientBackgroundNode { return GradientBackgroundNode.CloneNode(parentNode: gradientBackgroundNode) @@ -1987,6 +2006,10 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun node.updateContents() return node } + + func hasExtraBubbleBackground() -> Bool { + return false + } func makeDimmedNode() -> ASDisplayNode? { return nil