From fabf9220a61bc1d8c0ef73691897602b7fe53882 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 31 Dec 2022 18:53:45 +0400 Subject: [PATCH 1/3] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 2 +- .../Sources/ChatListSearchListPaneNode.swift | 8 +- .../Sources/Node/ChatListBadgeNode.swift | 2 +- .../DrawingUI/Sources/DrawingPenTool.swift | 2 +- submodules/GalleryUI/BUILD | 1 + .../ChatItemGalleryFooterContentNode.swift | 26 +++-- .../ChatVideoGalleryItemScrubberView.swift | 61 ++++++++-- .../Sources/Items/ChatImageGalleryItem.swift | 110 ++++++------------ .../Sources/MediaPlayerScrubbingNode.swift | 23 +++- .../Sources/ChatButtonKeyboardInputNode.swift | 1 - .../ChatHistoryNavigationButtonNode.swift | 71 ++++++++--- 11 files changed, 189 insertions(+), 118 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 9f52ff5c54..78f4bd1a59 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8032,7 +8032,7 @@ Sorry for the inconvenience."; "Login.Continue" = "Continue"; "Login.EnterCodeSMSTitle" = "Enter Code"; -"Login.EnterCodeSMSText" = "We've sent and SMS with an activation code to your phone **%@**."; +"Login.EnterCodeSMSText" = "We've sent an SMS with an activation code to your phone **%@**."; "Login.SendCodeAsSMS" = "Send the code as an SMS"; "Login.EnterCodeTelegramTitle" = "Enter Code"; "Login.EnterCodeTelegramText" = "We've sent the code to the **Telegram app** for %@ on your other device."; diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index c19d67c859..7e1ad8f506 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2096,6 +2096,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil) let previousSelectedMessages = Atomic?>(value: nil) + let previousExpandGlobalSearch = Atomic(value: false) let _ = (searchQuery |> deliverOnMainQueue).start(next: { [weak self, weak listInteraction, weak chatListInteraction] query in @@ -2109,10 +2110,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { self?.searchOptionsValue = options }) + self.searchDisposable.set((foundItems |> deliverOnMainQueue).start(next: { [weak self] foundItems in if let strongSelf = self { let previousSelectedMessageIds = previousSelectedMessages.swap(strongSelf.selectedMessages) + let previousExpandGlobalSearch = previousExpandGlobalSearch.swap(strongSelf.searchStateValue.expandGlobalSearch) var entriesAndFlags = foundItems?.0 @@ -2147,7 +2150,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { let previousEntries = previousSearchItems.swap(entriesAndFlags) let newEntries = entriesAndFlags ?? [] - let animated = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil) + let selectionChanged = (previousSelectedMessageIds == nil) != (strongSelf.selectedMessages == nil) + let expandGlobalSearchChanged = previousExpandGlobalSearch != strongSelf.searchStateValue.expandGlobalSearch + + let animated = selectionChanged || expandGlobalSearchChanged let firstTime = previousEntries == nil var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags != nil, isEmpty: !isSearching && (entriesAndFlags?.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, location: location, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture, location in interaction.peerContextAction?(message, node, rect, gesture, location) diff --git a/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift b/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift index e99291b98c..21dcc04e09 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListBadgeNode.swift @@ -120,7 +120,7 @@ final class ChatListBadgeNode: ASDisplayNode { strongSelf.isHiddenInternal = false if !strongSelf.disableBounce { if bounce { - strongSelf.layer.animateScale(from: 0.0001, to: 1.2, duration: 0.2, removeOnCompletion: false, completion: { [weak self] finished in + strongSelf.layer.animateScale(from: 0.0001, to: 1.2, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in if let strongSelf = self { strongSelf.layer.animateScale(from: 1.15, to: 1.0, duration: 0.12, removeOnCompletion: false) } diff --git a/submodules/DrawingUI/Sources/DrawingPenTool.swift b/submodules/DrawingUI/Sources/DrawingPenTool.swift index 85391730ba..cb7ab9b59b 100644 --- a/submodules/DrawingUI/Sources/DrawingPenTool.swift +++ b/submodules/DrawingUI/Sources/DrawingPenTool.swift @@ -216,7 +216,7 @@ final class PenTool: DrawingElement { context.scaleBy(x: 1.0 / parent.drawScale.width, y: 1.0 / parent.drawScale.height) element.drawSegments(in: context, from: parent.start, to: parent.segmentsCount) - if !element.isEraser || !element.isBlur { + if !element.isEraser && !element.isBlur { element.drawActiveSegments(in: context, strokeWidth: !parent.isActiveDrying ? element.renderLineWidth * parent.dryingFactor : nil) } else { element.drawActiveSegments(in: context, strokeWidth: nil) diff --git a/submodules/GalleryUI/BUILD b/submodules/GalleryUI/BUILD index 180d1e9a21..ba2b6e0711 100644 --- a/submodules/GalleryUI/BUILD +++ b/submodules/GalleryUI/BUILD @@ -40,6 +40,7 @@ swift_library( "//submodules/UndoUI:UndoUI", "//submodules/InvisibleInkDustNode:InvisibleInkDustNode", "//submodules/TranslateUI:TranslateUI", + "//submodules/ShimmerEffect:ShimmerEffect", "//submodules/Utils/RangeSet:RangeSet", "//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities", "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 0651503435..3359f3e098 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -188,8 +188,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll case .info: self.authorNameNode.isHidden = false self.dateNode.isHidden = false - self.backwardButton.isHidden = true - self.forwardButton.isHidden = true + self.hasSeekControls = false self.playbackControlButton.isHidden = true self.statusButtonNode.isHidden = true self.statusNode.isHidden = true @@ -197,8 +196,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.currentIsPaused = true self.authorNameNode.isHidden = true self.dateNode.isHidden = true - self.backwardButton.isHidden = !seekable - self.forwardButton.isHidden = !seekable + self.hasSeekControls = seekable if status == .Local { self.playbackControlButton.isHidden = false self.playPauseIconNode.enqueueState(.play, animated: true) @@ -226,8 +224,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.currentIsPaused = paused self.authorNameNode.isHidden = true self.dateNode.isHidden = true - self.backwardButton.isHidden = !seekable - self.forwardButton.isHidden = !seekable + self.hasSeekControls = seekable self.playbackControlButton.isHidden = false let icon: PlayPauseIconNodeState @@ -244,6 +241,17 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } } + var hasSeekControls: Bool = false { + didSet { + let alpha = self.hasSeekControls ? 1.0 : 0.0 + let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut) + transition.updateAlpha(node: self.backwardButton, alpha: alpha) + transition.updateAlpha(node: self.forwardButton, alpha: alpha) + self.backwardButton.isUserInteractionEnabled = self.hasSeekControls + self.forwardButton.isUserInteractionEnabled = self.hasSeekControls + } + } + private var scrubbingHandleRelativePosition: CGFloat = 0.0 private var scrubbingVisualTimestamp: Double? @@ -339,11 +347,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.dateNode.displaysAsynchronously = false self.backwardButton = PlaybackButtonNode() - self.backwardButton.isHidden = true + self.backwardButton.alpha = 0.0 + self.backwardButton.isUserInteractionEnabled = false self.backwardButton.backgroundIconNode.image = backwardImage self.forwardButton = PlaybackButtonNode() - self.forwardButton.isHidden = true + self.forwardButton.alpha = 0.0 + self.forwardButton.isUserInteractionEnabled = false self.forwardButton.forward = true self.forwardButton.backgroundIconNode.image = forwardImage diff --git a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift index 2101e55219..bee6431d99 100644 --- a/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift +++ b/submodules/GalleryUI/Sources/ChatVideoGalleryItemScrubberView.swift @@ -8,6 +8,7 @@ import Display import UniversalMediaPlayer import TelegramPresentationData import RangeSet +import ShimmerEffect private let textFont = Font.with(size: 13.0, design: .regular, weight: .regular, traits: [.monospacedNumbers]) @@ -22,6 +23,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { private let rightTimestampNode: MediaPlayerTimeTextNode private let infoNode: ASTextNode private let scrubberNode: MediaPlayerScrubbingNode + private let shimmerEffectNode: ShimmerEffectForegroundNode private let hapticFeedback = HapticFeedback() @@ -31,6 +33,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { private var fetchStatusDisposable = MetaDisposable() private var scrubbingDisposable = MetaDisposable() private var chapterDisposable = MetaDisposable() + private var loadingDisposable = MetaDisposable() private var leftTimestampNodePushed = false private var rightTimestampNodePushed = false @@ -66,6 +69,7 @@ final class ChatVideoGalleryItemScrubberView: UIView { init(chapters: [MediaPlayerScrubbingChapter]) { self.chapters = chapters self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 5.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: scrubberBackgroundColor, foregroundColor: scrubberForegroundColor, bufferingColor: scrubberBufferingColor, chapters: chapters)) + self.shimmerEffectNode = ShimmerEffectForegroundNode() self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white) self.rightTimestampNode = MediaPlayerTimeTextNode(textColor: .white) @@ -123,19 +127,20 @@ final class ChatVideoGalleryItemScrubberView: UIView { deinit { self.scrubbingDisposable.dispose() self.fetchStatusDisposable.dispose() + self.chapterDisposable.dispose() + self.loadingDisposable.dispose() } - var collapsed: Bool? + var isLoading = false + var isCollapsed: Bool? func setCollapsed(_ collapsed: Bool, animated: Bool) { - guard self.collapsed != collapsed else { + guard self.isCollapsed != collapsed else { return } - self.collapsed = collapsed + self.isCollapsed = collapsed - let alpha: CGFloat = collapsed ? 0.0 : 1.0 - self.leftTimestampNode.alpha = alpha - self.rightTimestampNode.alpha = alpha + self.updateTimestampsVisibility(animated: animated) self.updateScrubberVisibility(animated: animated) if let (size, _, _) = self.containerLayout { @@ -143,12 +148,19 @@ final class ChatVideoGalleryItemScrubberView: UIView { } } + func updateTimestampsVisibility(animated: Bool) { + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate + let alpha: CGFloat = self.isCollapsed == true || self.isLoading ? 0.0 : 1.0 + transition.updateAlpha(node: self.leftTimestampNode, alpha: alpha) + transition.updateAlpha(node: self.rightTimestampNode, alpha: alpha) + } + private func updateScrubberVisibility(animated: Bool) { - var collapsed = self.collapsed + var collapsed = self.isCollapsed var alpha: CGFloat = 1.0 if let playbackStatus = self.playbackStatus, playbackStatus.duration <= 30.0 { } else { - alpha = self.collapsed == true ? 0.0 : 1.0 + alpha = self.isCollapsed == true ? 0.0 : 1.0 collapsed = false } self.scrubberNode.setCollapsed(collapsed == true, animated: animated) @@ -174,6 +186,30 @@ final class ChatVideoGalleryItemScrubberView: UIView { self.rightTimestampNode.status = mappedStatus if let mappedStatus = mappedStatus { + self.loadingDisposable.set((mappedStatus + |> deliverOnMainQueue).start(next: { [weak self] status in + if let strongSelf = self { + if status.duration < 1.0 { + strongSelf.isLoading = true + strongSelf.updateTimestampsVisibility(animated: true) + + if strongSelf.shimmerEffectNode.supernode == nil { + strongSelf.scrubberNode.containerNode.addSubnode(strongSelf.shimmerEffectNode) + } + } else { + strongSelf.isLoading = false + strongSelf.updateTimestampsVisibility(animated: true) + if strongSelf.shimmerEffectNode.supernode != nil { + strongSelf.shimmerEffectNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.shimmerEffectNode.removeFromSupernode() + } + }) + } + } + } + })) + self.chapterDisposable.set((mappedStatus |> deliverOnMainQueue).start(next: { [weak self] status in if let strongSelf = self, status.duration > 1.0, strongSelf.chapters.count > 0 { @@ -320,9 +356,14 @@ final class ChatVideoGalleryItemScrubberView: UIView { let infoSize = self.infoNode.measure(infoConstrainedSize) self.infoNode.bounds = CGRect(origin: CGPoint(), size: infoSize) transition.updatePosition(node: self.infoNode, position: CGPoint(x: size.width / 2.0, y: infoOffset + infoSize.height / 2.0)) - self.infoNode.alpha = size.width < size.height && self.collapsed == false ? 1.0 : 0.0 + self.infoNode.alpha = size.width < size.height && self.isCollapsed == false ? 1.0 : 0.0 - self.scrubberNode.frame = CGRect(origin: CGPoint(x: scrubberInset, y: 6.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberInset * 2.0, height: scrubberHeight)) + let scrubberFrame = CGRect(origin: CGPoint(x: scrubberInset, y: 6.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberInset * 2.0, height: scrubberHeight)) + self.scrubberNode.frame = scrubberFrame + self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: scrubberFrame.size), within: scrubberFrame.size) + self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.75), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil) + self.shimmerEffectNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 4.0), size: CGSize(width: scrubberFrame.size.width, height: 5.0)) + self.shimmerEffectNode.cornerRadius = 2.5 } override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index d177481c66..80c40276b4 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -20,6 +20,7 @@ import TranslateUI import ShareController import UndoUI import ContextUI +import SaveToCameraRoll enum ChatMediaGalleryThumbnail: Equatable { case image(ImageMediaReference) @@ -485,7 +486,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: message.id.peerId)) - |> deliverOnMainQueue).start(next: { [weak self] peer in + |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self, let peer = peer else { return } @@ -501,82 +502,34 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { f(.default) }) }))) + + if !message.isCopyProtected(), let media = self.contextAndMedia?.1 { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Gallery_SaveImage, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { [weak self] _, f in + f(.default) + + let _ = (SaveToCameraRoll.saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: media) + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let strongSelf = self else { + return + } + guard let controller = strongSelf.galleryController() else { + return + } + controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .mediaSaved(text: strongSelf.presentationData.strings.Gallery_ImageSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + }) + }))) + } } + + if self.canDelete() { + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak self] _, f in + f(.default) -// if #available(iOS 11.0, *) { -// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in -// f(.default) -// guard let strongSelf = self else { -// return -// } -// strongSelf.beginAirPlaySetup() -// }))) -// } - -// if let (message, _, _) = strongSelf.contentInfo() { -// for media in message.media { -// if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { -// let url = content.url -// -// let item = OpenInItem.url(url: url) -// let openText = strongSelf.presentationData.strings.Conversation_FileOpenIn -// items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in -// f(.default) -// -// if let strongSelf = self, let controller = strongSelf.galleryController() { -// var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } -// if !presentationData.theme.overallDarkAppearance { -// presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) -// } -// let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in -// if let strongSelf = self { -// strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {}) -// } -// }) -// controller.present(actionSheet, in: .window(.root)) -// } -// }))) -// break -// } -// } -// } - -// if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() { -// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in -// f(.default) -// -// if let strongSelf = self { -// switch strongSelf.fetchStatus { -// case .Local: -// let _ = (SaveToCameraRoll.saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, mediaReference: .message(message: MessageReference(message), media: file)) -// |> deliverOnMainQueue).start(completed: { -// guard let strongSelf = self else { -// return -// } -// guard let controller = strongSelf.galleryController() else { -// return -// } -// controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .mediaSaved(text: strongSelf.presentationData.strings.Gallery_VideoSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) -// }) -// default: -// guard let controller = strongSelf.galleryController() else { -// return -// } -// controller.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Gallery_WaitForVideoDownoad, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: { -// })]), in: .window(.root)) -// } -// } -// }))) -// } -// if strongSelf.canDelete() { -// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in -// f(.default) -// -// if let strongSelf = self { -// strongSelf.footerContentNode.deleteButtonPressed() -// } -// }))) -// } + if let strongSelf = self { + strongSelf.footerContentNode.deleteButtonPressed() + } + }))) + } return .single(items) } @@ -676,6 +629,13 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.zoomableContent = (largestSize.cgSize, self.imageNode) self.setupStatus(resource: fileReference.media.resource) + + var barButtonItems: [UIBarButtonItem] = [] + if self.message != nil { + let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)! + barButtonItems.append(moreMenuItem) + } + self._rightBarButtonItems.set(.single(barButtonItems)) } else { self._ready.set(.single(Void())) } diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index cb60ee6800..e95bdd9d0b 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -350,6 +350,15 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { } } + public var containerNode: ASDisplayNode { + switch self.contentNodes { + case let .standard(node): + return node.containerNode + case let .custom(node): + return node.backgroundNode + } + } + private var _statusValue: MediaPlayerStatus? private var statusValue: MediaPlayerStatus? { get { @@ -947,7 +956,10 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { if let handleNodeContainer = node.handleNodeContainer { handleNodeContainer.bounds = bounds.offsetBy(dx: -floorToScreenPixels(bounds.size.width * progress), dy: 0.0) - handleNodeContainer.isHidden = false + if handleNodeContainer.alpha.isZero { + handleNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + handleNodeContainer.alpha = 1.0 } } else if let statusValue = self.statusValue { var actualTimestamp: Double @@ -975,15 +987,18 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { if let handleNodeContainer = node.handleNodeContainer { handleNodeContainer.bounds = bounds.offsetBy(dx: -floorToScreenPixels(bounds.size.width * progress), dy: 0.0) - handleNodeContainer.isHidden = false + if handleNodeContainer.alpha.isZero { + handleNodeContainer.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + handleNodeContainer.alpha = 1.0 } } else { - node.handleNodeContainer?.isHidden = true node.foregroundNode.frame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: 0.0, height: backgroundFrame.size.height)) + node.handleNodeContainer?.alpha = 0.0 } } else { node.foregroundNode.frame = CGRect(origin: backgroundFrame.origin, size: CGSize(width: 0.0, height: backgroundFrame.size.height)) - node.handleNodeContainer?.isHidden = true + node.handleNodeContainer?.alpha = 0.0 } case let .custom(node): if let handleNodeContainer = node.handleNodeContainer { diff --git a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift index 055d139c32..b01d8b2fe5 100644 --- a/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatButtonKeyboardInputNode.swift @@ -78,7 +78,6 @@ private final class ChatButtonKeyboardInputButtonNode: HighlightTrackingButtonNo if let strongSelf = self { if highlighted, !strongSelf.bounds.width.isZero { let scale = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width - strongSelf.layer.animateScale(from: 1.0, to: scale, duration: 0.15, removeOnCompletion: false) strongSelf.backgroundContainerNode.layer.removeAnimation(forKey: "opacity") diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift index 266645020b..fd6d9a92da 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit import Display import TelegramPresentationData import WallpaperBackgroundNode +import AnimatedCountLabelNode private let badgeFont = Font.regular(13.0) @@ -20,7 +21,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { private var backgroundContent: WallpaperBubbleBackgroundNode? private let imageNode: ASImageNode private let badgeBackgroundNode: ASImageNode - private let badgeTextNode: ASTextNode + private let badgeTextNode: ImmediateAnimatedCountLabelNode var tapped: (() -> Void)? { didSet { @@ -67,15 +68,15 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.imageNode.isLayerBacked = true self.badgeBackgroundNode = ASImageNode() - self.badgeBackgroundNode.isLayerBacked = true self.badgeBackgroundNode.displayWithoutProcessing = true self.badgeBackgroundNode.displaysAsynchronously = false self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme) + self.badgeBackgroundNode.alpha = 0.0 - self.badgeTextNode = ASTextNode() - self.badgeTextNode.maximumNumberOfLines = 1 + self.badgeTextNode = ImmediateAnimatedCountLabelNode() self.badgeTextNode.isUserInteractionEnabled = false self.badgeTextNode.displaysAsynchronously = false + self.badgeTextNode.reverseAnimationDirection = true super.init() @@ -99,7 +100,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.imageNode.frame = CGRect(origin: CGPoint(), size: size) self.buttonNode.addSubnode(self.badgeBackgroundNode) - self.buttonNode.addSubnode(self.badgeTextNode) + self.badgeBackgroundNode.addSubnode(self.badgeTextNode) self.frame = CGRect(origin: CGPoint(), size: size) } @@ -119,10 +120,15 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { } self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme) - if let string = self.badgeTextNode.attributedText?.string { - self.badgeTextNode.attributedText = NSAttributedString(string: string, font: badgeFont, textColor: theme.chat.historyNavigation.badgeTextColor) - self.badgeTextNode.redrawIfPossible() + var segments: [AnimatedCountLabelNode.Segment] = [] + if let value = Int(self.badge) { + self.currentValue = value + segments.append(.number(value, NSAttributedString(string: self.badge, font: badgeFont, textColor: self.theme.chat.historyNavigation.badgeTextColor))) + } else { + self.currentValue = 0 + segments.append(.text(100, NSAttributedString(string: self.badge, font: badgeFont, textColor: self.theme.chat.historyNavigation.badgeTextColor))) } + self.badgeTextNode.segments = segments } if backgroundNode.hasExtraBubbleBackground() { @@ -160,20 +166,53 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { } } + private var currentValue: Int = 0 private func layoutBadge() { if !self.badge.isEmpty { - self.badgeTextNode.attributedText = NSAttributedString(string: self.badge, font: badgeFont, textColor: self.theme.chat.historyNavigation.badgeTextColor) - self.badgeBackgroundNode.isHidden = false - self.badgeTextNode.isHidden = false + let previousValue = self.currentValue + var segments: [AnimatedCountLabelNode.Segment] = [] + if let value = Int(self.badge) { + self.currentValue = value + segments.append(.number(value, NSAttributedString(string: self.badge, font: badgeFont, textColor: self.theme.chat.historyNavigation.badgeTextColor))) + } else { + self.currentValue = 0 + segments.append(.text(100, NSAttributedString(string: self.badge, font: badgeFont, textColor: self.theme.chat.historyNavigation.badgeTextColor))) + } + self.badgeTextNode.segments = segments - let badgeSize = self.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0)) - let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) + let badgeSize = self.badgeTextNode.updateLayout(size: CGSize(width: 200.0, height: 100.0), animated: true) + let backgroundSize = CGSize(width: self.badge.count == 1 ? 18.0 : max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) let backgroundFrame = CGRect(origin: CGPoint(x: floor((38.0 - backgroundSize.width) / 2.0), y: -9.0), size: backgroundSize) self.badgeBackgroundNode.frame = backgroundFrame - self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: -8.0), size: badgeSize) + self.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.width - badgeSize.width) / 2.0), y: 1.0), size: badgeSize) + + if self.badgeBackgroundNode.alpha < 1.0 { + self.badgeBackgroundNode.alpha = 1.0 + + self.badgeBackgroundNode.layer.animateScale(from: 0.01, to: 1.2, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in + if let strongSelf = self { + strongSelf.badgeBackgroundNode.layer.animateScale(from: 1.15, to: 1.0, duration: 0.12, removeOnCompletion: false, completion: { _ in + strongSelf.badgeBackgroundNode.layer.removeAllAnimations() + }) + } + }) + self.badgeBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } else if previousValue != self.currentValue { + self.badgeBackgroundNode.layer.animateScale(from: 1.0, to: 1.2, duration: 0.12, removeOnCompletion: false, completion: { [weak self] finished in + if let strongSelf = self { + strongSelf.badgeBackgroundNode.layer.animateScale(from: 1.2, to: 1.0, duration: 0.12, removeOnCompletion: false, completion: { _ in + strongSelf.badgeBackgroundNode.layer.removeAllAnimations() + }) + } + }) + } } else { - self.badgeBackgroundNode.isHidden = true - self.badgeTextNode.isHidden = true + self.currentValue = 0 + if self.badgeBackgroundNode.alpha > 0.0 { + self.badgeBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + self.badgeBackgroundNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2) + } + self.badgeBackgroundNode.alpha = 0.0 } } } From 68a13a4c07926ba584b46e70c4d68cd273383e75 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 1 Jan 2023 02:08:27 +0400 Subject: [PATCH 2/3] Various fixes --- .../ContextControllerExtractedPresentationNode.swift | 10 ++++++++-- submodules/DrawingUI/Sources/DrawingPenTool.swift | 8 ++++++-- submodules/DrawingUI/Sources/DrawingScreen.swift | 2 +- .../Sources/TGMediaPickerGalleryInterfaceView.m | 4 +++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 40b1117aac..51f519c8f5 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -769,8 +769,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if let reactionContextNode = self.reactionContextNode { additionalVisibleOffsetY += reactionContextNode.visibleExtensionDistance } - if case .reference = self.source { - if actionsFrame.maxY > layout.size.height { + if case let .reference(source) = self.source { + var actionsFrameIsOutOfScreen = false + if let contentAreaInScreenSpace = source.transitionInfo()?.contentAreaInScreenSpace { + if !contentAreaInScreenSpace.contains(actionsFrame) { + actionsFrameIsOutOfScreen = true + } + } + if actionsFrame.maxY > layout.size.height || actionsFrameIsOutOfScreen { actionsFrame.origin.y = contentRect.minY - actionsSize.height - contentActionsSpacing } } diff --git a/submodules/DrawingUI/Sources/DrawingPenTool.swift b/submodules/DrawingUI/Sources/DrawingPenTool.swift index cb7ab9b59b..4569107717 100644 --- a/submodules/DrawingUI/Sources/DrawingPenTool.swift +++ b/submodules/DrawingUI/Sources/DrawingPenTool.swift @@ -237,7 +237,6 @@ final class PenTool: DrawingElement { var renderArrowLineWidth: CGFloat let isEraser: Bool - let isBlur: Bool var arrowStart: CGPoint? @@ -277,7 +276,12 @@ final class PenTool: DrawingElement { } var bounds: CGRect { - return normalizeDrawingRect(boundingRect(from: 0, to: self.segments.count).insetBy(dx: -20.0, dy: -20.0), drawingSize: self.drawingSize) + let segmentsBounds = boundingRect(from: 0, to: self.segments.count).insetBy(dx: -20.0, dy: -20.0) + var combinedBounds = segmentsBounds + if self.hasArrow, let arrowLeftPath, let arrowRightPath { + combinedBounds = combinedBounds.union(arrowLeftPath.bounds).union(arrowRightPath.bounds).insetBy(dx: -20.0, dy: -20.0) + } + return normalizeDrawingRect(combinedBounds, drawingSize: self.drawingSize) } required init(drawingSize: CGSize, color: DrawingColor, lineWidth: CGFloat, hasArrow: Bool, isEraser: Bool, isBlur: Bool, blurredImage: UIImage?) { diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 7591996476..b70a9509ea 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -359,7 +359,7 @@ private final class ReferenceContentSource: ContextReferenceContentSource { } func transitionInfo() -> ContextControllerReferenceViewInfo? { - return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: self.contentArea, customPosition: customPosition) + return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: self.contentArea, customPosition: self.customPosition) } } diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m index 17f0b3a49b..785952c7c6 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryInterfaceView.m @@ -363,7 +363,9 @@ _landscapeToolbarView.cancelPressed = toolbarCancelPressed; _landscapeToolbarView.donePressed = toolbarDonePressed; _landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed; - [_wrapperView addSubview:_landscapeToolbarView]; + + if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) + [_wrapperView addSubview:_landscapeToolbarView]; } return self; } From 2c7f754b2fe1de67ae676a66917742591ea619b0 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 1 Jan 2023 17:02:05 +0400 Subject: [PATCH 3/3] Various fixes --- .../GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 3359f3e098..6e6f5082cf 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -1013,8 +1013,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.fullscreenButton.alpha = 1.0 self.actionButton.alpha = 1.0 self.editButton.alpha = 1.0 - self.backwardButton.alpha = 1.0 - self.forwardButton.alpha = 1.0 + self.backwardButton.alpha = self.hasSeekControls ? 1.0 : 0.0 + self.forwardButton.alpha = self.hasSeekControls ? 1.0 : 0.0 self.statusNode.alpha = 1.0 self.playbackControlButton.alpha = 1.0 self.scrollWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)