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 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 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 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 startIcon = ItemListRevealOptionIcon.animation(animation: "anim_play", 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 closeIcon = ItemListRevealOptionIcon.animation(animation: "anim_pause", scale: 1.0, offset: 0.0, replaceColors: [0xbdbdc2], flip: false)
|
||||||
|
|
||||||
private enum RevealOptionKey: Int32 {
|
private enum RevealOptionKey: Int32 {
|
||||||
case pin
|
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)
|
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)
|
var dimensions = CGSize(width: 100.0, height: 100.0)
|
||||||
if case let .image(image) = self.media {
|
if case let .image(image) = self.media {
|
||||||
self.playIcon.isHidden = true
|
self.playIcon.isHidden = true
|
||||||
@ -451,6 +452,9 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if case let .file(file) = self.media {
|
} else if case let .file(file) = self.media {
|
||||||
|
if file.isInstantVideo {
|
||||||
|
isRound = true
|
||||||
|
}
|
||||||
if file.isAnimated {
|
if file.isAnimated {
|
||||||
self.playIcon.isHidden = true
|
self.playIcon.isHidden = true
|
||||||
} else {
|
} else {
|
||||||
@ -468,7 +472,7 @@ private final class ChatListMediaPreviewNode: ASDisplayNode {
|
|||||||
|
|
||||||
let makeLayout = self.imageNode.asyncLayout()
|
let makeLayout = self.imageNode.asyncLayout()
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
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()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1400,7 +1404,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
break inner
|
break inner
|
||||||
} else if let file = media as? TelegramMediaFile {
|
} 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
|
let fitSize = contentImageSize
|
||||||
contentImageSpecs.append((message, .file(file), fitSize))
|
contentImageSpecs.append((message, .file(file), fitSize))
|
||||||
}
|
}
|
||||||
|
@ -214,21 +214,25 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
|
|
||||||
let titleText: String
|
let titleText: String
|
||||||
let subtitleText: String
|
let subtitleText: String
|
||||||
|
let subtitleColor: UIColor
|
||||||
if let username = item.username {
|
if let username = item.username {
|
||||||
titleText = "@\(username.username)"
|
titleText = "@\(username.username)"
|
||||||
|
|
||||||
if username.flags.contains(.isEditable) || username.flags.contains(.isActive) {
|
if username.flags.contains(.isEditable) || username.flags.contains(.isActive) {
|
||||||
subtitleText = item.presentationData.strings.Group_Setup_LinkActive
|
subtitleText = item.presentationData.strings.Group_Setup_LinkActive
|
||||||
|
subtitleColor = item.presentationData.theme.list.itemAccentColor
|
||||||
} else {
|
} else {
|
||||||
subtitleText = item.presentationData.strings.Group_Setup_LinkInactive
|
subtitleText = item.presentationData.strings.Group_Setup_LinkInactive
|
||||||
|
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
titleText = " "
|
titleText = " "
|
||||||
subtitleText = " "
|
subtitleText = " "
|
||||||
|
subtitleColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleAttributedString = NSAttributedString(string: titleText, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
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 reorderControlSizeAndApply = reorderControlLayout(item.presentationData.theme)
|
||||||
let reorderInset: CGFloat = reorderControlSizeAndApply.0
|
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 currentBackgroundColor: UIColor?
|
||||||
private var currentForegroundColor: UIColor?
|
private var currentForegroundColor: UIColor?
|
||||||
private var currentHorizontal: Bool?
|
private var currentHorizontal: Bool?
|
||||||
@ -187,7 +187,7 @@ final class ShimmerEffectForegroundNode: ASDisplayNode {
|
|||||||
private var globalTimeOffset = true
|
private var globalTimeOffset = true
|
||||||
private var duration: Double?
|
private var duration: Double?
|
||||||
|
|
||||||
override init() {
|
public override init() {
|
||||||
self.imageNodeContainer = ASDisplayNode()
|
self.imageNodeContainer = ASDisplayNode()
|
||||||
self.imageNodeContainer.isLayerBacked = true
|
self.imageNodeContainer.isLayerBacked = true
|
||||||
|
|
||||||
@ -206,21 +206,21 @@ final class ShimmerEffectForegroundNode: ASDisplayNode {
|
|||||||
self.addSubnode(self.imageNodeContainer)
|
self.addSubnode(self.imageNodeContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didEnterHierarchy() {
|
public override func didEnterHierarchy() {
|
||||||
super.didEnterHierarchy()
|
super.didEnterHierarchy()
|
||||||
|
|
||||||
self.isCurrentlyInHierarchy = true
|
self.isCurrentlyInHierarchy = true
|
||||||
self.updateAnimation()
|
self.updateAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didExitHierarchy() {
|
public override func didExitHierarchy() {
|
||||||
super.didExitHierarchy()
|
super.didExitHierarchy()
|
||||||
|
|
||||||
self.isCurrentlyInHierarchy = false
|
self.isCurrentlyInHierarchy = false
|
||||||
self.updateAnimation()
|
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 {
|
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.isEqual(backgroundColor), let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.isEqual(foregroundColor), self.currentHorizontal == horizontal {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -274,7 +274,7 @@ final class ShimmerEffectForegroundNode: ASDisplayNode {
|
|||||||
self.updateAnimation()
|
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 {
|
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||||
return
|
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?
|
var blurredHistoryNode: ASImageNode?
|
||||||
let historyNodeContainer: ASDisplayNode
|
let historyNodeContainer: ASDisplayNode
|
||||||
let loadingNode: ChatLoadingNode
|
let loadingNode: ChatLoadingNode
|
||||||
|
private var loadingPlaceholderNode: ChatLoadingPlaceholderNode
|
||||||
|
|
||||||
private var emptyNode: ChatEmptyNode?
|
private var emptyNode: ChatEmptyNode?
|
||||||
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
||||||
private var didDisplayEmptyGreeting = false
|
private var didDisplayEmptyGreeting = false
|
||||||
@ -220,17 +222,36 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private var isLoadingValue: Bool = false
|
private var isLoadingValue: Bool = false
|
||||||
private func updateIsLoading(isLoading: Bool, animated: Bool) {
|
private func updateIsLoading(isLoading: Bool, animated: Bool) {
|
||||||
|
loadingPlaceholderNode.isHidden = true
|
||||||
if isLoading != self.isLoadingValue {
|
if isLoading != self.isLoadingValue {
|
||||||
self.isLoadingValue = isLoading
|
self.isLoadingValue = isLoading
|
||||||
if 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.isHidden = false
|
||||||
self.loadingNode.layer.removeAllAnimations()
|
self.loadingNode.layer.removeAllAnimations()
|
||||||
self.loadingNode.alpha = 1.0
|
self.loadingNode.alpha = 1.0
|
||||||
if animated {
|
if animated {
|
||||||
self.loadingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
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 {
|
} 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
|
self.loadingNode.alpha = 0.0
|
||||||
if animated {
|
if animated {
|
||||||
self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
|
self.loadingNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
|
||||||
@ -394,6 +415,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.loadingNode = ChatLoadingNode(theme: self.chatPresentationInterfaceState.theme, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, bubbleCorners: self.chatPresentationInterfaceState.bubbleCorners)
|
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.inputPanelContainerNode = ChatInputPanelContainer()
|
||||||
self.inputPanelOverlayNode = SparseNode()
|
self.inputPanelOverlayNode = SparseNode()
|
||||||
self.inputPanelClippingNode = SparseNode()
|
self.inputPanelClippingNode = SparseNode()
|
||||||
@ -468,6 +491,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
assert(Queue.mainQueue().isCurrent())
|
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
|
self.historyNode.setLoadStateUpdated { [weak self] loadState, animated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let wasLoading = strongSelf.isLoadingValue
|
let wasLoading = strongSelf.isLoadingValue
|
||||||
@ -532,6 +561,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
self.addSubnode(self.contentContainerNode)
|
self.addSubnode(self.contentContainerNode)
|
||||||
self.contentContainerNode.addSubnode(self.backgroundNode)
|
self.contentContainerNode.addSubnode(self.backgroundNode)
|
||||||
|
self.contentContainerNode.addSubnode(self.loadingPlaceholderNode)
|
||||||
self.contentContainerNode.addSubnode(self.historyNodeContainer)
|
self.contentContainerNode.addSubnode(self.historyNodeContainer)
|
||||||
|
|
||||||
if let navigationBar = self.navigationBar {
|
if let navigationBar = self.navigationBar {
|
||||||
@ -1387,6 +1417,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
//transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds)
|
//transition.updateFrame(node: self.historyScrollingArea, frame: contentBounds)
|
||||||
|
|
||||||
transition.updateFrame(node: self.loadingNode, frame: contentBounds)
|
transition.updateFrame(node: self.loadingNode, frame: contentBounds)
|
||||||
|
transition.updateFrame(node: self.loadingPlaceholderNode, frame: contentBounds)
|
||||||
|
|
||||||
if let restrictedNode = self.restrictedNode {
|
if let restrictedNode = self.restrictedNode {
|
||||||
transition.updateFrame(node: restrictedNode, frame: contentBounds)
|
transition.updateFrame(node: restrictedNode, frame: contentBounds)
|
||||||
@ -1544,6 +1575,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.visibleAreaInset = visibleAreaInset
|
self.visibleAreaInset = visibleAreaInset
|
||||||
self.loadingNode.updateLayout(size: contentBounds.size, insets: visibleAreaInset, transition: transition)
|
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 {
|
if let containerNode = self.containerNode {
|
||||||
contentBottomInset += 8.0
|
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))
|
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 TelegramCore
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ActivityIndicator
|
import ActivityIndicator
|
||||||
|
import WallpaperBackgroundNode
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
final class ChatLoadingNode: ASDisplayNode {
|
final class ChatLoadingNode: ASDisplayNode {
|
||||||
private let backgroundNode: NavigationBackgroundNode
|
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
|
var animateReplyNodeIn = false
|
||||||
if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) {
|
if (translation.x < -45.0) != (self.currentSwipeToReplyTranslation < -45.0) {
|
||||||
if translation.x < -45.0, self.swipeToReplyNode == nil, let item = self.item {
|
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))
|
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.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() {
|
override func openMessageContextMenu() {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
|
@ -797,6 +797,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) {
|
func animateContentFromTextInputField(textInput: ChatMessageTransitionNode.Source.TextInput, transition: CombinedTransition) {
|
||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
@ -2531,6 +2573,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
strongSelf.nameNode = nil
|
strongSelf.nameNode = nil
|
||||||
strongSelf.adminBadgeNode?.removeFromSupernode()
|
strongSelf.adminBadgeNode?.removeFromSupernode()
|
||||||
strongSelf.adminBadgeNode = nil
|
strongSelf.adminBadgeNode = nil
|
||||||
|
strongSelf.credibilityIconView?.removeFromSuperview()
|
||||||
|
strongSelf.credibilityIconView = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let beginAt = applyInfo.timestamp ?? CACurrentMediaTime()
|
let beginAt = applyInfo.timestamp ?? CACurrentMediaTime()
|
||||||
@ -3892,6 +3936,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func swipeToReplyGesture(_ recognizer: ChatSwipeToReplyRecognizer) {
|
@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 {
|
switch recognizer.state {
|
||||||
case .began:
|
case .began:
|
||||||
self.currentSwipeToReplyTranslation = 0.0
|
self.currentSwipeToReplyTranslation = 0.0
|
||||||
@ -3903,17 +3956,14 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
case .changed:
|
case .changed:
|
||||||
var translation = recognizer.translation(in: self.view)
|
var translation = recognizer.translation(in: self.view)
|
||||||
translation.x = max(-80.0, min(0.0, translation.x))
|
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
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.swipeToReplyNode = swipeToReplyNode
|
||||||
self.insertSubnode(swipeToReplyNode, belowSubnode: self.messageAccessibilityArea)
|
self.insertSubnode(swipeToReplyNode, at: 0)
|
||||||
animateReplyNodeIn = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentSwipeToReplyTranslation = translation.x
|
self.currentSwipeToReplyTranslation = translation.x
|
||||||
var bounds = self.bounds
|
var bounds = self.bounds
|
||||||
bounds.origin.x = -translation.x
|
bounds.origin.x = -translation.x
|
||||||
@ -3925,25 +3975,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
self.updateAttachedAvatarNodeOffset(offset: translation.x, transition: .immediate)
|
self.updateAttachedAvatarNodeOffset(offset: translation.x, transition: .immediate)
|
||||||
|
|
||||||
if let swipeToReplyNode = self.swipeToReplyNode {
|
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 {
|
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)
|
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)
|
swipeToReplyNode.updateAbsoluteRect(mappedRect, within: containerSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if animateReplyNodeIn {
|
swipeToReplyNode.updateProgress(abs(translation.x) / swipeOffset)
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case .cancelled, .ended:
|
case .cancelled, .ended:
|
||||||
self.swipeToReplyFeedback = nil
|
self.swipeToReplyFeedback = nil
|
||||||
|
|
||||||
let translation = recognizer.translation(in: self.view)
|
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 item = self.item {
|
||||||
if let currentSwipeAction = currentSwipeAction {
|
if let currentSwipeAction = currentSwipeAction {
|
||||||
switch currentSwipeAction {
|
switch currentSwipeAction {
|
||||||
|
@ -5,6 +5,8 @@ import AsyncDisplayKit
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import WallpaperBackgroundNode
|
import WallpaperBackgroundNode
|
||||||
|
|
||||||
|
private let size = CGSize(width: 33.0, height: 33.0)
|
||||||
|
|
||||||
final class ChatMessageSwipeToReplyNode: ASDisplayNode {
|
final class ChatMessageSwipeToReplyNode: ASDisplayNode {
|
||||||
enum Action {
|
enum Action {
|
||||||
case reply
|
case reply
|
||||||
@ -17,6 +19,10 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
|
|||||||
private let backgroundNode: NavigationBackgroundNode
|
private let backgroundNode: NavigationBackgroundNode
|
||||||
private let foregroundNode: ASImageNode
|
private let foregroundNode: ASImageNode
|
||||||
|
|
||||||
|
private let maskNode: ASDisplayNode
|
||||||
|
private let progressLayer: CAShapeLayer
|
||||||
|
private let fillLayer: CAShapeLayer
|
||||||
|
|
||||||
private var absolutePosition: (CGRect, CGSize)?
|
private var absolutePosition: (CGRect, CGSize)?
|
||||||
|
|
||||||
init(fillColor: UIColor, enableBlur: Bool, foregroundColor: UIColor, backgroundNode: WallpaperBackgroundNode?, action: ChatMessageSwipeToReplyNode.Action) {
|
init(fillColor: UIColor, enableBlur: Bool, foregroundColor: UIColor, backgroundNode: WallpaperBackgroundNode?, action: ChatMessageSwipeToReplyNode.Action) {
|
||||||
@ -60,15 +66,42 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.maskNode = ASDisplayNode()
|
||||||
|
self.progressLayer = CAShapeLayer()
|
||||||
|
self.fillLayer = CAShapeLayer()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.allowsGroupOpacity = true
|
self.allowsGroupOpacity = true
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
|
|
||||||
|
self.maskNode.layer.addSublayer(self.progressLayer)
|
||||||
|
self.maskNode.layer.addSublayer(self.fillLayer)
|
||||||
|
|
||||||
self.addSubnode(self.foregroundNode)
|
self.addSubnode(self.foregroundNode)
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 33.0, height: 33.0))
|
|
||||||
|
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.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.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 backgroundNode?.hasExtraBubbleBackground() == true {
|
||||||
if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) {
|
if let backgroundContent = backgroundNode?.makeBubbleBackground(for: .free) {
|
||||||
@ -84,8 +117,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode {
|
|||||||
|
|
||||||
if let backgroundContent = self.backgroundContent {
|
if let backgroundContent = self.backgroundContent {
|
||||||
self.backgroundNode.isHidden = true
|
self.backgroundNode.isHidden = true
|
||||||
backgroundContent.cornerRadius = min(self.backgroundNode.bounds.width, self.backgroundNode.bounds.height) / 2.0
|
backgroundContent.frame = backgroundFrame
|
||||||
backgroundContent.frame = self.backgroundNode.frame
|
|
||||||
if let (rect, containerSize) = self.absolutePosition {
|
if let (rect, containerSize) = self.absolutePosition {
|
||||||
var backgroundFrame = backgroundContent.frame
|
var backgroundFrame = backgroundContent.frame
|
||||||
backgroundFrame.origin.x += rect.minX
|
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) {
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
self.absolutePosition = (rect, containerSize)
|
self.absolutePosition = (rect, containerSize)
|
||||||
if let backgroundContent = self.backgroundContent {
|
if let backgroundContent = self.backgroundContent {
|
||||||
|
@ -2612,7 +2612,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
if self.isSettings, let user = peer as? TelegramUser {
|
if self.isSettings, let user = peer as? TelegramUser {
|
||||||
var subtitle = formatPhoneNumber(user.phone ?? "")
|
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 {
|
if let mainUsername = mainUsername, !mainUsername.isEmpty {
|
||||||
subtitle = "\(subtitle) • @\(mainUsername)"
|
subtitle = "\(subtitle) • @\(mainUsername)"
|
||||||
}
|
}
|
||||||
|
@ -956,7 +956,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
if let username = user.username {
|
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?
|
var additionalUsernames: String?
|
||||||
let usernames = user.usernames.filter { $0.flags.contains(.isActive) && $0.username != mainUsername }
|
let usernames = user.usernames.filter { $0.flags.contains(.isActive) && $0.username != mainUsername }
|
||||||
if !usernames.isEmpty {
|
if !usernames.isEmpty {
|
||||||
@ -1135,7 +1135,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
|||||||
|
|
||||||
if let username = channel.username {
|
if let username = channel.username {
|
||||||
var additionalUsernames: String?
|
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 }
|
let usernames = channel.usernames.filter { $0.flags.contains(.isActive) && $0.username != mainUsername }
|
||||||
if !usernames.isEmpty {
|
if !usernames.isEmpty {
|
||||||
|
@ -283,6 +283,10 @@ final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateIsLooping(_ isLooping: Bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
|
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
|
|||||||
func update(wallpaper: TelegramWallpaper)
|
func update(wallpaper: TelegramWallpaper)
|
||||||
func _internalUpdateIsSettingUpWallpaper()
|
func _internalUpdateIsSettingUpWallpaper()
|
||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
|
||||||
|
func updateIsLooping(_ isLooping: Bool)
|
||||||
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool)
|
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool)
|
||||||
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners)
|
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners)
|
||||||
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
|
func hasBubbleBackground(for type: WallpaperBubbleType) -> Bool
|
||||||
@ -740,6 +741,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
gradientAngle = file.settings.rotation ?? 0
|
gradientAngle = file.settings.rotation ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var scheduleLoopingEvent = false
|
||||||
if gradientColors.count >= 3 {
|
if gradientColors.count >= 3 {
|
||||||
let mappedColors = gradientColors.map { color -> UIColor in
|
let mappedColors = gradientColors.map { color -> UIColor in
|
||||||
return UIColor(rgb: color)
|
return UIColor(rgb: color)
|
||||||
@ -749,6 +751,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
self.gradientBackgroundNode = gradientBackgroundNode
|
self.gradientBackgroundNode = gradientBackgroundNode
|
||||||
self.insertSubnode(gradientBackgroundNode, aboveSubnode: self.contentNode)
|
self.insertSubnode(gradientBackgroundNode, aboveSubnode: self.contentNode)
|
||||||
gradientBackgroundNode.setPatternOverlay(layer: self.patternImageLayer)
|
gradientBackgroundNode.setPatternOverlay(layer: self.patternImageLayer)
|
||||||
|
|
||||||
|
if self.isLooping {
|
||||||
|
scheduleLoopingEvent = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.gradientBackgroundNode?.updateColors(colors: mappedColors)
|
self.gradientBackgroundNode?.updateColors(colors: mappedColors)
|
||||||
|
|
||||||
@ -828,6 +834,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
if let size = self.validLayout {
|
if let size = self.validLayout {
|
||||||
self.updateLayout(size: size, transition: .immediate)
|
self.updateLayout(size: size, transition: .immediate)
|
||||||
self.updateBubbles()
|
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) {
|
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: {})
|
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) {
|
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
|
||||||
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {
|
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {
|
||||||
self.bubbleTheme = bubbleTheme
|
self.bubbleTheme = bubbleTheme
|
||||||
@ -1916,6 +1943,7 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isLooping = false
|
||||||
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
|
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
|
||||||
if let gradient = self.gradient {
|
if let gradient = self.gradient {
|
||||||
self.isAnimating = true
|
self.isAnimating = true
|
||||||
@ -1924,14 +1952,27 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strongSelf.isLooping {
|
||||||
|
strongSelf.animateEvent(transition: transition, extendAnimation: extendAnimation)
|
||||||
|
} else {
|
||||||
strongSelf.isAnimating = false
|
strongSelf.isAnimating = false
|
||||||
strongSelf.componentsUpdated()
|
strongSelf.componentsUpdated()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.isAnimating = false
|
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) {
|
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners) {
|
||||||
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {
|
if self.bubbleTheme !== bubbleTheme || self.bubbleCorners != bubbleCorners {
|
||||||
self.bubbleTheme = bubbleTheme
|
self.bubbleTheme = bubbleTheme
|
||||||
|
Loading…
x
Reference in New Issue
Block a user