Various improvements

This commit is contained in:
Ilya Laktyushin 2022-10-13 19:49:15 +03:00
parent 35e9b2caab
commit fe7dcc6866
14 changed files with 594 additions and 43 deletions

View File

@ -226,8 +226,8 @@ private let archiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_ar
private let unarchiveIcon = ItemListRevealOptionIcon.animation(animation: "anim_unarchive", scale: 0.642, offset: -9.0, replaceColors: [0xa9a9ad], flip: false)
private let hideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: 2.0, replaceColors: [0xbdbdc2], flip: false)
private let unhideIcon = ItemListRevealOptionIcon.animation(animation: "anim_hide", scale: 1.0, offset: -20.0, replaceColors: [0xbdbdc2], flip: true)
private let startIcon = ItemListRevealOptionIcon.animation(animation: "anim_pin", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false)
private let closeIcon = ItemListRevealOptionIcon.animation(animation: "anim_pin", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false)
private let startIcon = ItemListRevealOptionIcon.animation(animation: "anim_play", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false)
private let closeIcon = ItemListRevealOptionIcon.animation(animation: "anim_pause", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false)
private enum RevealOptionKey: Int32 {
case pin
@ -439,6 +439,7 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
self.playIcon.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
}
var isRound = false
var dimensions = CGSize(width: 100.0, height: 100.0)
if case let .image(image) = self.media {
self.playIcon.isHidden = true
@ -451,6 +452,9 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
}
}
} else if case let .file(file) = self.media {
if file.isInstantVideo {
isRound = true
}
if file.isAnimated {
self.playIcon.isHidden = true
} else {
@ -468,7 +472,7 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
let makeLayout = self.imageNode.asyncLayout()
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: isRound ? size.width / 2.0 : 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
apply()
}
}
@ -1400,7 +1404,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
break inner
} else if let file = media as? TelegramMediaFile {
if file.isVideo, !file.isInstantVideo && !file.isVideoSticker, let _ = file.dimensions {
if file.isVideo, !file.isVideoSticker, let _ = file.dimensions {
let fitSize = contentImageSize
contentImageSpecs.append((message, .file(file), fitSize))
}

View File

@ -214,21 +214,25 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode {
let titleText: String
let subtitleText: String
let subtitleColor: UIColor
if let username = item.username {
titleText = "@\(username.username)"
if username.flags.contains(.isEditable) || username.flags.contains(.isActive) {
subtitleText = item.presentationData.strings.Group_Setup_LinkActive
subtitleColor = item.presentationData.theme.list.itemAccentColor
} else {
subtitleText = item.presentationData.strings.Group_Setup_LinkInactive
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
}
} else {
titleText = " "
subtitleText = " "
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
}
let titleAttributedString = NSAttributedString(string: titleText, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
let subtitleAttributedString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
let subtitleAttributedString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: subtitleColor)
let reorderControlSizeAndApply = reorderControlLayout(item.presentationData.theme)
let reorderInset: CGFloat = reorderControlSizeAndApply.0

View File

@ -174,7 +174,7 @@ public final class ShimmerEffectForegroundView: UIView {
}
}
final class ShimmerEffectForegroundNode: ASDisplayNode {
public final class ShimmerEffectForegroundNode: ASDisplayNode {
private var currentBackgroundColor: UIColor?
private var currentForegroundColor: UIColor?
private var currentHorizontal: Bool?
@ -187,7 +187,7 @@ final class ShimmerEffectForegroundNode: ASDisplayNode {
private var globalTimeOffset = true
private var duration: Double?
override init() {
public override init() {
self.imageNodeContainer = ASDisplayNode()
self.imageNodeContainer.isLayerBacked = true
@ -206,21 +206,21 @@ final class ShimmerEffectForegroundNode: ASDisplayNode {
self.addSubnode(self.imageNodeContainer)
}
override func didEnterHierarchy() {
public override func didEnterHierarchy() {
super.didEnterHierarchy()
self.isCurrentlyInHierarchy = true
self.updateAnimation()
}
override func didExitHierarchy() {
public override func didExitHierarchy() {
super.didExitHierarchy()
self.isCurrentlyInHierarchy = false
self.updateAnimation()
}
func update(backgroundColor: UIColor, foregroundColor: UIColor, horizontal: Bool, effectSize: CGFloat?, globalTimeOffset: Bool, duration: Double?) {
public func update(backgroundColor: UIColor, foregroundColor: UIColor, horizontal: Bool, effectSize: CGFloat?, globalTimeOffset: Bool, duration: Double?) {
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), self.currentHorizontal == horizontal {
return
}
@ -274,7 +274,7 @@ final class ShimmerEffectForegroundNode: ASDisplayNode {
self.updateAnimation()
}
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
return
}

View File

@ -0,0 +1 @@
{"v":"5.9.6","fr":60,"ip":0,"op":30,"w":228,"h":228,"nm":"pause","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"pause Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[114,91,0],"ix":2,"l":2},"a":{"a":0,"k":[114,91,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[110,110,100]},{"t":20,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.761,0],[0,-2.761],[0,0],[-2.761,0],[0,2.761],[0,0]],"o":[[-2.761,0],[0,0],[0,2.761],[2.761,0],[0,0],[0,-2.761]],"v":[[12,-20],[7,-15],[7,15],[12,20],[17,15],[17,-15]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-2.761,0],[0,-2.761],[0,0],[2.761,0],[0,2.761]],"o":[[0,-2.761],[2.761,0],[0,0],[0,2.761],[-2.761,0],[0,0]],"v":[[-17,-15],[-12,-20],[-7,-15],[-7,15],[-12,20],[-17,15]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.72549021244,0.72549021244,0.733333349228,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[114,91],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-23.196],[23.196,0],[0,23.196],[-23.196,0]],"o":[[0,23.196],[-23.196,0],[0,-23.196],[23.196,0]],"v":[[42,0],[0,42],[-42,0],[0,-42]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[114,91],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"ct":1,"bm":0}],"markers":[]}

View File

@ -0,0 +1 @@
{"v":"5.9.6","fr":60,"ip":0,"op":30,"w":228,"h":228,"nm":"play","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"play Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[114,89.499,0],"ix":2,"l":2},"a":{"a":0,"k":[114,89.499,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[110,110,100]},{"t":20,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-2.367],[0,0],[-1.997,1.271],[0,0],[1.852,1.179]],"o":[[-1.997,-1.271],[0,0],[0,2.367],[0,0],[1.852,-1.179],[0,0]],"v":[[-7.389,-18.066],[-12,-15.535],[-12,15.535],[-7.389,18.066],[17.023,2.531],[17.023,-2.531]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.78823530674,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[114,91],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-23.196,0],[0,23.196],[23.196,0],[0,-23.196]],"o":[[23.196,0],[0,-23.196],[-23.196,0],[0,23.196]],"v":[[0,42],[42,0],[0,-42],[-42,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[114,91],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":30,"st":0,"ct":1,"bm":0}],"markers":[]}

View File

@ -92,6 +92,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
var blurredHistoryNode: ASImageNode?
let historyNodeContainer: ASDisplayNode
let loadingNode: ChatLoadingNode
private var loadingPlaceholderNode: ChatLoadingPlaceholderNode
private var emptyNode: ChatEmptyNode?
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
private var didDisplayEmptyGreeting = false
@ -220,17 +222,36 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
private var isLoadingValue: Bool = false
private func updateIsLoading(isLoading: Bool, animated: Bool) {
loadingPlaceholderNode.isHidden = true
if isLoading != self.isLoadingValue {
self.isLoadingValue = isLoading
if isLoading {
self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, belowSubnode: self.historyNodeContainer)
// self.historyNodeContainer.supernode?.insertSubnode(self.loadingNode, belowSubnode: self.historyNodeContainer)
self.loadingNode.isHidden = false
self.loadingNode.layer.removeAllAnimations()
self.loadingNode.alpha = 1.0
if animated {
self.loadingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
self.loadingPlaceholderNode.alpha = 1.0
self.loadingPlaceholderNode.isHidden = false
self.historyNode.alpha = 0.0
} else {
self.loadingPlaceholderNode.alpha = 0.0
self.loadingPlaceholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak self] completed in
if let strongSelf = self {
strongSelf.loadingPlaceholderNode.layer.removeAllAnimations()
if completed {
strongSelf.loadingPlaceholderNode.isHidden = true
}
}
})
self.loadingPlaceholderNode.animateOut(self.historyNode)
self.historyNode.alpha = 1.0
self.historyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.loadingNode.alpha = 0.0
if animated {
self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
@ -393,6 +414,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
})
self.loadingNode = ChatLoadingNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners)
self.loadingPlaceholderNode = ChatLoadingPlaceholderNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners, backgroundNode: self.backgroundNode, hasAvatar: chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser)
self.inputPanelContainerNode = ChatInputPanelContainer()
self.inputPanelOverlayNode = SparseNode()
@ -468,6 +491,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
assert(Queue.mainQueue().isCurrent())
// self.updateIsLoading(isLoading: true, animated: false)
//
// Queue.mainQueue().after(1.0) {
// self.updateIsLoading(isLoading: false, animated: true)
// }
self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in
if let strongSelf = self {
let wasLoading = strongSelf.isLoadingValue
@ -532,6 +561,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.addSubnode(self.contentContainerNode)
self.contentContainerNode.addSubnode(self.backgroundNode)
self.contentContainerNode.addSubnode(self.loadingPlaceholderNode)
self.contentContainerNode.addSubnode(self.historyNodeContainer)
if let navigationBar = self.navigationBar {
@ -1387,6 +1417,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
//transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds)
transition.updateFrame(node: self.loadingNode, frame: contentBounds)
transition.updateFrame(node: self.loadingPlaceholderNode, frame: contentBounds)
if let restrictedNode = self.restrictedNode {
transition.updateFrame(node: restrictedNode, frame: contentBounds)
@ -1544,6 +1575,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.visibleAreaInset = visibleAreaInset
self.loadingNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, transition: transition)
self.loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, transition: transition)
self.loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: transition)
if let containerNode = self.containerNode {
contentBottomInset += 8.0
let containerNodeFrame = CGRect(origin: CGPoint(x: wrappingInsets.left, y: wrappingInsets.top), size: CGSize(width: contentBounds.size.width, height: contentBounds.size.height - containerInsets.bottom - inputPanelsHeight - 8.0))

View File

@ -5,6 +5,8 @@ import Display
import TelegramCore
import TelegramPresentationData
import ActivityIndicator
import WallpaperBackgroundNode
import ShimmerEffect
final class ChatLoadingNode: ASDisplayNode {
private let backgroundNode: NavigationBackgroundNode
@ -44,3 +46,283 @@ final class ChatLoadingNode: ASDisplayNode {
}
}
private let avatarSize = CGSize(width: 38.0, height: 38.0)
final class ChatLoadingPlaceholderMessageContainer {
let avatarNode: ASImageNode?
let avatarBorderNode: ASImageNode?
let bubbleNode: ASImageNode
let bubbleBorderNode: ASImageNode
var parentView: UIView? {
return self.bubbleNode.supernode?.view
}
var frame: CGRect {
return self.bubbleNode.frame
}
init(hasAvatar: Bool, bubbleImage: UIImage?, bubbleBorderImage: UIImage?) {
if hasAvatar {
self.avatarNode = ASImageNode()
self.avatarNode?.displaysAsynchronously = false
self.avatarNode?.image = generateFilledCircleImage(diameter: avatarSize.width, color: .white)
self.avatarBorderNode = ASImageNode()
self.avatarBorderNode?.displaysAsynchronously = false
self.avatarBorderNode?.image = generateCircleImage(diameter: avatarSize.width, lineWidth: 1.0 - UIScreenPixel, color: .red)
} else {
self.avatarNode = nil
self.avatarBorderNode = nil
}
self.bubbleNode = ASImageNode()
self.bubbleNode.displaysAsynchronously = false
self.bubbleNode.image = bubbleImage
self.bubbleBorderNode = ASImageNode()
self.bubbleBorderNode.displaysAsynchronously = false
self.bubbleBorderNode.image = bubbleBorderImage
}
func setup(maskNode: ASDisplayNode, borderMaskNode: ASDisplayNode) {
if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
maskNode.addSubnode(avatarNode)
borderMaskNode.addSubnode(avatarBorderNode)
}
maskNode.addSubnode(self.bubbleNode)
borderMaskNode.addSubnode(self.bubbleBorderNode)
}
func animateWith(_ listItemNode: ListViewItemNode, transition: ContainedViewLayoutTransition) {
if let bubbleItemNode = listItemNode as? ChatMessageBubbleItemNode {
bubbleItemNode.animateFromLoadingPlaceholder(messageContainer: self, transition: transition)
} else if let stickerItemNode = listItemNode as? ChatMessageAnimatedStickerItemNode {
stickerItemNode.animateFromLoadingPlaceholder(messageContainer: self, transition: transition)
}
}
func animateBackgroundFrame(to frame: CGRect, transition: ContainedViewLayoutTransition) {
let targetFrame = CGRect(origin: CGPoint(x: self.bubbleNode.frame.minX, y: frame.minY), size: frame.size)
transition.updateFrame(node: self.bubbleNode, frame: targetFrame)
transition.updateFrame(node: self.bubbleBorderNode, frame: targetFrame)
if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
let avatarFrame = CGRect(origin: CGPoint(x: 3.0, y: frame.maxY + 1.0 - avatarSize.height), size: avatarSize)
transition.updateFrame(node: avatarNode, frame: avatarFrame)
transition.updateFrame(node: avatarBorderNode, frame: avatarFrame)
}
}
func update(size: CGSize, rect: CGRect, transition: ContainedViewLayoutTransition) {
var avatarOffset: CGFloat = 0.0
if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode {
let avatarFrame = CGRect(origin: CGPoint(x: 3.0, y: rect.maxY + 1.0 - avatarSize.height), size: avatarSize)
transition.updateFrame(node: avatarNode, frame: avatarFrame)
transition.updateFrame(node: avatarBorderNode, frame: avatarFrame)
avatarOffset += avatarSize.width - 1.0
}
let bubbleFrame = CGRect(origin: CGPoint(x: 3.0 + avatarOffset, y: rect.origin.y), size: CGSize(width: rect.width, height: rect.height))
transition.updateFrame(node: self.bubbleNode, frame: bubbleFrame)
transition.updateFrame(node: self.bubbleBorderNode, frame: bubbleFrame)
}
}
final class ChatLoadingPlaceholderNode: ASDisplayNode {
private weak var backgroundNode: WallpaperBackgroundNode?
private let maskNode: ASDisplayNode
private let borderMaskNode: ASDisplayNode
private let containerNode: ASDisplayNode
private var backgroundContent: WallpaperBubbleBackgroundNode?
private let backgroundColorNode: ASDisplayNode
private let effectNode: ShimmerEffectForegroundNode
private let borderNode: ASDisplayNode
private let borderEffectNode: ShimmerEffectForegroundNode
private let messageContainers: [ChatLoadingPlaceholderMessageContainer]
private var absolutePosition: (CGRect, CGSize)?
private var validLayout: (CGSize, UIEdgeInsets)?
init(theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, backgroundNode: WallpaperBackgroundNode, hasAvatar: Bool) {
self.backgroundNode = backgroundNode
self.maskNode = ASDisplayNode()
self.borderMaskNode = ASDisplayNode()
let bubbleImage = messageBubbleImage(maxCornerRadius: bubbleCorners.mainRadius, minCornerRadius: bubbleCorners.auxiliaryRadius, incoming: true, fillColor: .white, strokeColor: .clear, neighbors: .none, theme: theme.chat, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true)
let bubbleBorderImage = messageBubbleImage(maxCornerRadius: bubbleCorners.mainRadius, minCornerRadius: bubbleCorners.auxiliaryRadius, incoming: true, fillColor: .clear, strokeColor: .red, neighbors: .none, theme: theme.chat, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true, onlyOutline: true)
var messageContainers: [ChatLoadingPlaceholderMessageContainer] = []
for _ in 0 ..< 8 {
let container = ChatLoadingPlaceholderMessageContainer(hasAvatar: hasAvatar, bubbleImage: bubbleImage, bubbleBorderImage: bubbleBorderImage)
container.setup(maskNode: self.maskNode, borderMaskNode: self.borderMaskNode)
messageContainers.append(container)
}
self.messageContainers = messageContainers
self.containerNode = ASDisplayNode()
self.borderNode = ASDisplayNode()
self.backgroundColorNode = ASDisplayNode()
self.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: theme, wallpaper: chatWallpaper)
self.effectNode = ShimmerEffectForegroundNode()
self.effectNode.layer.compositingFilter = "screenBlendMode"
self.borderEffectNode = ShimmerEffectForegroundNode()
self.borderEffectNode.layer.compositingFilter = "screenBlendMode"
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundColorNode)
self.containerNode.addSubnode(self.effectNode)
self.addSubnode(self.borderNode)
self.borderNode.addSubnode(self.borderEffectNode)
// self.addSubnode(self.maskNode)
// self.addSubnode(self.borderMaskNode)
}
override func didLoad() {
super.didLoad()
self.containerNode.view.mask = self.maskNode.view
self.borderNode.view.mask = self.borderMaskNode.view
// self.backgroundNode?.updateIsLooping(true)
}
func animateOut(_ historyNode: ChatHistoryNode) {
guard let listNode = historyNode as? ListView, let (size, _) = self.validLayout else {
return
}
self.backgroundNode?.updateIsLooping(false)
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
var lastFrame: CGRect?
var i = 0
listNode.forEachVisibleItemNode { itemNode in
guard i < self.messageContainers.count, let listItemNode = itemNode as? ListViewItemNode else {
return
}
if let bubbleItemNode = itemNode as? ChatMessageBubbleItemNode,
bubbleItemNode.contentNodes.contains(where: { $0 is ChatMessageActionBubbleContentNode }) {
return
}
let messageContainer = self.messageContainers[i]
messageContainer.animateWith(listItemNode, transition: transition)
lastFrame = messageContainer.frame
i += 1
}
if let lastFrame = lastFrame, i < self.messageContainers.count {
var offset = lastFrame.minY
for k in i ..< self.messageContainers.count {
let messageContainer = self.messageContainers[k]
let messageSize = messageContainer.frame.size
messageContainer.update(size: size, rect: CGRect(origin: CGPoint(x: 0.0, y: offset - messageSize.height), size: messageSize), transition: transition)
offset -= messageSize.height
}
}
}
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)
}
}
func updateLayout(size: CGSize, insets: UIEdgeInsets, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, insets)
let bounds = CGRect(origin: .zero, size: size)
transition.updateFrame(node: self.maskNode, frame: bounds)
transition.updateFrame(node: self.borderMaskNode, frame: bounds)
transition.updateFrame(node: self.containerNode, frame: bounds)
transition.updateFrame(node: self.borderNode, frame: bounds)
transition.updateFrame(node: self.backgroundColorNode, frame: bounds)
transition.updateFrame(node: self.effectNode, frame: bounds)
transition.updateFrame(node: self.borderEffectNode, frame: bounds)
self.effectNode.updateAbsoluteRect(bounds, within: bounds.size)
self.borderEffectNode.updateAbsoluteRect(bounds, within: bounds.size)
self.effectNode.update(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.15), horizontal: true, effectSize: 280.0, globalTimeOffset: false, duration: 1.6)
self.borderEffectNode.update(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.45), horizontal: true, effectSize: 320.0, globalTimeOffset: false, duration: 1.6)
let shortHeight: CGFloat = 71.0
let tallHeight: CGFloat = 93.0
let dimensions: [CGSize] = [
CGSize(width: floorToScreenPixels(0.47 * size.width), height: tallHeight),
CGSize(width: floorToScreenPixels(0.57 * size.width), height: tallHeight),
CGSize(width: floorToScreenPixels(0.73 * size.width), height: tallHeight),
CGSize(width: floorToScreenPixels(0.47 * size.width), height: tallHeight),
CGSize(width: floorToScreenPixels(0.57 * size.width), height: tallHeight),
CGSize(width: floorToScreenPixels(0.36 * size.width), height: shortHeight),
CGSize(width: floorToScreenPixels(0.47 * size.width), height: tallHeight),
CGSize(width: floorToScreenPixels(0.57 * size.width), height: tallHeight),
]
var offset: CGFloat = 5.0
var i = 0
for messageContainer in self.messageContainers {
let messageSize = dimensions[i % 12]
messageContainer.update(size: size, rect: CGRect(origin: CGPoint(x: 0.0, y: size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition)
offset += messageSize.height
i += 1
}
if backgroundNode?.hasExtraBubbleBackground() == true {
self.backgroundColorNode.isHidden = true
} else {
self.backgroundColorNode.isHidden = false
}
if let backgroundNode = self.backgroundNode, let backgroundContent = backgroundNode.makeBubbleBackground(for: .free) {
if self.backgroundContent == nil {
self.backgroundContent = backgroundContent
self.containerNode.insertSubnode(backgroundContent, at: 0)
}
} else {
self.backgroundContent?.removeFromSupernode()
self.backgroundContent = nil
}
if let backgroundContent = self.backgroundContent {
transition.updateFrame(node: backgroundContent, frame: bounds)
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)
}
}
}
}

View File

@ -2285,7 +2285,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
var animateReplyNodeIn = false
if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) {
if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item {
self.swipeToReplyFeedback?.impact()
self.swipeToReplyFeedback?.impact(.heavy)
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
self.swipeToReplyNode = swipeToReplyNode
@ -2673,6 +2673,42 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, transition: ContainedViewLayoutTransition) {
guard let _ = self.item, let animationNode = self.animationNode else {
return
}
let targetFrame = self.view.convert(animationNode.frame, to: messageContainer.parentView)
messageContainer.animateBackgroundFrame(to: targetFrame, transition: transition)
var backgroundFrame = messageContainer.frame
backgroundFrame.size.width = backgroundFrame.size.height
// let localSourceContentFrame = self.contextSourceNode.contentNode.view.convert(backgroundFrame.offsetBy(dx: self.contextSourceNode.contentRect.minX, dy: self.contextSourceNode.contentRect.minY), to: self.contextSourceNode.contentNode.view)
// let sourceCenter = CGPoint(
// x: localSourceContentFrame.minX + 11.2,
// y: localSourceContentFrame.midY - 1.8
// )
let sourceScale: CGFloat = 36.0 / self.imageNode.frame.height
let offset = CGPoint(x: -90.0, y: 90.0)
// let offset = CGPoint(
// x: sourceCenter.x - self.imageNode.frame.midX,
// y: sourceCenter.y - self.imageNode.frame.midY
// )
transition.animatePositionAdditive(layer: self.imageNode.layer, offset: offset)
transition.animateTransformScale(node: self.imageNode, from: sourceScale)
if let animationNode = self.animationNode {
transition.animatePositionAdditive(layer: animationNode.layer, offset: offset)
transition.animateTransformScale(node: animationNode, from: sourceScale)
}
transition.animatePositionAdditive(layer: self.placeholderNode.layer, offset: offset)
transition.animateTransformScale(node: self.placeholderNode, from: sourceScale)
}
override func openMessageContextMenu() {
guard let item = self.item else {
return

View File

@ -796,6 +796,48 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
}
}
func animateFromLoadingPlaceholder(messageContainer: ChatLoadingPlaceholderMessageContainer, transition: ContainedViewLayoutTransition) {
guard let _ = self.item else {
return
}
let targetFrame = self.view.convert(self.backgroundNode.frame, to: messageContainer.parentView)
messageContainer.animateBackgroundFrame(to: targetFrame, transition: transition)
// let backgroundFrame = messageContainer.frame
//// let widthDifference = self.backgroundNode.frame.width - backgroundFrame.width
//// let heightDifference = self.backgroundNode.frame.height - backgroundFrame.height
//
// if let type = self.backgroundNode.type {
// if case .none = type {
// } else {
// self.clippingNode.clipsToBounds = true
// }
// }
// transition.animateFrame(layer: self.clippingNode.layer, from: CGRect(origin: CGPoint(x: self.clippingNode.frame.minX, y: backgroundFrame.minY), size: backgroundFrame.size), completion: { [weak self] _ in
// guard let strongSelf = self else {
// return
// }
// strongSelf.clippingNode.clipsToBounds = false
// })
// transition.animateOffsetAdditive(layer: self.clippingNode.layer, offset: backgroundFrame.minY - self.clippingNode.frame.minY)
//
// let combinedTransition = CombinedTransition(horizontal: transition, vertical: transition)
//
// self.backgroundWallpaperNode.animateFrom(sourceView: messageContainer.bubbleNode.view, mediaBox: item.context.account.postbox.mediaBox, transition: combinedTransition)
// self.backgroundNode.animateFrom(sourceView: messageContainer.bubbleNode.view, transition: combinedTransition)
// for contentNode in self.contentNodes {
// if let contentNode = contentNode as? ChatMessageTextBubbleContentNode {
// let localSourceContentFrame = self.mainContextSourceNode.contentNode.view.convert(textInput.contentView.frame.offsetBy(dx: self.mainContextSourceNode.contentRect.minX, dy: self.mainContextSourceNode.contentRect.minY), to: contentNode.view)
// textInput.contentView.frame = localSourceContentFrame
// contentNode.animateFrom(sourceView: textInput.contentView, scrollOffset: textInput.scrollOffset, widthDifference: widthDifference, transition: transition)
// } else if let contentNode = contentNode as? ChatMessageWebpageBubbleContentNode {
// transition.animatePositionAdditive(node: contentNode, offset: CGPoint(x: 0.0, y: heightDifference))
// }
// }
}
func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) {
guard let item = self.item else {
@ -2531,6 +2573,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
strongSelf.nameNode = nil
strongSelf.adminBadgeNode?.removeFromSupernode()
strongSelf.adminBadgeNode = nil
strongSelf.credibilityIconView?.removeFromSuperview()
strongSelf.credibilityIconView = nil
}
let beginAt = applyInfo.timestamp ?? CACurrentMediaTime()
@ -3892,6 +3936,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
}
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
var offset: CGFloat = 0.0
var swipeOffset: CGFloat = 45.0
if let item = self.item, item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData) {
offset = -24.0
} else {
offset = 10.0
swipeOffset = 60.0
}
switch recognizer.state {
case .began:
self.currentSwipeToReplyTranslation = 0.0
@ -3903,17 +3956,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
case .changed:
var translation = recognizer.translation(in: self.view)
translation.x = max(-80.0, min(0.0, translation.x))
var animateReplyNodeIn = false
if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) {
if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item {
self.swipeToReplyFeedback?.impact(.heavy)
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
self.swipeToReplyNode = swipeToReplyNode
self.insertSubnode(swipeToReplyNode, belowSubnode: self.messageAccessibilityArea)
animateReplyNodeIn = true
}
if let item = self.item, self.swipeToReplyNode == nil {
let swipeToReplyNode = ChatMessageSwipeToReplyNode(fillColor: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), foregroundColor: bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: item.presentationData.theme.wallpaper), backgroundNode: item.controllerInteraction.presentationContext.backgroundNode, action: ChatMessageSwipeToReplyNode.Action(self.currentSwipeAction))
self.swipeToReplyNode = swipeToReplyNode
self.insertSubnode(swipeToReplyNode, at: 0)
}
self.currentSwipeToReplyTranslation = translation.x
var bounds = self.bounds
bounds.origin.x = -translation.x
@ -3925,25 +3975,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
self.updateAttachedAvatarNodeOffset(offset: translation.x, transition: .immediate)
if let swipeToReplyNode = self.swipeToReplyNode {
swipeToReplyNode.frame = CGRect(origin: CGPoint(x: bounds.size.width, y: floor((self.contentSize.height - 33.0) / 2.0)), size: CGSize(width: 33.0, height: 33.0))
swipeToReplyNode.frame = CGRect(origin: CGPoint(x: bounds.size.width + offset, y: floor((self.contentSize.height - 33.0) / 2.0)), size: CGSize(width: 33.0, height: 33.0))
if let (rect, containerSize) = self.absoluteRect {
let mappedRect = CGRect(origin: CGPoint(x: rect.minX + swipeToReplyNode.frame.minX, y: rect.minY + swipeToReplyNode.frame.minY), size: swipeToReplyNode.frame.size)
swipeToReplyNode.updateAbsoluteRect(mappedRect, within: containerSize)
}
if animateReplyNodeIn {
swipeToReplyNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12)
swipeToReplyNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
} else {
swipeToReplyNode.alpha = min(1.0, abs(translation.x / 45.0))
}
swipeToReplyNode.updateProgress(abs(translation.x) / swipeOffset)
}
case .cancelled, .ended:
self.swipeToReplyFeedback = nil
let translation = recognizer.translation(in: self.view)
if case .ended = recognizer.state, translation.x < -45.0 {
if case .ended = recognizer.state, translation.x < -swipeOffset {
if let item = self.item {
if let currentSwipeAction = currentSwipeAction {
switch currentSwipeAction {

View File

@ -5,6 +5,8 @@ import AsyncDisplayKit
import AppBundle
import WallpaperBackgroundNode
private let size = CGSize(width: 33.0, height: 33.0)
final class ChatMessageSwipeToReplyNode: ASDisplayNode {
enum Action {
case reply
@ -17,6 +19,10 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
private let backgroundNode: NavigationBackgroundNode
private let foregroundNode: ASImageNode
private let maskNode: ASDisplayNode
private let progressLayer: CAShapeLayer
private let fillLayer: CAShapeLayer
private var absolutePosition: (CGRect, CGSize)?
init(fillColor: UIColor, enableBlur: Bool, foregroundColor: UIColor, backgroundNode: WallpaperBackgroundNode?, action: ChatMessageSwipeToReplyNode.Action) {
@ -60,16 +66,43 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
}
})
self.maskNode = ASDisplayNode()
self.progressLayer = CAShapeLayer()
self.fillLayer = CAShapeLayer()
super.init()
self.allowsGroupOpacity = true
self.addSubnode(self.backgroundNode)
self.addSubnode(self.foregroundNode)
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 33.0, height: 33.0))
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: self.backgroundNode.bounds.height / 2.0, transition: .immediate)
self.foregroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 33.0, height: 33.0))
self.maskNode.layer.addSublayer(self.progressLayer)
self.maskNode.layer.addSublayer(self.fillLayer)
self.addSubnode(self.foregroundNode)
let backgroundFrame = CGRect(origin: CGPoint(), size: size).insetBy(dx: -22.0, dy: -22.0)
self.backgroundNode.frame = backgroundFrame
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: self.backgroundNode.bounds.height / 2.0, transition: .immediate)
self.foregroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.progressLayer.strokeColor = UIColor.white.cgColor
self.progressLayer.fillColor = UIColor.clear.cgColor
self.progressLayer.lineCap = .round
self.progressLayer.lineWidth = 2.0 - UIScreenPixel
self.fillLayer.strokeColor = UIColor.white.cgColor
self.fillLayer.fillColor = UIColor.clear.cgColor
self.fillLayer.isHidden = true
self.maskNode.frame = CGRect(origin: CGPoint(x: 22.0, y: 22.0), size: backgroundFrame.size)
self.progressLayer.frame = CGRect(origin: .zero, size: size).insetBy(dx: -20.0, dy: -20.0)
self.fillLayer.frame = CGRect(origin: .zero, size: size)
let path = UIBezierPath(arcCenter: CGPoint(x: self.progressLayer.frame.width / 2.0, y: self.progressLayer.frame.height / 2.0), radius: size.width / 2.0, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true)
self.progressLayer.path = path.cgPath
if backgroundNode?.hasExtraBubbleBackground() == true {
if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
@ -84,8 +117,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
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
backgroundContent.frame = backgroundFrame
if let (rect, containerSize) = self.absolutePosition {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
@ -97,6 +129,73 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
}
}
override func didLoad() {
super.didLoad()
if let backgroundContent = self.backgroundContent {
backgroundContent.view.mask = self.maskNode.view
} else {
self.backgroundNode.view.mask = self.maskNode.view
}
}
private var animatedWave = false
func updateProgress(_ progress: CGFloat) {
let progress = max(0.0, min(1.0, progress))
var foregroundProgress = min(1.0, progress * 1.2)
var scaleProgress = 0.65 + foregroundProgress * 0.35
if !self.animatedWave {
let path = UIBezierPath(arcCenter: CGPoint(x: self.progressLayer.frame.width / 2.0, y: self.progressLayer.frame.height / 2.0), radius: size.width / 2.0, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(-0.5 * .pi + progress * 2.0 * .pi), clockwise: true)
self.progressLayer.path = path.cgPath
} else {
foregroundProgress = progress
scaleProgress = progress
self.maskNode.alpha = progress
}
self.layer.sublayerTransform = CATransform3DMakeScale(scaleProgress, scaleProgress, 1.0)
self.foregroundNode.alpha = foregroundProgress
self.foregroundNode.transform = CATransform3DMakeScale(foregroundProgress, foregroundProgress, 1.0)
if progress == 1.0 {
self.playSuccessAnimation()
}
}
func playSuccessAnimation() {
guard !self.animatedWave else {
return
}
self.animatedWave = true
var lineWidth = self.progressLayer.lineWidth
self.progressLayer.lineWidth = 1.0
self.progressLayer.animate(from: lineWidth as NSNumber, to: 1.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.25, completion: { [weak self] _ in
self?.progressLayer.animate(from: 1.0 as NSNumber, to: 0.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.15, removeOnCompletion: false)
})
var path = self.progressLayer.path
var targetPath = UIBezierPath(arcCenter: CGPoint(x: self.progressLayer.frame.width / 2.0, y: self.progressLayer.frame.height / 2.0), radius: 35.0, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(-0.5 * .pi + 2.0 * .pi), clockwise: true).cgPath
self.progressLayer.path = targetPath
self.progressLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
self.fillLayer.isHidden = false
self.fillLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).cgPath
self.fillLayer.lineWidth = 2.0 - UIScreenPixel
lineWidth = self.fillLayer.lineWidth
self.fillLayer.lineWidth = 18.0
self.fillLayer.animate(from: lineWidth as NSNumber, to: 18.0 as NSNumber, keyPath: "lineWidth", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.25)
path = self.fillLayer.path
targetPath = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size).insetBy(dx: 9.0, dy: 9.0)).cgPath
self.fillLayer.path = targetPath
self.fillLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.25)
}
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
self.absolutePosition = (rect, containerSize)
if let backgroundContent = self.backgroundContent {

View File

@ -2612,7 +2612,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
if self.isSettings, let user = peer as? TelegramUser {
var subtitle = formatPhoneNumber(user.phone ?? "")
let mainUsername = user.usernames.first?.username ?? user.username
let mainUsername = user.usernames.first(where: { $0.flags.contains(.isActive) })?.username ?? user.username
if let mainUsername = mainUsername, !mainUsername.isEmpty {
subtitle = "\(subtitle) • @\(mainUsername)"
}

View File

@ -956,7 +956,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}))
}
if let username = user.username {
let mainUsername = user.usernames.first?.username ?? username
let mainUsername = user.usernames.first(where: { $0.flags.contains(.isActive) })?.username ?? username
var additionalUsernames: String?
let usernames = user.usernames.filter { $0.flags.contains(.isActive) && $0.username != mainUsername }
if !usernames.isEmpty {
@ -1135,7 +1135,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
if let username = channel.username {
var additionalUsernames: String?
let mainUsername = channel.usernames.first?.username ?? username
let mainUsername = channel.usernames.first(where: { $0.flags.contains(.isActive) })?.username ?? username
let usernames = channel.usernames.filter { $0.flags.contains(.isActive) && $0.username != mainUsername }
if !usernames.isEmpty {

View File

@ -283,6 +283,10 @@ final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode
}
func updateIsLooping(_ isLooping: Bool) {
}
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
}

View File

@ -58,6 +58,7 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
func update(wallpaper: TelegramWallpaper)
func _internalUpdateIsSettingUpWallpaper()
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
func updateIsLooping(_ isLooping: Bool)
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool)
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners)
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
@ -740,6 +741,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
gradientAngle = file.settings.rotation ?? 0
}
var scheduleLoopingEvent = false
if gradientColors.count >= 3 {
let mappedColors = gradientColors.map { color -> UIColor in
return UIColor(rgb: color)
@ -749,6 +751,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.gradientBackgroundNode = gradientBackgroundNode
self.insertSubnode(gradientBackgroundNode, aboveSubnode: self.contentNode)
gradientBackgroundNode.setPatternOverlay(layer: self.patternImageLayer)
if self.isLooping {
scheduleLoopingEvent = true
}
}
self.gradientBackgroundNode?.updateColors(colors: mappedColors)
@ -828,6 +834,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
self.updateBubbles()
if scheduleLoopingEvent {
self.animateEvent(transition: .animated(duration: 0.7, curve: .linear), extendAnimation: false)
}
}
}
@ -1089,11 +1099,28 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
}
}
private var isLooping = false
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: {})
self.gradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: { [weak self] in
if let strongSelf = self {
if strongSelf.isLooping {
strongSelf.animateEvent(transition: transition, extendAnimation: extendAnimation)
}
}
})
self.outgoingBubbleGradientBackgroundNode?.animateEvent(transition: transition, extendAnimation: extendAnimation, backwards: false, completion: {})
}
func updateIsLooping(_ isLooping: Bool) {
let wasLooping = self.isLooping
self.isLooping = isLooping
if isLooping && !wasLooping {
self.animateEvent(transition: .animated(duration: 0.7, curve: .linear), extendAnimation: false)
}
}
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {
self.bubbleTheme = bubbleTheme
@ -1916,6 +1943,7 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
}
}
private var isLooping = false
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
if let gradient = self.gradient {
self.isAnimating = true
@ -1924,13 +1952,26 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
guard let strongSelf = self else {
return
}
strongSelf.isAnimating = false
strongSelf.componentsUpdated()
if strongSelf.isLooping {
strongSelf.animateEvent(transition: transition, extendAnimation: extendAnimation)
} else {
strongSelf.isAnimating = false
strongSelf.componentsUpdated()
}
})
} else {
self.isAnimating = false
}
}
func updateIsLooping(_ isLooping: Bool) {
let wasLooping = self.isLooping
self.isLooping = isLooping
if isLooping && !wasLooping {
self.animateEvent(transition: .animated(duration: 0.4, curve: .linear), extendAnimation: false)
}
}
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {