Various improvements

This commit is contained in:
Ilya Laktyushin 2022-12-31 18:53:45 +04:00
parent 6a15dbb4bb
commit fabf9220a6
11 changed files with 189 additions and 118 deletions

View File

@ -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.";

View File

@ -2096,6 +2096,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
let previousSelectedMessages = Atomic<Set<EngineMessage.Id>?>(value: nil)
let previousExpandGlobalSearch = Atomic<Bool>(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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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",

View File

@ -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

View File

@ -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 {

View File

@ -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()))
}

View File

@ -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 {

View File

@ -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")

View File

@ -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
}
}
}