Display a toast after sticker list changes

This commit is contained in:
Ali
2019-12-24 13:52:38 +04:00
parent 7dbf4cd90d
commit 4ed2893065
10 changed files with 347 additions and 79 deletions

View File

@@ -9,23 +9,31 @@ import Markdown
import RadialStatusNode
import AppBundle
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import AnimationUI
import SyncCore
import Postbox
import TelegramCore
import StickerResources
final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let elevatedLayout: Bool
private let statusNode: RadialStatusNode
private var statusNode: RadialStatusNode?
private let timerTextNode: ImmediateTextNode
private let iconNode: ASImageNode?
private let iconCheckNode: RadialStatusNode?
private let animationNode: AnimationNode?
private let animatedStickerNode: AnimatedStickerNode?
private var animatedStickerNode: AnimatedStickerNode?
private var stillStickerNode: TransformImageNode?
private var stickerImageSize: CGSize?
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let buttonTextNode: ImmediateTextNode
private let buttonNode: HighlightTrackingButtonNode
private let undoButtonTextNode: ImmediateTextNode
private let undoButtonNode: HighlightTrackingButtonNode
private let panelNode: ASDisplayNode
private let panelWrapperNode: ASDisplayNode
private let action: (Bool) -> Void
private let action: (UndoOverlayAction) -> Bool
private let dismiss: () -> Void
private let effectView: UIView
@@ -38,7 +46,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private var validLayout: ContainerViewLayout?
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, action: @escaping (Bool) -> Void, dismiss: @escaping () -> Void) {
private var fetchResourceDisposable: Disposable?
init(presentationData: PresentationData, content: UndoOverlayContent, elevatedLayout: Bool, action: @escaping (UndoOverlayAction) -> Bool, dismiss: @escaping () -> Void) {
self.elevatedLayout = elevatedLayout
self.action = action
@@ -55,6 +65,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.displaysAsynchronously = false
self.textNode.maximumNumberOfLines = 0
self.buttonNode = HighlightTrackingButtonNode()
var displayUndo = true
var undoText = presentationData.strings.Undo_Undo
var undoTextColor = UIColor(rgb: 0x5ac8fa)
@@ -74,6 +86,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
displayUndo = true
self.originalRemainingSeconds = 5
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
case let .archivedChat(_, title, text, undo):
if undo {
self.iconNode = ASImageNode()
@@ -129,12 +142,17 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.iconCheckNode = nil
self.animationNode = AnimationNode(animation: "anim_success", colors: ["info1.info1.stroke": self.animationBackgroundColor, "info2.info2.Fill": self.animationBackgroundColor], scale: 1.0)
self.animatedStickerNode = nil
undoTextColor = UIColor(rgb: 0xff7b74)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white)
self.textNode.attributedText = attributedText
displayUndo = true
undoText = cancel
undoTextColor = UIColor(rgb: 0xff7b74)
self.originalRemainingSeconds = 3
case let .emoji(path, text):
self.iconNode = nil
@@ -161,17 +179,102 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.textNode.maximumNumberOfLines = 2
displayUndo = false
self.originalRemainingSeconds = 5
case let .stickersModified(title, text, undo, info, topItem, account):
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
let stillStickerNode = TransformImageNode()
self.stillStickerNode = stillStickerNode
enum StickerPackThumbnailItem {
case still(TelegramMediaImageRepresentation)
case animated(MediaResource)
}
var thumbnailItem: StickerPackThumbnailItem?
var resourceReference: MediaResourceReference?
if let thumbnail = info.thumbnail {
if info.flags.contains(.isAnimated) {
thumbnailItem = .animated(thumbnail.resource)
resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)
} else {
thumbnailItem = .still(thumbnail)
resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)
}
} else if let item = topItem as? StickerPackItem {
if item.file.isAnimatedSticker {
thumbnailItem = .animated(item.file.resource)
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource)
} else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource {
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource))
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource)
}
}
var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedFetchSignal: Signal<FetchResourceSourceType, FetchResourceError>?
let imageBoundingSize = CGSize(width: 34.0, height: 34.0)
if let thumbnailItem = thumbnailItem {
switch thumbnailItem {
case let .still(representation):
let stillImageSize = representation.dimensions.cgSize.aspectFitted(imageBoundingSize)
self.stickerImageSize = stillImageSize
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource)
case let .animated(resource):
self.stickerImageSize = imageBoundingSize
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true)
}
if let resourceReference = resourceReference {
updatedFetchSignal = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference)
}
} else {
updatedImageSignal = .single({ _ in return nil })
updatedFetchSignal = .complete()
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
displayUndo = undo
self.originalRemainingSeconds = 2
if let updatedFetchSignal = updatedFetchSignal {
self.fetchResourceDisposable = updatedFetchSignal.start()
}
if let updatedImageSignal = updatedImageSignal {
stillStickerNode.setSignal(updatedImageSignal)
}
if let thumbnailItem = thumbnailItem {
switch thumbnailItem {
case .still:
break
case let .animated(resource):
let animatedStickerNode = AnimatedStickerNode()
self.animatedStickerNode = animatedStickerNode
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached)
}
}
}
self.remainingSeconds = self.originalRemainingSeconds
self.statusNode = RadialStatusNode(backgroundNodeColor: .clear)
self.undoButtonTextNode = ImmediateTextNode()
self.undoButtonTextNode.displaysAsynchronously = false
self.undoButtonTextNode.attributedText = NSAttributedString(string: undoText, font: Font.regular(17.0), textColor: undoTextColor)
self.buttonTextNode = ImmediateTextNode()
self.buttonTextNode.displaysAsynchronously = false
self.buttonTextNode.attributedText = NSAttributedString(string: undoText, font: Font.regular(17.0), textColor: undoTextColor)
self.buttonNode = HighlightTrackingButtonNode()
self.undoButtonNode = HighlightTrackingButtonNode()
self.panelNode = ASDisplayNode()
if presentationData.theme.overallDarkAppearance {
@@ -191,35 +294,46 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content {
case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode)
self.panelWrapperNode.addSubnode(self.statusNode)
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded:
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified:
break
}
self.statusNode.flatMap(self.panelWrapperNode.addSubnode)
self.iconNode.flatMap(self.panelWrapperNode.addSubnode)
self.iconCheckNode.flatMap(self.panelWrapperNode.addSubnode)
self.animationNode.flatMap(self.panelWrapperNode.addSubnode)
self.stillStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.animatedStickerNode.flatMap(self.panelWrapperNode.addSubnode)
self.panelWrapperNode.addSubnode(self.titleNode)
self.panelWrapperNode.addSubnode(self.textNode)
self.panelWrapperNode.addSubnode(self.buttonNode)
if displayUndo {
self.panelWrapperNode.addSubnode(self.buttonTextNode)
self.panelWrapperNode.addSubnode(self.buttonNode)
self.panelWrapperNode.addSubnode(self.undoButtonTextNode)
self.panelWrapperNode.addSubnode(self.undoButtonNode)
}
self.addSubnode(self.panelNode)
self.addSubnode(self.panelWrapperNode)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
self.undoButtonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.buttonTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.buttonTextNode.alpha = 0.4
strongSelf.undoButtonTextNode.layer.removeAnimation(forKey: "opacity")
strongSelf.undoButtonTextNode.alpha = 0.4
} else {
strongSelf.buttonTextNode.alpha = 1.0
strongSelf.buttonTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.undoButtonTextNode.alpha = 1.0
strongSelf.undoButtonTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.undoButtonNode.addTarget(self, action: #selector(self.undoButtonPressed), forControlEvents: .touchUpInside)
self.animatedStickerNode?.started = { [weak self] in
self?.stillStickerNode?.isHidden = true
}
}
deinit {
self.fetchResourceDisposable?.dispose()
}
override func didLoad() {
@@ -230,7 +344,13 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
}
@objc private func buttonPressed() {
self.action(false)
if self.action(.info) {
self.dismiss()
}
}
@objc private func undoButtonPressed() {
self.action(.undo)
self.dismiss()
}
@@ -239,7 +359,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.remainingSeconds -= 1
}
if self.remainingSeconds == 0 {
self.action(true)
self.action(.commit)
self.dismiss()
} else {
if !self.timerTextNode.bounds.size.width.isZero, let snapshot = self.timerTextNode.view.snapshotContentTree() {
@@ -286,9 +406,9 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let margin: CGFloat = 16.0
let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
let buttonMinX: CGFloat
if self.buttonNode.supernode != nil {
if self.undoButtonNode.supernode != nil {
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset - buttonTextSize.width - margin * 2.0
} else {
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset
@@ -316,8 +436,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - margin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
transition.updateFrame(node: self.buttonTextNode, frame: buttonTextFrame)
self.buttonNode.frame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - margin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + margin, height: contentHeight))
transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - margin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + margin, height: contentHeight))
self.undoButtonNode.frame = undoButtonFrame
self.buttonNode.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: CGSize(width: undoButtonFrame.minX - layout.safeInsets.left, height: contentHeight))
var textContentHeight = textSize.height
var textOffset: CGFloat = 0.0
@@ -351,7 +475,22 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
transition.updateFrame(node: animationNode, frame: iconFrame)
}
if let animatedStickerNode = self.animatedStickerNode {
if let stickerImageSize = self.stickerImageSize {
let iconSize = stickerImageSize
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
if let stillStickerNode = self.stillStickerNode {
let makeImageLayout = stillStickerNode.asyncLayout()
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stickerImageSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
let _ = imageApply()
transition.updateFrame(node: stillStickerNode, frame: iconFrame)
}
if let animatedStickerNode = self.animatedStickerNode {
animatedStickerNode.updateLayout(size: iconFrame.size)
transition.updateFrame(node: animatedStickerNode, frame: iconFrame)
}
} else if let animatedStickerNode = self.animatedStickerNode {
let iconSize = CGSize(width: 32.0, height: 32.0)
let iconFrame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) / 2.0), y: floor((contentHeight - iconSize.height) / 2.0)), size: iconSize)
animatedStickerNode.updateLayout(size: iconFrame.size)
@@ -361,9 +500,11 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let timerTextSize = self.timerTextNode.updateLayout(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: self.timerTextNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - timerTextSize.width) / 2.0), y: floor((contentHeight - timerTextSize.height) / 2.0)), size: timerTextSize))
let statusSize: CGFloat = 30.0
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize)))
if firstLayout {
self.statusNode.transitionToState(.secretTimeout(color: .white, icon: nil, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
if let statusNode = self.statusNode {
transition.updateFrame(node: statusNode, frame: CGRect(origin: CGPoint(x: floor((leftInset - statusSize) / 2.0), y: floor((contentHeight - statusSize) / 2.0)), size: CGSize(width: statusSize, height: statusSize)))
if firstLayout {
statusNode.transitionToState(.secretTimeout(color: .white, icon: nil, beginTime: CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970, timeout: Double(self.remainingSeconds), sparks: false), completion: {})
}
}
}