mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
35e9b2caab
commit
fe7dcc6866
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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":[]}
|
@ -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":[]}
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)"
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -283,6 +283,10 @@ final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode
|
||||
|
||||
}
|
||||
|
||||
func updateIsLooping(_ isLooping: Bool) {
|
||||
|
||||
}
|
||||
|
||||
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
|
||||
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user